diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index 0e6969c5d5a..bf0c43b23ee 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -757,8 +757,9 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list) bool query_supports_distinctness(Query *query) { - /* SRFs break distinctness except with DISTINCT, see below */ - if (query->hasTargetSRFs && query->distinctClause == NIL) + /* SRFs break distinctness except with plain DISTINCT, see below */ + if (query->hasTargetSRFs && + (query->distinctClause == NIL || query->hasDistinctOn)) return false; /* check for features we can prove distinctness with */ @@ -842,10 +843,15 @@ query_is_distinct_for_with_collations(Query *query, List *distinct_cols) /* * DISTINCT (including DISTINCT ON) guarantees uniqueness if all the * columns in the DISTINCT clause appear in colnos and operator semantics - * match. This is true even if there are SRFs in the DISTINCT columns or - * elsewhere in the tlist. + * match. With plain DISTINCT this is true even if there are SRFs in the + * tlist, since they are all DISTINCT columns and hence get expanded + * before the Unique step. But with DISTINCT ON, the planner may postpone + * SRFs that are not DISTINCT ON or ORDER BY columns until after the + * Unique step, which can produce duplicates of the DISTINCT ON columns; + * so we can't rely on DISTINCT ON if there are any tlist SRFs. */ - if (query->distinctClause) + if (query->distinctClause && + !(query->hasTargetSRFs && query->hasDistinctOn)) { foreach(l, query->distinctClause) { diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 2eca3782e60..4c5920df8b3 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -4678,6 +4678,39 @@ select d.* from d left join (select distinct * from b) s -> Seq Scan on d (9 rows) +-- join removal is not possible when the subquery has DISTINCT ON and a +-- set-returning function that is not a DISTINCT ON column +explain (costs off) +select d.* from d left join + (select distinct on (id) id, generate_series(1, 2) as g from b order by id) s + on d.a = s.id + order by 1, 2; + QUERY PLAN +----------------------------------------------------------------------- + Sort + Sort Key: d.a, d.b + -> Hash Left Join + Hash Cond: (d.a = s.id) + -> Seq Scan on d + -> Hash + -> Subquery Scan on s + -> ProjectSet + -> Unique + -> Index Only Scan using b_pkey on b +(10 rows) + +select d.* from d left join + (select distinct on (id) id, generate_series(1, 2) as g from b order by id) s + on d.a = s.id + order by 1, 2; + a | b +---+--- + 1 | 3 + 1 | 3 + 2 | 2 + 3 | 1 +(4 rows) + -- check join removal works when uniqueness of the join condition is enforced -- by a UNION explain (costs off) diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 9d622d18a12..146a262ddf4 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1622,6 +1622,18 @@ explain (costs off) select d.* from d left join (select distinct * from b) s on d.a = s.id; +-- join removal is not possible when the subquery has DISTINCT ON and a +-- set-returning function that is not a DISTINCT ON column +explain (costs off) +select d.* from d left join + (select distinct on (id) id, generate_series(1, 2) as g from b order by id) s + on d.a = s.id + order by 1, 2; +select d.* from d left join + (select distinct on (id) id, generate_series(1, 2) as g from b order by id) s + on d.a = s.id + order by 1, 2; + -- check join removal works when uniqueness of the join condition is enforced -- by a UNION explain (costs off)