diff --git a/pkg/sql/opt/norm/rules/join.opt b/pkg/sql/opt/norm/rules/join.opt index 0e654701e839..6211cb4c95a0 100644 --- a/pkg/sql/opt/norm/rules/join.opt +++ b/pkg/sql/opt/norm/rules/join.opt @@ -738,3 +738,28 @@ $left (ExtractUnboundConditions $outsideOn $cols) (EmptyJoinPrivate) ) + +# RemoveJoinNotNullCondition removes a filter with an IS NOT NULL condition when +# the given column has a NOT NULL constraint. Only left joins and full joins are +# matched because filters can be pushed down from the ON conditions of inner and +# semi joins. +[RemoveJoinNotNullCondition, Normalize] +(LeftJoin | FullJoin + $left:* + $right:* + $on:[ + ... + $item:(FiltersItem + (IsNot + (Variable + $col:* & (IsColNotNull2 $col $left $right) + ) + (Null) + ) + ) + ... + ] + $private:* +) +=> +((OpName) $left $right (RemoveFiltersItem $on $item) $private) diff --git a/pkg/sql/opt/norm/testdata/rules/join b/pkg/sql/opt/norm/testdata/rules/join index 4493d1570ad1..f4c486356699 100644 --- a/pkg/sql/opt/norm/testdata/rules/join +++ b/pkg/sql/opt/norm/testdata/rules/join @@ -2156,7 +2156,7 @@ full-join (cross) # Can't simplify: one equality condition has columns from same side of join. norm expect-not=(SimplifyRightJoin,SimplifyLeftJoin) -SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.k AND a.f=a.f AND a2.f=a2.f +SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.k AND a.i=a.f AND a2.i=a2.f ---- full-join (hash) ├── columns: k:1 i:2 f:3 s:4 j:5 k:7 i:8 f:9 s:10 j:11 @@ -2173,8 +2173,8 @@ full-join (hash) │ └── fd: (7)-->(8-11) └── filters ├── a.k:1 = a2.k:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)] - ├── a.f:3 IS DISTINCT FROM CAST(NULL AS FLOAT8) [outer=(3), constraints=(/3: (/NULL - ]; tight)] - └── a2.f:9 IS DISTINCT FROM CAST(NULL AS FLOAT8) [outer=(9), constraints=(/9: (/NULL - ]; tight)] + ├── a.i:2 = a.f:3 [outer=(2,3), constraints=(/2: (/NULL - ]; /3: (/NULL - ]), fd=(2)==(3), (3)==(2)] + └── a2.i:8 = a2.f:9 [outer=(8,9), constraints=(/8: (/NULL - ]; /9: (/NULL - ]), fd=(8)==(9), (9)==(8)] # Can't simplify: equality conditions have columns from different tables. norm expect-not=(SimplifyRightJoin,SimplifyLeftJoin) @@ -3369,3 +3369,87 @@ inner-join (cross) │ └── k = x ├── scan uv └── filters (true) + +# -------------------------------------------------- +# RemoveJoinNotNullCondition +# -------------------------------------------------- + +# Left join case. +norm expect=RemoveJoinNotNullCondition +SELECT * FROM a LEFT JOIN a AS a2 ON a.k=a2.k AND a.f=a.f AND a2.f=a2.f +---- +inner-join (hash) + ├── columns: k:1!null i:2 f:3!null s:4 j:5 k:7!null i:8 f:9!null s:10 j:11 + ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) + ├── key: (7) + ├── fd: (1)-->(2-5), (7)-->(8-11), (1)==(7), (7)==(1) + ├── scan a + │ ├── columns: a.k:1!null a.i:2 a.f:3!null a.s:4 a.j:5 + │ ├── key: (1) + │ └── fd: (1)-->(2-5) + ├── scan a2 + │ ├── columns: a2.k:7!null a2.i:8 a2.f:9!null a2.s:10 a2.j:11 + │ ├── key: (7) + │ └── fd: (7)-->(8-11) + └── filters + └── a.k:1 = a2.k:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)] + +# Full join case. +norm expect=RemoveJoinNotNullCondition +SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.k AND a.f=a.f AND a2.f=a2.f +---- +inner-join (hash) + ├── columns: k:1!null i:2 f:3!null s:4 j:5 k:7!null i:8 f:9!null s:10 j:11 + ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) + ├── key: (7) + ├── fd: (7)-->(8-11), (1)-->(2-5), (1)==(7), (7)==(1) + ├── scan a2 + │ ├── columns: a2.k:7!null a2.i:8 a2.f:9!null a2.s:10 a2.j:11 + │ ├── key: (7) + │ └── fd: (7)-->(8-11) + ├── scan a + │ ├── columns: a.k:1!null a.i:2 a.f:3!null a.s:4 a.j:5 + │ ├── key: (1) + │ └── fd: (1)-->(2-5) + └── filters + └── a.k:1 = a2.k:7 [outer=(1,7), constraints=(/1: (/NULL - ]; /7: (/NULL - ]), fd=(1)==(7), (7)==(1)] + +# No-op case because i is nullable. +norm expect-not=RemoveJoinNotNullCondition +SELECT i FROM a +FULL JOIN xy ON k < y AND i IS NOT NULL +---- +project + ├── columns: i:2 + └── full-join (cross) + ├── columns: k:1 i:2 y:8 + ├── fd: (1)-->(2) + ├── scan a + │ ├── columns: k:1!null i:2 + │ ├── key: (1) + │ └── fd: (1)-->(2) + ├── scan xy + │ └── columns: y:8 + └── filters + ├── k:1 < y:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ])] + └── i:2 IS NOT NULL [outer=(2), constraints=(/2: (/NULL - ]; tight)] + +# No-op case because i+k can be NULL. +norm expect-not=RemoveJoinNotNullCondition +SELECT k FROM a +FULL JOIN xy ON i+k IS NOT NULL +---- +project + ├── columns: k:1 + ├── immutable + └── full-join (cross) + ├── columns: k:1 i:2 + ├── immutable + ├── fd: (1)-->(2) + ├── scan a + │ ├── columns: k:1!null i:2 + │ ├── key: (1) + │ └── fd: (1)-->(2) + ├── scan xy + └── filters + └── (i:2 + k:1) IS NOT NULL [outer=(1,2), immutable] diff --git a/pkg/sql/opt/norm/testdata/rules/scalar b/pkg/sql/opt/norm/testdata/rules/scalar index 3395fc94367c..0ac0d1ab34f8 100644 --- a/pkg/sql/opt/norm/testdata/rules/scalar +++ b/pkg/sql/opt/norm/testdata/rules/scalar @@ -1842,8 +1842,7 @@ full-join (cross) │ ├── columns: k:1!null │ └── key: (1) ├── scan xy - └── filters - └── k:1 IS DISTINCT FROM CAST(NULL AS INT8) [outer=(1), constraints=(/1: (/NULL - ]; tight)] + └── filters (true) norm expect=(SimplifySameVarEqualities,SimplifyJoinFilters) SELECT a.k FROM a FULL OUTER JOIN xy ON a.k >= a.k @@ -1854,8 +1853,7 @@ full-join (cross) │ ├── columns: k:1!null │ └── key: (1) ├── scan xy - └── filters - └── k:1 IS DISTINCT FROM CAST(NULL AS INT8) [outer=(1), constraints=(/1: (/NULL - ]; tight)] + └── filters (true) norm expect=(SimplifySameVarEqualities,SimplifyJoinFilters) SELECT a.k FROM a FULL OUTER JOIN xy ON a.k <= a.k @@ -1866,8 +1864,7 @@ full-join (cross) │ ├── columns: k:1!null │ └── key: (1) ├── scan xy - └── filters - └── k:1 IS DISTINCT FROM CAST(NULL AS INT8) [outer=(1), constraints=(/1: (/NULL - ]; tight)] + └── filters (true) norm expect=SimplifySameVarEqualities SELECT k = k FROM a