From 67a1d4d7b14baaba74c50dc642dd9022b774e98b Mon Sep 17 00:00:00 2001 From: Rebecca Taft Date: Wed, 29 Apr 2020 10:42:06 -0500 Subject: [PATCH] opt: add rule to eliminate Exists when input has zero rows This commit adds a new rule, EliminateExistsZeroRows, which converts an Exists subquery to False when it's known that the input produces zero rows. Informs #47058 Release note (performance improvement): The optimizer can now detect when an Exists subquery can be eliminated because the input has zero rows. This leads to better plans in some cases. --- pkg/sql/opt/norm/rules/scalar.opt | 5 ++++ pkg/sql/opt/norm/testdata/rules/join | 4 +-- pkg/sql/opt/norm/testdata/rules/scalar | 34 ++++++++++++++------------ 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/pkg/sql/opt/norm/rules/scalar.opt b/pkg/sql/opt/norm/rules/scalar.opt index ad5e38003f0e..1d9f2edef4f3 100644 --- a/pkg/sql/opt/norm/rules/scalar.opt +++ b/pkg/sql/opt/norm/rules/scalar.opt @@ -90,6 +90,11 @@ $result ) +# EliminateExistsZeroRows converts an Exists subquery to False when it's known +# that the input produces zero rows. +[EliminateExistsZeroRows, Normalize] +(Exists $input:* & (HasZeroRows $input)) => (False) + # EliminateExistsProject discards a Project input to the Exists operator. The # Project operator never changes the row cardinality of its input, and row # cardinality is the only thing that Exists cares about, so Project is a no-op. diff --git a/pkg/sql/opt/norm/testdata/rules/join b/pkg/sql/opt/norm/testdata/rules/join index 9e1685bbd37e..7749f0201d4f 100644 --- a/pkg/sql/opt/norm/testdata/rules/join +++ b/pkg/sql/opt/norm/testdata/rules/join @@ -2165,7 +2165,7 @@ scan a # SimplifyZeroCardinalitySemiJoin # -------------------------------------------------- # TODO(justin): figure out if there's a good way to make this still apply. -norm disable=SimplifyZeroCardinalityGroup expect=SimplifyZeroCardinalitySemiJoin +norm disable=(SimplifyZeroCardinalityGroup,EliminateExistsZeroRows) expect=SimplifyZeroCardinalitySemiJoin SELECT * FROM a WHERE EXISTS(SELECT * FROM (VALUES (k)) OFFSET 1) ---- values @@ -2178,7 +2178,7 @@ values # EliminateAntiJoin # -------------------------------------------------- # TODO(justin): figure out if there's a good way to make this still apply. -norm disable=SimplifyZeroCardinalityGroup expect=EliminateAntiJoin +norm disable=(SimplifyZeroCardinalityGroup,EliminateExistsZeroRows) expect=EliminateAntiJoin SELECT * FROM a WHERE NOT EXISTS(SELECT * FROM (VALUES (k)) OFFSET 1) ---- scan a diff --git a/pkg/sql/opt/norm/testdata/rules/scalar b/pkg/sql/opt/norm/testdata/rules/scalar index 772d554e7247..7e729f43d138 100644 --- a/pkg/sql/opt/norm/testdata/rules/scalar +++ b/pkg/sql/opt/norm/testdata/rules/scalar @@ -249,6 +249,20 @@ values ├── fd: ()-->(1) └── (true IN (NULL, NULL, ('201.249.149.90/18' & '97a7:3650:3dd8:d4e9:35fe:6cfb:a714:1c17/61') << 'e22f:2067:2ed2:7b07:b167:206f:f17b:5b7d/82'),) +# -------------------------------------------------- +# EliminateExistsZeroRows +# -------------------------------------------------- + +norm expect=EliminateExistsZeroRows +SELECT EXISTS(SELECT * FROM (VALUES (1)) WHERE false) +---- +values + ├── columns: exists:2!null + ├── cardinality: [1 - 1] + ├── key: () + ├── fd: ()-->(2) + └── (false,) + # -------------------------------------------------- # EliminateExistsProject # -------------------------------------------------- @@ -524,21 +538,11 @@ anti-join (hash) norm expect-not=EliminateExistsLimit SELECT * FROM a a1 WHERE EXISTS(SELECT i FROM a a2 where a1.i = a2.i LIMIT 0) ---- -select - ├── columns: k:1!null i:2 f:3 s:4 j:5 arr:6 - ├── key: (1) - ├── fd: (1)-->(2-6) - ├── scan a1 - │ ├── columns: a1.k:1!null a1.i:2 a1.f:3 a1.s:4 a1.j:5 a1.arr:6 - │ ├── key: (1) - │ └── fd: (1)-->(2-6) - └── filters - └── exists [subquery] - └── values - ├── columns: a2.i:8!null - ├── cardinality: [0 - 0] - ├── key: () - └── fd: ()-->(8) +values + ├── columns: k:1!null i:2!null f:3!null s:4!null j:5!null arr:6!null + ├── cardinality: [0 - 0] + ├── key: () + └── fd: ()-->(1-6) # Don't eliminate a limit from a non-correlated subquery. norm expect-not=EliminateExistsLimit