Re: ANSI SQL proposal: SELECT DISTINCT ON (... ORDER BY ...) and UNION DISTINCT ON (... ORDER BY ...)

From: Hannu Krosing <hannuk(at)google(dot)com>
To: Vik Fearing <vik(at)postgresfriends(dot)org>
Cc: pgsql-hackers <pgsql-hackers(at)postgresql(dot)org>, Peter Eisentraut <peter(at)eisentraut(dot)org>, Dilip Kumar <dilipkumarb(at)google(dot)com>
Subject: Re: ANSI SQL proposal: SELECT DISTINCT ON (... ORDER BY ...) and UNION DISTINCT ON (... ORDER BY ...)
Date: 2026-10-17 09:20:29
Message-ID: CAMT0RQSS=Deeu2JJ0TKcwGAN+fJmkux6JM=ggE39b6tX_G5jYQ@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Subject: [PATCH v3 0/5] Support UNION DISTINCT ON and inline DISTINCT
ON (... ORDER BY ...)

Hi hackers,

Attached is v3 of the patch series introducing:

1. Inline ORDER BY inside DISTINCT ON for SELECT queries:
SELECT DISTINCT ON (a ORDER BY b DESC) a, b, c FROM t ORDER BY c;
Along with HashAggregate support for DISTINCT ON tie-breaking via
ReplaceTupleHashEntryIfBetter().

2. UNION DISTINCT ON (expr [, ...] [ORDER BY sort_expr [, ...]]) for
non-recursive set operations:
SELECT a, b FROM t1
UNION DISTINCT ON (a ORDER BY b DESC)
SELECT a, b FROM t2;

3. Dynamic state-space pruning in recursive CTEs (WITH RECURSIVE) using
UNION DISTINCT ON (key ORDER BY cost), enabling Bellman-Ford /
shortest-path and optimal state-space search directly in SQL without
infinite loops or post-hoc filtering.

Changes in v3:
- Fixed tuple extraction in ReplaceTupleHashEntryIfBetter() to use a
dedicated TupleTableSlot and slot_getattr() instead of
minimal_tuple_getattr(), ensuring safe handling of pass-by-reference /
varlena attributes and arbitrary targetlist column positions.
- Evaluated sort comparators in table->tempcxt with per-comparison memory
context resets and added in-place MinimalTuple overwrite when the
replacement tuple fits in the existing allocation.
- Extracted shared ExecPrepareTupleSortSupport() helper and properly
initialized ssup_cxt in both HashAggregate and RecursiveUnion.
- Replaced RecursiveUnion result_table Tuplestore materialization with
direct TupleHashIterator scanning, added seed-phase deduplication before
populating working_table, and added intra-wave obsolete-tuple pruning.
- Replaced pre-parse col_in_distinct_on() inspection with standard
targetlist resolution via transformDistinctOnClause() and post-type-
resolution build_distinct_sort_clause().
- Normalized SetOperationStmt groupClauses and sortClauses so
tleSortGroupRef == resno holds uniformly across set-operation planning,
and enabled MergeAppend + Unique paths for UNION DISTINCT ON with inline
ORDER BY.
- Added support for nested / multi-way UNION DISTINCT ON set-operation
trees, guarded Append child flattening when groupClauses/sortClauses
differ, and extended subquery WHERE pushdown safety checks to inspect
nested set-operation trees recursively.
- Added EXPLAIN "Sort Key:" output for HashAggregate and RecursiveUnion
when tie-breaker sort columns are active, and added full pg_get_viewdef()
deparsing in ruleutils.c for both SELECT DISTINCT ON (... ORDER BY ...)
and UNION DISTINCT ON (... [ORDER BY ...]).
- Expanded regression test coverage in select_distinct_on.sql and
union.sql.

Hannu Krosing (5):
Refactor set operation group clauses to decouple from targetlist
positions
Support inline ORDER BY in DISTINCT ON for SELECT and enable HashAgg
Support UNION DISTINCT ON for non-recursive set operations
Support dynamic pruning in recursive UNION DISTINCT ON
Fix bugs, security issues, and optimizations in DISTINCT ON and UNION
DISTINCT ON

doc/src/sgml/ref/select.sgml | 47 ++-
src/backend/commands/explain.c | 25 +-
src/backend/executor/execGrouping.c | 136 ++++++
src/backend/executor/nodeAgg.c | 151 +++++--
src/backend/executor/nodeRecursiveunion.c | 234 ++++++++++-
src/backend/nodes/nodeFuncs.c | 2 +
src/backend/optimizer/path/allpaths.c | 62 ++-
src/backend/optimizer/path/equivclass.c | 45 +-
src/backend/optimizer/plan/createplan.c | 88 ++++
src/backend/optimizer/plan/planner.c | 239 +++++++----
src/backend/optimizer/prep/prepunion.c | 91 ++--
src/backend/optimizer/util/pathnode.c | 7 +-
src/backend/parser/analyze.c | 204 +++++++--
src/backend/parser/gram.y | 45 +-
src/backend/parser/parse_clause.c | 34 +-
src/backend/parser/parse_cte.c | 28 ++
src/backend/rewrite/rewriteSearchCycle.c | 27 +-
src/backend/utils/adt/ruleutils.c | 34 +-
src/include/executor/executor.h | 11 +
src/include/executor/nodeAgg.h | 2 +
src/include/nodes/execnodes.h | 6 +
src/include/nodes/parsenodes.h | 4 +
src/include/nodes/pathnodes.h | 2 +
src/include/nodes/plannodes.h | 14 +
src/include/optimizer/pathnode.h | 4 +-
src/include/parser/parse_clause.h | 3 +-
.../regress/expected/select_distinct_on.out | 239 +++++++++++
src/test/regress/expected/union.out | 390 ++++++++++++++++++
src/test/regress/sql/select_distinct_on.sql | 128 ++++++
src/test/regress/sql/union.sql | 202 +++++++++
30 files changed, 2251 insertions(+), 253 deletions(-)

On Fri, Sep 18, 2026 at 7:01 PM Hannu Krosing <hannuk(at)google(dot)com> wrote:
>
> At first glance it looks very much like what DuckDB does with USING
> KEY syntax they have described here
> https://www.cidrdb.org/cidr2023/papers/p14-hirn.pdf
>
> I learned about DuckDB USING KEY just 2 days ago from
> https://lnkd.in/p/ebCwa9Wk
>
> -----
> Hannu

Attachment Content-Type Size
v3-0001-Refactor-set-operation-group-clauses-to-decouple-.patch text/x-patch 9.7 KB
v3-0002-Support-inline-ORDER-BY-in-DISTINCT-ON-for-SELECT.patch text/x-patch 28.9 KB
v3-0003-Support-UNION-DISTINCT-ON-for-non-recursive-set-o.patch text/x-patch 21.0 KB
v3-0004-Support-dynamic-pruning-in-recursive-UNION-DISTIN.patch text/x-patch 17.8 KB
v3-0005-Fix-bugs-security-issues-and-optimizations-in-DIS.patch text/x-patch 113.1 KB

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Manu 2026-10-17 10:02:50 Re: [PG19] Promotion hangs behind a backend blocked on a stuck client
Previous Message Richard Guo 2026-10-17 08:34:53 Re: Assert failure in standard_join_search()