]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Simplify COALESCE() with one surviving argument.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 3 Jul 2025 21:39:53 +0000 (17:39 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 3 Jul 2025 21:39:53 +0000 (17:39 -0400)
If, after removal of useless null-constant arguments, a CoalesceExpr
has exactly one remaining argument, we can just take that argument as
the result, without bothering to wrap a new CoalesceExpr around it.
This isn't likely to produce any great improvement in runtime per se,
but it can lead to better plans since the planner no longer has to
treat the expression as non-strict.

However, there were a few regression test cases that intentionally
wrote COALESCE(x) as a shorthand way of creating a non-strict
subexpression.  To avoid ruining the intent of those tests, write
COALESCE(x,x) instead.  (If anyone ever proposes de-duplicating
COALESCE arguments, we'll need another iteration of this arms race.
But it seems pretty unlikely that such an optimization would be
worthwhile.)

Author: Maksim Milyutin <maksim.milyutin@tantorlabs.ru>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/8e8573c3-1411-448d-877e-53258b7b2be0@tantorlabs.ru

src/backend/optimizer/util/clauses.c
src/test/regress/expected/join.out
src/test/regress/expected/subselect.out
src/test/regress/sql/join.sql
src/test/regress/sql/subselect.sql

index 26a3e0500866cff85a87037fa39a2c6c796b1c62..f45131c34c5a3931bec8ddc9670dca32c303b2a9 100644 (file)
@@ -3333,6 +3333,13 @@ eval_const_expressions_mutator(Node *node,
                                                                                                  -1,
                                                                                                  coalesceexpr->coalescecollid);
 
+                               /*
+                                * If there's exactly one surviving argument, we no longer
+                                * need COALESCE at all: the result is that argument
+                                */
+                               if (list_length(newargs) == 1)
+                                       return (Node *) linitial(newargs);
+
                                newcoalesce = makeNode(CoalesceExpr);
                                newcoalesce->coalescetype = coalesceexpr->coalescetype;
                                newcoalesce->coalescecollid = coalesceexpr->coalescecollid;
index 390aabfb34b9a4f81b5c9c5fc1150503e65e9e5b..46ddfa844c5953ff8a113bbdf82bf63bf1be070f 100644 (file)
@@ -5626,14 +5626,14 @@ select * from
   (select 1 as id) as xx
   left join
     (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
-  on (xx.id = coalesce(yy.id));
-              QUERY PLAN               
----------------------------------------
+  on (xx.id = coalesce(yy.id, yy.id));
+                QUERY PLAN                
+------------------------------------------
  Nested Loop Left Join
    ->  Result
    ->  Hash Full Join
          Hash Cond: (a1.unique1 = (1))
-         Filter: (1 = COALESCE((1)))
+         Filter: (1 = COALESCE((1), (1)))
          ->  Seq Scan on tenk1 a1
          ->  Hash
                ->  Result
@@ -5643,7 +5643,7 @@ select * from
   (select 1 as id) as xx
   left join
     (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
-  on (xx.id = coalesce(yy.id));
+  on (xx.id = coalesce(yy.id, yy.id));
  id | unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 | id 
 ----+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------+----
   1 |       1 |    2838 |   1 |    1 |   1 |      1 |       1 |        1 |           1 |         1 |        1 |   2 |    3 | BAAAAA   | EFEAAA   | OOOOxx  |  1
@@ -8411,20 +8411,20 @@ select * from int4_tbl i left join
 
 explain (verbose, costs off)
 select * from int4_tbl i left join
-  lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
-             QUERY PLAN              
--------------------------------------
+  lateral (select coalesce(i, i) from int2_tbl j where i.f1 = j.f1) k on true;
+                QUERY PLAN                
+------------------------------------------
  Nested Loop Left Join
-   Output: i.f1, (COALESCE(i.*))
+   Output: i.f1, (COALESCE(i.*, i.*))
    ->  Seq Scan on public.int4_tbl i
          Output: i.f1, i.*
    ->  Seq Scan on public.int2_tbl j
-         Output: j.f1, COALESCE(i.*)
+         Output: j.f1, COALESCE(i.*, i.*)
          Filter: (i.f1 = j.f1)
 (7 rows)
 
 select * from int4_tbl i left join
-  lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+  lateral (select coalesce(i, i) from int2_tbl j where i.f1 = j.f1) k on true;
      f1      | coalesce 
 -------------+----------
            0 | (0)
@@ -9593,14 +9593,14 @@ CREATE STATISTICS group_tbl_stat (ndistinct) ON a, b FROM group_tbl;
 ANALYZE group_tbl;
 EXPLAIN (COSTS OFF)
 SELECT 1 FROM group_tbl t1
-    LEFT JOIN (SELECT a c1, COALESCE(a) c2 FROM group_tbl t2) s ON TRUE
+    LEFT JOIN (SELECT a c1, COALESCE(a, a) c2 FROM group_tbl t2) s ON TRUE
 GROUP BY s.c1, s.c2;
-                 QUERY PLAN                 
---------------------------------------------
+                   QUERY PLAN                   
+------------------------------------------------
  Group
-   Group Key: t2.a, (COALESCE(t2.a))
+   Group Key: t2.a, (COALESCE(t2.a, t2.a))
    ->  Sort
-         Sort Key: t2.a, (COALESCE(t2.a))
+         Sort Key: t2.a, (COALESCE(t2.a, t2.a))
          ->  Nested Loop Left Join
                ->  Seq Scan on group_tbl t1
                ->  Seq Scan on group_tbl t2
index 40d8056fcea40a5650abf66d1fc7f28b34642c66..18fed63e7381a8e641a171bbf583c02be36243f9 100644 (file)
@@ -2127,30 +2127,30 @@ explain (verbose, costs off)
 select ss2.* from
   int8_tbl t1 left join
   (int8_tbl t2 left join
-   (select coalesce(q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 inner join
+   (select coalesce(q1, q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 inner join
    lateral (select ss1.x as y, * from int8_tbl t4) ss2 on t2.q2 = ss2.q1)
   on t1.q2 = ss2.q1
 order by 1, 2, 3;
-                           QUERY PLAN                           
-----------------------------------------------------------------
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
  Sort
-   Output: (COALESCE(t3.q1)), t4.q1, t4.q2
-   Sort Key: (COALESCE(t3.q1)), t4.q1, t4.q2
+   Output: (COALESCE(t3.q1, t3.q1)), t4.q1, t4.q2
+   Sort Key: (COALESCE(t3.q1, t3.q1)), t4.q1, t4.q2
    ->  Hash Right Join
-         Output: (COALESCE(t3.q1)), t4.q1, t4.q2
+         Output: (COALESCE(t3.q1, t3.q1)), t4.q1, t4.q2
          Hash Cond: (t4.q1 = t1.q2)
          ->  Hash Join
-               Output: (COALESCE(t3.q1)), t4.q1, t4.q2
+               Output: (COALESCE(t3.q1, t3.q1)), t4.q1, t4.q2
                Hash Cond: (t2.q2 = t4.q1)
                ->  Hash Left Join
-                     Output: t2.q2, (COALESCE(t3.q1))
+                     Output: t2.q2, (COALESCE(t3.q1, t3.q1))
                      Hash Cond: (t2.q1 = t3.q2)
                      ->  Seq Scan on public.int8_tbl t2
                            Output: t2.q1, t2.q2
                      ->  Hash
-                           Output: t3.q2, (COALESCE(t3.q1))
+                           Output: t3.q2, (COALESCE(t3.q1, t3.q1))
                            ->  Seq Scan on public.int8_tbl t3
-                                 Output: t3.q2, COALESCE(t3.q1)
+                                 Output: t3.q2, COALESCE(t3.q1, t3.q1)
                ->  Hash
                      Output: t4.q1, t4.q2
                      ->  Seq Scan on public.int8_tbl t4
@@ -2164,7 +2164,7 @@ order by 1, 2, 3;
 select ss2.* from
   int8_tbl t1 left join
   (int8_tbl t2 left join
-   (select coalesce(q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 inner join
+   (select coalesce(q1, q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 inner join
    lateral (select ss1.x as y, * from int8_tbl t4) ss2 on t2.q2 = ss2.q1)
   on t1.q2 = ss2.q1
 order by 1, 2, 3;
@@ -2201,32 +2201,32 @@ explain (verbose, costs off)
 select ss2.* from
   int8_tbl t1 left join
   (int8_tbl t2 left join
-   (select coalesce(q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 left join
+   (select coalesce(q1, q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 left join
    lateral (select ss1.x as y, * from int8_tbl t4) ss2 on t2.q2 = ss2.q1)
   on t1.q2 = ss2.q1
 order by 1, 2, 3;
-                           QUERY PLAN                           
-----------------------------------------------------------------
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
  Sort
-   Output: ((COALESCE(t3.q1))), t4.q1, t4.q2
-   Sort Key: ((COALESCE(t3.q1))), t4.q1, t4.q2
+   Output: ((COALESCE(t3.q1, t3.q1))), t4.q1, t4.q2
+   Sort Key: ((COALESCE(t3.q1, t3.q1))), t4.q1, t4.q2
    ->  Hash Right Join
-         Output: ((COALESCE(t3.q1))), t4.q1, t4.q2
+         Output: ((COALESCE(t3.q1, t3.q1))), t4.q1, t4.q2
          Hash Cond: (t4.q1 = t1.q2)
          ->  Nested Loop
-               Output: t4.q1, t4.q2, ((COALESCE(t3.q1)))
+               Output: t4.q1, t4.q2, ((COALESCE(t3.q1, t3.q1)))
                Join Filter: (t2.q2 = t4.q1)
                ->  Hash Left Join
-                     Output: t2.q2, (COALESCE(t3.q1))
+                     Output: t2.q2, (COALESCE(t3.q1, t3.q1))
                      Hash Cond: (t2.q1 = t3.q2)
                      ->  Seq Scan on public.int8_tbl t2
                            Output: t2.q1, t2.q2
                      ->  Hash
-                           Output: t3.q2, (COALESCE(t3.q1))
+                           Output: t3.q2, (COALESCE(t3.q1, t3.q1))
                            ->  Seq Scan on public.int8_tbl t3
-                                 Output: t3.q2, COALESCE(t3.q1)
+                                 Output: t3.q2, COALESCE(t3.q1, t3.q1)
                ->  Seq Scan on public.int8_tbl t4
-                     Output: t4.q1, t4.q2, (COALESCE(t3.q1))
+                     Output: t4.q1, t4.q2, (COALESCE(t3.q1, t3.q1))
          ->  Hash
                Output: t1.q2
                ->  Seq Scan on public.int8_tbl t1
@@ -2236,7 +2236,7 @@ order by 1, 2, 3;
 select ss2.* from
   int8_tbl t1 left join
   (int8_tbl t2 left join
-   (select coalesce(q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 left join
+   (select coalesce(q1, q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 left join
    lateral (select ss1.x as y, * from int8_tbl t4) ss2 on t2.q2 = ss2.q1)
   on t1.q2 = ss2.q1
 order by 1, 2, 3;
index f6e7070db656b09e2be4dfc0d95c5d1cdb484ee0..5f0a475894ddc4bb8a1a078d6d97b71feff75ab1 100644 (file)
@@ -1977,13 +1977,13 @@ select * from
   (select 1 as id) as xx
   left join
     (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
-  on (xx.id = coalesce(yy.id));
+  on (xx.id = coalesce(yy.id, yy.id));
 
 select * from
   (select 1 as id) as xx
   left join
     (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
-  on (xx.id = coalesce(yy.id));
+  on (xx.id = coalesce(yy.id, yy.id));
 
 --
 -- test ability to push constants through outer join clauses
@@ -3169,9 +3169,9 @@ select * from int4_tbl i left join
   lateral (select * from int2_tbl j where i.f1 = j.f1) k on true;
 explain (verbose, costs off)
 select * from int4_tbl i left join
-  lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+  lateral (select coalesce(i, i) from int2_tbl j where i.f1 = j.f1) k on true;
 select * from int4_tbl i left join
-  lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true;
+  lateral (select coalesce(i, i) from int2_tbl j where i.f1 = j.f1) k on true;
 explain (verbose, costs off)
 select * from int4_tbl a,
   lateral (
@@ -3637,7 +3637,7 @@ ANALYZE group_tbl;
 
 EXPLAIN (COSTS OFF)
 SELECT 1 FROM group_tbl t1
-    LEFT JOIN (SELECT a c1, COALESCE(a) c2 FROM group_tbl t2) s ON TRUE
+    LEFT JOIN (SELECT a c1, COALESCE(a, a) c2 FROM group_tbl t2) s ON TRUE
 GROUP BY s.c1, s.c2;
 
 DROP TABLE group_tbl;
index fec38ef85a6a68119416b92ffe11a60b5cff023e..d9a841fbc9ffdd4fba98be6eb4dc1d945c8e07c9 100644 (file)
@@ -1041,7 +1041,7 @@ explain (verbose, costs off)
 select ss2.* from
   int8_tbl t1 left join
   (int8_tbl t2 left join
-   (select coalesce(q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 inner join
+   (select coalesce(q1, q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 inner join
    lateral (select ss1.x as y, * from int8_tbl t4) ss2 on t2.q2 = ss2.q1)
   on t1.q2 = ss2.q1
 order by 1, 2, 3;
@@ -1049,7 +1049,7 @@ order by 1, 2, 3;
 select ss2.* from
   int8_tbl t1 left join
   (int8_tbl t2 left join
-   (select coalesce(q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 inner join
+   (select coalesce(q1, q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 inner join
    lateral (select ss1.x as y, * from int8_tbl t4) ss2 on t2.q2 = ss2.q1)
   on t1.q2 = ss2.q1
 order by 1, 2, 3;
@@ -1059,7 +1059,7 @@ explain (verbose, costs off)
 select ss2.* from
   int8_tbl t1 left join
   (int8_tbl t2 left join
-   (select coalesce(q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 left join
+   (select coalesce(q1, q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 left join
    lateral (select ss1.x as y, * from int8_tbl t4) ss2 on t2.q2 = ss2.q1)
   on t1.q2 = ss2.q1
 order by 1, 2, 3;
@@ -1067,7 +1067,7 @@ order by 1, 2, 3;
 select ss2.* from
   int8_tbl t1 left join
   (int8_tbl t2 left join
-   (select coalesce(q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 left join
+   (select coalesce(q1, q1) as x, * from int8_tbl t3) ss1 on t2.q1 = ss1.q2 left join
    lateral (select ss1.x as y, * from int8_tbl t4) ss2 on t2.q2 = ss2.q1)
   on t1.q2 = ss2.q1
 order by 1, 2, 3;