| From: | Henson Choi <assam258(at)gmail(dot)com> |
|---|---|
| To: | Keyerror Smart <smartkeyerror(at)gmail(dot)com> |
| Cc: | pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Subject: | Re: [PATCH] Consider window functions when reordering GROUP BY items |
| Date: | 2026-11-01 06:30:30 |
| Message-ID: | CAAAe_zC_AdBTca_SQoL0Qkj1U2Hj_aBpRS0tS73y2TWtLT1oag@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi Zhenglong,
Thanks for v2. With both patches applied on master, make check
passes and every plan in your mail comes out as shown.
I did find two cases where a query that works on master gets slower
or fails with v2.
> 0002 is the window patch with the change you suggested, rebased on
> 0001.
1. Partial aggregation below a join goes away.
When a window sorts a GROUP BY column in descending order, the
partial aggregation that master does below the join off an ascending
index is gone in v2:
CREATE TABLE ea (i1 int, i2 int, x int);
INSERT INTO ea SELECT i % 100, (i / 100) % 1000, i
FROM generate_series(1, 1000000) i;
CREATE INDEX ON ea (i1, i2);
CREATE TABLE eb (k int, v int);
INSERT INTO eb SELECT i, i FROM generate_series(0, 99) i;
VACUUM ANALYZE ea, eb;
SET enable_hashagg = off;
EXPLAIN (COSTS OFF)
SELECT a.i1, a.i2, sum(a.x),
count(*) OVER (PARTITION BY a.i1 ORDER BY a.i2 DESC)
FROM ea a JOIN eb b ON a.i1 = b.k
GROUP BY a.i1, a.i2;
-- master
WindowAgg
-> Incremental Sort
Sort Key: a.i1, a.i2 DESC
Presorted Key: a.i1
-> Finalize GroupAggregate
Group Key: a.i1, a.i2
-> Merge Join
Merge Cond: (a.i1 = b.k)
-> Partial GroupAggregate
Group Key: a.i1, a.i2
-> Index Scan using ea_i1_i2_idx on ea a
...
-- v2
WindowAgg
-> Incremental Sort
Sort Key: a.i1, a.i2 DESC
Presorted Key: a.i1
-> GroupAggregate
Group Key: a.i1, a.i2
-> Merge Join
Merge Cond: (a.i1 = b.k)
-> Index Scan using ea_i1_i2_idx on ea a
...
The Incremental Sort for the window stays either way, so nothing is
gained; only the cost and the run time go up.
> 0001 lets [...] match an input pathkey with
> the same equivalence class and operator family regardless of
> direction
2. GROUP BY fails on an operator class without the reverse operator.
With a btree operator class that has only the less-than and equals
operators, a GROUP BY that works on master fails in v2 with a
"should not happen" error:
CREATE TYPE color AS ENUM ('r', 'g', 'b');
CREATE FUNCTION color_lt(color, color) RETURNS bool
LANGUAGE internal IMMUTABLE STRICT AS 'enum_lt';
CREATE FUNCTION color_eq(color, color) RETURNS bool
LANGUAGE internal IMMUTABLE STRICT AS 'enum_eq';
CREATE FUNCTION color_cmp(color, color) RETURNS int
LANGUAGE internal IMMUTABLE STRICT AS 'enum_cmp';
CREATE OPERATOR <<< (leftarg = color, rightarg = color,
function = color_lt);
CREATE OPERATOR === (leftarg = color, rightarg = color,
function = color_eq);
CREATE OPERATOR CLASS color_ops DEFAULT FOR TYPE color
USING btree AS
OPERATOR 1 <<<, OPERATOR 3 ===,
FUNCTION 1 color_cmp(color, color);
CREATE TABLE tc (c color);
INSERT INTO tc SELECT (ARRAY['r', 'g', 'b'])[1 + i % 3]::color
FROM generate_series(1, 3000) i;
CREATE INDEX ON tc (c);
ANALYZE tc;
SELECT c, count(*) FROM tc GROUP BY c;
-- master: the counts for r, g and b
-- v2: ERROR: missing operator 5(...) in opfamily ...
Such an operator class breaks the btree rules. Still, the missing
operator is needed only for one plan being considered, not for the
result, so wouldn't it be better to skip that plan than to raise an
error?
For the tests, I'd add:
- The two cases above.
- A query with two windows. None of the new queries in window.sql
has more than one OVER. For example, with two windows sorting c in
opposite directions, v2 does one sort fewer than master:
SELECT a, b, c,
count(*) OVER (PARTITION BY b ORDER BY c),
count(*) OVER (PARTITION BY b ORDER BY c DESC)
FROM t GROUP BY a, b, c;
- The groupwin result check. The test EXPLAINs one query and runs
another, and thanks to its outer ORDER BY the one it runs has the
same plan as on master, so the result it checks does not come from
v2's change. Wrapped in a subquery, master sorts twice and v2
once, so the result comes from the plan that v2 changes. Cases
like this, where the GROUP BY keys take the window's direction,
are where results most need checking:
SELECT * FROM
(SELECT x, z, count(*) c,
sum(count(*)) OVER (PARTITION BY x
ORDER BY z DESC NULLS LAST) s
FROM groupwin GROUP BY x, z) ss
ORDER BY x, z DESC NULLS LAST;
I tried such cases with different directions in the outer ORDER
BY, joins, outer windows and LIMIT, and the results all matched
master.
Best regards,
Henson
| From | Date | Subject | |
|---|---|---|---|
| Previous Message | Tender Wang | 2026-11-01 06:11:41 | Re: Remove unused arguments from split_selfjoin_quals() |