Skip to content

Commit f1da588

Browse files
author
hackorum
committed
Apply 0002-skip-distinct-when-input-provably-unique.patch
1 parent d6347b9 commit f1da588

3 files changed

Lines changed: 223 additions & 0 deletions

File tree

src/backend/optimizer/plan/planner.c

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ static void create_one_window_path(PlannerInfo *root,
208208
PathTarget *output_target,
209209
WindowFuncLists *wflists,
210210
List *activeWindows);
211+
static bool distinct_input_provably_unique(PlannerInfo *root);
211212
static RelOptInfo *create_distinct_paths(PlannerInfo *root,
212213
RelOptInfo *input_rel,
213214
PathTarget *target);
@@ -2134,6 +2135,14 @@ grouping_planner(PlannerInfo *root, double tuple_fraction,
21342135
*/
21352136
root->distinct_elided = true;
21362137
}
2138+
else if (distinct_input_provably_unique(root))
2139+
{
2140+
/*
2141+
* The input is already distinct on the DISTINCT columns, so
2142+
* enforcement would be a no-op; skip building it.
2143+
*/
2144+
root->distinct_elided = true;
2145+
}
21372146
else
21382147
current_rel = create_distinct_paths(root,
21392148
current_rel,
@@ -5069,6 +5078,79 @@ create_one_window_path(PlannerInfo *root,
50695078
add_path(window_rel, path);
50705079
}
50715080

5081+
/*
5082+
* distinct_input_provably_unique
5083+
* Detect whether this query's DISTINCT clause is a no-op because its
5084+
* input is provably distinct on the DISTINCT columns already.
5085+
*
5086+
* We handle only the simple but common shape where the query reads exactly
5087+
* one subquery RTE (no joins -- joining can multiply rows) and every
5088+
* DISTINCT column is a plain Var of that subquery, and the subquery's own
5089+
* query structure (DISTINCT, GROUP BY, aggregation, non-ALL set operation)
5090+
* proves distinctness via query_is_distinct_for(). WHERE quals are fine:
5091+
* filtering cannot introduce duplicates. We derive distinctness only from
5092+
* the subquery's structure, never from catalog uniqueness metadata.
5093+
*
5094+
* We bail out on target SRFs (they multiply rows after the subquery scan),
5095+
* and conservatively on window functions, though those merely add columns.
5096+
* DISTINCT ON is out too: with more than one row per group it changes which
5097+
* rows survive, so proving groups of one would need all ON columns covered;
5098+
* we don't bother. The caller must not modify parse->distinctClause; on a
5099+
* true return it merely refrains from building enforcement paths.
5100+
*/
5101+
static bool
5102+
distinct_input_provably_unique(PlannerInfo *root)
5103+
{
5104+
Query *parse = root->parse;
5105+
RangeTblRef *rtr;
5106+
RangeTblEntry *rte;
5107+
List *distinct_cols = NIL;
5108+
ListCell *lc;
5109+
5110+
if (parse->hasDistinctOn)
5111+
return false;
5112+
5113+
/* bail out on anything that complicates the proof */
5114+
if (parse->hasTargetSRFs || parse->hasWindowFuncs || parse->hasAggs ||
5115+
parse->groupClause || parse->groupingSets || parse->havingQual)
5116+
return false;
5117+
5118+
/* the input must be exactly one subquery RTE */
5119+
if (list_length(parse->jointree->fromlist) != 1)
5120+
return false;
5121+
rtr = (RangeTblRef *) linitial(parse->jointree->fromlist);
5122+
if (!IsA(rtr, RangeTblRef))
5123+
return false;
5124+
rte = root->simple_rte_array[rtr->rtindex];
5125+
if (rte->rtekind != RTE_SUBQUERY)
5126+
return false;
5127+
if (!query_supports_distinctness(rte->subquery))
5128+
return false;
5129+
5130+
/* every DISTINCT column must be a plain Var of that subquery */
5131+
foreach(lc, root->processed_distinctClause)
5132+
{
5133+
SortGroupClause *sgc = lfirst_node(SortGroupClause, lc);
5134+
TargetEntry *tle = get_sortgroupclause_tle(sgc, root->processed_tlist);
5135+
Var *var = (Var *) tle->expr;
5136+
DistinctColInfo *info;
5137+
5138+
if (!IsA(var, Var) ||
5139+
var->varno != rtr->rtindex ||
5140+
var->varlevelsup != 0 ||
5141+
var->varattno <= 0)
5142+
return false;
5143+
5144+
info = palloc_object(DistinctColInfo);
5145+
info->colno = var->varattno;
5146+
info->opid = sgc->eqop;
5147+
info->collid = var->varcollid;
5148+
distinct_cols = lappend(distinct_cols, info);
5149+
}
5150+
5151+
return query_is_distinct_for(rte->subquery, distinct_cols);
5152+
}
5153+
50725154
/*
50735155
* create_distinct_paths
50745156
*

src/test/regress/expected/select_distinct.out

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,3 +596,110 @@ SELECT DISTINCT y, x FROM distinct_tbl ORDER BY y;
596596

597597
RESET enable_hashagg;
598598
DROP TABLE distinct_tbl;
599+
--
600+
-- Skip DISTINCT enforcement when the input is provably distinct already
601+
-- (structural proof via the input subquery's own DISTINCT/GROUP BY/setop).
602+
--
603+
-- elided: grouped subquery proves distinctness
604+
EXPLAIN (COSTS OFF)
605+
SELECT DISTINCT four FROM (SELECT four FROM tenk1 GROUP BY four) ss;
606+
QUERY PLAN
607+
-------------------------
608+
HashAggregate
609+
Group Key: tenk1.four
610+
-> Seq Scan on tenk1
611+
(3 rows)
612+
613+
-- elided: superset of the unique columns is still unique
614+
EXPLAIN (COSTS OFF)
615+
SELECT DISTINCT four, c FROM
616+
(SELECT four, count(*) AS c FROM tenk1 GROUP BY four) ss;
617+
QUERY PLAN
618+
-------------------------
619+
HashAggregate
620+
Group Key: tenk1.four
621+
-> Seq Scan on tenk1
622+
(3 rows)
623+
624+
-- elided: aggregate without GROUP BY returns one row
625+
EXPLAIN (COSTS OFF)
626+
SELECT DISTINCT c FROM (SELECT count(*) AS c FROM tenk1) ss;
627+
QUERY PLAN
628+
----------------------------------------------------
629+
Aggregate
630+
-> Index Only Scan using tenk1_hundred on tenk1
631+
(2 rows)
632+
633+
-- elided: non-ALL setop output is unique; WHERE cannot break uniqueness
634+
EXPLAIN (COSTS OFF)
635+
SELECT DISTINCT x FROM
636+
(SELECT four AS x FROM tenk1 UNION SELECT ten FROM tenk1) ss WHERE x > 2;
637+
QUERY PLAN
638+
---------------------------------------
639+
HashAggregate
640+
Group Key: tenk1.four
641+
-> Append
642+
-> Seq Scan on tenk1
643+
Filter: (four > 2)
644+
-> Seq Scan on tenk1 tenk1_1
645+
Filter: (ten > 2)
646+
(7 rows)
647+
648+
-- not elided: subset of output that isn't the unique key
649+
EXPLAIN (COSTS OFF)
650+
SELECT DISTINCT c FROM
651+
(SELECT four, count(*) AS c FROM tenk1 GROUP BY four) ss;
652+
QUERY PLAN
653+
-------------------------------------------
654+
Unique
655+
-> Sort
656+
Sort Key: ss.c
657+
-> Subquery Scan on ss
658+
-> HashAggregate
659+
Group Key: tenk1.four
660+
-> Seq Scan on tenk1
661+
(7 rows)
662+
663+
-- not elided: joining two unique subqueries can duplicate rows
664+
EXPLAIN (COSTS OFF)
665+
SELECT DISTINCT a.four FROM
666+
(SELECT four FROM tenk1 GROUP BY four) a,
667+
(SELECT ten FROM tenk1 GROUP BY ten) b;
668+
QUERY PLAN
669+
---------------------------------------------------------
670+
Unique
671+
-> Sort
672+
Sort Key: tenk1_1.four
673+
-> Nested Loop
674+
-> HashAggregate
675+
Group Key: tenk1.ten
676+
-> Seq Scan on tenk1
677+
-> Materialize
678+
-> HashAggregate
679+
Group Key: tenk1_1.four
680+
-> Seq Scan on tenk1 tenk1_1
681+
(11 rows)
682+
683+
-- not elided: UNION ALL proves nothing
684+
EXPLAIN (COSTS OFF)
685+
SELECT DISTINCT x FROM
686+
(SELECT four AS x FROM tenk1 UNION ALL SELECT ten FROM tenk1) ss;
687+
QUERY PLAN
688+
---------------------------------------
689+
HashAggregate
690+
Group Key: tenk1.four
691+
-> Append
692+
-> Seq Scan on tenk1
693+
-> Seq Scan on tenk1 tenk1_1
694+
(5 rows)
695+
696+
-- results sanity
697+
SELECT DISTINCT four FROM (SELECT four FROM tenk1 GROUP BY four) ss ORDER BY 1;
698+
four
699+
------
700+
0
701+
1
702+
2
703+
3
704+
(4 rows)
705+

src/test/regress/sql/select_distinct.sql

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,37 @@ SELECT DISTINCT y, x FROM distinct_tbl ORDER BY y;
274274
RESET enable_hashagg;
275275

276276
DROP TABLE distinct_tbl;
277+
278+
--
279+
-- Skip DISTINCT enforcement when the input is provably distinct already
280+
-- (structural proof via the input subquery's own DISTINCT/GROUP BY/setop).
281+
--
282+
-- elided: grouped subquery proves distinctness
283+
EXPLAIN (COSTS OFF)
284+
SELECT DISTINCT four FROM (SELECT four FROM tenk1 GROUP BY four) ss;
285+
-- elided: superset of the unique columns is still unique
286+
EXPLAIN (COSTS OFF)
287+
SELECT DISTINCT four, c FROM
288+
(SELECT four, count(*) AS c FROM tenk1 GROUP BY four) ss;
289+
-- elided: aggregate without GROUP BY returns one row
290+
EXPLAIN (COSTS OFF)
291+
SELECT DISTINCT c FROM (SELECT count(*) AS c FROM tenk1) ss;
292+
-- elided: non-ALL setop output is unique; WHERE cannot break uniqueness
293+
EXPLAIN (COSTS OFF)
294+
SELECT DISTINCT x FROM
295+
(SELECT four AS x FROM tenk1 UNION SELECT ten FROM tenk1) ss WHERE x > 2;
296+
-- not elided: subset of output that isn't the unique key
297+
EXPLAIN (COSTS OFF)
298+
SELECT DISTINCT c FROM
299+
(SELECT four, count(*) AS c FROM tenk1 GROUP BY four) ss;
300+
-- not elided: joining two unique subqueries can duplicate rows
301+
EXPLAIN (COSTS OFF)
302+
SELECT DISTINCT a.four FROM
303+
(SELECT four FROM tenk1 GROUP BY four) a,
304+
(SELECT ten FROM tenk1 GROUP BY ten) b;
305+
-- not elided: UNION ALL proves nothing
306+
EXPLAIN (COSTS OFF)
307+
SELECT DISTINCT x FROM
308+
(SELECT four AS x FROM tenk1 UNION ALL SELECT ten FROM tenk1) ss;
309+
-- results sanity
310+
SELECT DISTINCT four FROM (SELECT four FROM tenk1 GROUP BY four) ss ORDER BY 1;

0 commit comments

Comments
 (0)