diff --git a/pkg/sql/opt/memo/testdata/stats/lookup-join b/pkg/sql/opt/memo/testdata/stats/lookup-join index cf7eaa3718a6..4d8f20b16e50 100644 --- a/pkg/sql/opt/memo/testdata/stats/lookup-join +++ b/pkg/sql/opt/memo/testdata/stats/lookup-join @@ -406,14 +406,14 @@ inner-join (lookup wxyz) │ ├── stats: [rows=19.8, distinct(1)=19.8, null(1)=0, distinct(6)=19.8, null(6)=0, distinct(7)=1, null(7)=0, distinct(11)=1, null(11)=0] │ ├── fd: ()-->(7), (9)-->(6,8), (1)==(6), (6)==(1) │ ├── project - │ │ ├── columns: "project_const_col_@7":11(int!null) m:1(int) n:2(int) + │ │ ├── columns: "lookup_join_const_col_@7":11(int!null) m:1(int) n:2(int) │ │ ├── stats: [rows=40, distinct(1)=40, null(1)=0, distinct(11)=1, null(11)=0] │ │ ├── fd: ()-->(11) │ │ ├── scan medium │ │ │ ├── columns: m:1(int) n:2(int) │ │ │ └── stats: [rows=40, distinct(1)=40, null(1)=0] │ │ └── projections - │ │ └── 10 [as="project_const_col_@7":11, type=int] + │ │ └── 10 [as="lookup_join_const_col_@7":11, type=int] │ └── filters (true) └── filters (true) diff --git a/pkg/sql/opt/xform/general_funcs.go b/pkg/sql/opt/xform/general_funcs.go index e7dc62c43521..8496785851d6 100644 --- a/pkg/sql/opt/xform/general_funcs.go +++ b/pkg/sql/opt/xform/general_funcs.go @@ -154,3 +154,77 @@ func (c *CustomFuncs) initIdxConstraintForIndex( ) return ic } + +// computedColFilters generates all filters that can be derived from the list of +// computed column expressions from the given table. A computed column can be +// used as a filter when it has a constant value. That is true when: +// +// 1. All other columns it references are constant, because other filters in +// the query constrain them to be so. +// 2. All functions in the computed column expression can be folded into +// constants (i.e. they do not have problematic side effects). +// +// Note that computed columns can depend on other computed columns; in general +// the dependencies form an acyclic directed graph. computedColFilters will +// return filters for all constant computed columns, regardless of the order of +// their dependencies. +// +// As with checkConstraintFilters, computedColFilters do not really filter any +// rows, they are rather facts or guarantees about the data. Treating them as +// filters may allow some indexes to be constrained and used. Consider the +// following example: +// +// CREATE TABLE t ( +// k INT NOT NULL, +// hash INT AS (k % 4) STORED, +// PRIMARY KEY (hash, k) +// ) +// +// SELECT * FROM t WHERE k = 5 +// +// Notice that the filter provided explicitly wouldn't allow the optimizer to +// seek using the primary index (it would have to fall back to a table scan). +// However, column "hash" can be proven to have the constant value of 1, since +// it's dependent on column "k", which has the constant value of 5. This enables +// usage of the primary index: +// +// scan t +// ├── columns: k:1(int!null) hash:2(int!null) +// ├── constraint: /2/1: [/1/5 - /1/5] +// ├── key: (2) +// └── fd: ()-->(1) +// +// The values of both columns in that index are known, enabling a single value +// constraint to be generated. +func (c *CustomFuncs) computedColFilters( + tabID opt.TableID, requiredFilters, optionalFilters memo.FiltersExpr, +) memo.FiltersExpr { + tabMeta := c.e.mem.Metadata().TableMeta(tabID) + if len(tabMeta.ComputedCols) == 0 { + return nil + } + + // Start with set of constant columns, as derived from the list of filter + // conditions. + constCols := make(map[opt.ColumnID]opt.ScalarExpr) + c.findConstantFilterCols(constCols, tabID, requiredFilters) + c.findConstantFilterCols(constCols, tabID, optionalFilters) + if len(constCols) == 0 { + // No constant values could be derived from filters, so assume that there + // are also no constant computed columns. + return nil + } + + // Construct a new filter condition for each computed column that is + // constant (i.e. all of its variables are in the constCols set). + var computedColFilters memo.FiltersExpr + for colID := range tabMeta.ComputedCols { + if c.tryFoldComputedCol(tabMeta, colID, constCols) { + constVal := constCols[colID] + // Note: Eq is not correct here because of NULLs. + eqOp := c.e.f.ConstructIs(c.e.f.ConstructVariable(colID), constVal) + computedColFilters = append(computedColFilters, c.e.f.ConstructFiltersItem(eqOp)) + } + } + return computedColFilters +} diff --git a/pkg/sql/opt/xform/join_funcs.go b/pkg/sql/opt/xform/join_funcs.go index 055ea230f771..89618d607b96 100644 --- a/pkg/sql/opt/xform/join_funcs.go +++ b/pkg/sql/opt/xform/join_funcs.go @@ -162,6 +162,45 @@ func (c *CustomFuncs) GenerateMergeJoins( // "sides" (in this example x,y on the left and z on the right) but there is // no overlap. // +// A lookup join can be created when the ON condition or implicit filters from +// CHECK constraints and computed columns constrain a prefix of the index +// columns to non-ranging constant values. To support this, the constant values +// are cross-joined with the input and used as key columns for the parent lookup +// join. +// +// For example, consider the tables and query below. +// +// CREATE TABLE abc (a INT PRIMARY KEY, b INT, c INT) +// CREATE TABLE xyz ( +// x INT PRIMARY KEY, +// y INT, +// z INT NOT NULL, +// CHECK z IN (1, 2, 3), +// INDEX (z, y) +// ) +// SELECT a, x FROM abc JOIN xyz ON a=y +// +// GenerateLookupJoins will perform the following transformation. +// +// Join LookupJoin(t@idx) +// / \ | +// / \ -> | +// Input Scan(t) Join +// / \ +// / \ +// Input Values(1, 2, 3) +// +// If a column is constrained to a single constant value, inlining normalization +// rules will reduce the cross join into a project. +// +// Join LookupJoin(t@idx) +// / \ | +// / \ -> | +// Input Scan(t) Project +// | +// | +// Input +// func (c *CustomFuncs) GenerateLookupJoins( grp memo.RelExpr, joinType opt.Operator, @@ -182,6 +221,12 @@ func (c *CustomFuncs) GenerateLookupJoins( return } + // Generate implicit filters from CHECK constraints and computed columns as + // optional filters to help generate lookup join keys. + optionalFilters := c.checkConstraintFilters(scanPrivate.Table) + computedColFilters := c.computedColFilters(scanPrivate.Table, on, optionalFilters) + optionalFilters = append(optionalFilters, computedColFilters...) + var pkCols opt.ColList var iter scanIndexIter iter.Init(c.e.mem, &c.im, scanPrivate, on, rejectInvertedIndexes) @@ -191,6 +236,7 @@ func (c *CustomFuncs) GenerateLookupJoins( numIndexKeyCols := index.LaxKeyColumnCount() var constFilters memo.FiltersExpr + allFilters := append(onFilters, optionalFilters...) // Check if the first column in the index has an equality constraint, or if // it is constrained to a constant value. This check doesn't guarantee that @@ -198,7 +244,7 @@ func (c *CustomFuncs) GenerateLookupJoins( // in most cases. firstIdxCol := scanPrivate.Table.IndexColumnID(index, 0) if _, ok := rightEq.Find(firstIdxCol); !ok { - if _, _, ok := c.findJoinFilterConstants(onFilters, firstIdxCol); !ok { + if _, _, ok := c.findJoinFilterConstants(allFilters, firstIdxCol); !ok { return } } @@ -226,7 +272,7 @@ func (c *CustomFuncs) GenerateLookupJoins( // constant values. We cannot use a NULL value because the lookup // join implements logic equivalent to simple equality between // columns (where NULL never equals anything). - foundVals, onIdx, ok := c.findJoinFilterConstants(onFilters, idxCol) + foundVals, allIdx, ok := c.findJoinFilterConstants(allFilters, idxCol) if !ok { break } @@ -261,7 +307,7 @@ func (c *CustomFuncs) GenerateLookupJoins( lookupJoin.KeyCols = append(lookupJoin.KeyCols, constColID) rightSideCols = append(rightSideCols, idxCol) - constFilters = append(constFilters, onFilters[onIdx]) + constFilters = append(constFilters, allFilters[allIdx]) } if len(lookupJoin.KeyCols) == 0 { diff --git a/pkg/sql/opt/xform/select_funcs.go b/pkg/sql/opt/xform/select_funcs.go index 3d9a2c07b3f9..328f0b4b1847 100644 --- a/pkg/sql/opt/xform/select_funcs.go +++ b/pkg/sql/opt/xform/select_funcs.go @@ -372,80 +372,6 @@ func (c *CustomFuncs) GenerateConstrainedScans( }) } -// computedColFilters generates all filters that can be derived from the list of -// computed column expressions from the given table. A computed column can be -// used as a filter when it has a constant value. That is true when: -// -// 1. All other columns it references are constant, because other filters in -// the query constrain them to be so. -// 2. All functions in the computed column expression can be folded into -// constants (i.e. they do not have problematic side effects). -// -// Note that computed columns can depend on other computed columns; in general -// the dependencies form an acyclic directed graph. computedColFilters will -// return filters for all constant computed columns, regardless of the order of -// their dependencies. -// -// As with checkConstraintFilters, computedColFilters do not really filter any -// rows, they are rather facts or guarantees about the data. Treating them as -// filters may allow some indexes to be constrained and used. Consider the -// following example: -// -// CREATE TABLE t ( -// k INT NOT NULL, -// hash INT AS (k % 4) STORED, -// PRIMARY KEY (hash, k) -// ) -// -// SELECT * FROM t WHERE k = 5 -// -// Notice that the filter provided explicitly wouldn't allow the optimizer to -// seek using the primary index (it would have to fall back to a table scan). -// However, column "hash" can be proven to have the constant value of 1, since -// it's dependent on column "k", which has the constant value of 5. This enables -// usage of the primary index: -// -// scan t -// ├── columns: k:1(int!null) hash:2(int!null) -// ├── constraint: /2/1: [/1/5 - /1/5] -// ├── key: (2) -// └── fd: ()-->(1) -// -// The values of both columns in that index are known, enabling a single value -// constraint to be generated. -func (c *CustomFuncs) computedColFilters( - tabID opt.TableID, requiredFilters, optionalFilters memo.FiltersExpr, -) memo.FiltersExpr { - tabMeta := c.e.mem.Metadata().TableMeta(tabID) - if len(tabMeta.ComputedCols) == 0 { - return nil - } - - // Start with set of constant columns, as derived from the list of filter - // conditions. - constCols := make(map[opt.ColumnID]opt.ScalarExpr) - c.findConstantFilterCols(constCols, tabID, requiredFilters) - c.findConstantFilterCols(constCols, tabID, optionalFilters) - if len(constCols) == 0 { - // No constant values could be derived from filters, so assume that there - // are also no constant computed columns. - return nil - } - - // Construct a new filter condition for each computed column that is - // constant (i.e. all of its variables are in the constCols set). - var computedColFilters memo.FiltersExpr - for colID := range tabMeta.ComputedCols { - if c.tryFoldComputedCol(tabMeta, colID, constCols) { - constVal := constCols[colID] - // Note: Eq is not correct here because of NULLs. - eqOp := c.e.f.ConstructIs(c.e.f.ConstructVariable(colID), constVal) - computedColFilters = append(computedColFilters, c.e.f.ConstructFiltersItem(eqOp)) - } - } - return computedColFilters -} - // findConstantFilterCols adds to constFilterCols mappings from table column ID // to the constant value of that column. It does this by iterating over the // given lists of filters and finding expressions that constrain columns to a diff --git a/pkg/sql/opt/xform/testdata/external/tpce b/pkg/sql/opt/xform/testdata/external/tpce index f59b07dc2777..1c7254f4ab7f 100644 --- a/pkg/sql/opt/xform/testdata/external/tpce +++ b/pkg/sql/opt/xform/testdata/external/tpce @@ -1550,7 +1550,7 @@ project │ │ ├── key: (1) │ │ ├── fd: (1)-->(3) │ │ ├── project - │ │ │ ├── columns: "project_const_col_@6":10!null tx_id:1!null tx_rate:3!null + │ │ │ ├── columns: "lookup_join_const_col_@6":10!null tx_id:1!null tx_rate:3!null │ │ │ ├── key: (1) │ │ │ ├── fd: ()-->(10), (1)-->(3) │ │ │ ├── scan taxrate @@ -1560,7 +1560,7 @@ project │ │ │ │ ├── key: (1) │ │ │ │ └── fd: (1)-->(3) │ │ │ └── projections - │ │ │ └── 0 [as="project_const_col_@6":10] + │ │ │ └── 0 [as="lookup_join_const_col_@6":10] │ │ └── filters (true) │ └── aggregations │ └── sum [as=sum:8, outer=(3)] @@ -2526,7 +2526,7 @@ project │ │ ├── key: (1) │ │ ├── fd: (1)-->(3) │ │ ├── project - │ │ │ ├── columns: "project_const_col_@6":10!null tx_id:1!null tx_rate:3!null + │ │ │ ├── columns: "lookup_join_const_col_@6":10!null tx_id:1!null tx_rate:3!null │ │ │ ├── key: (1) │ │ │ ├── fd: ()-->(10), (1)-->(3) │ │ │ ├── scan taxrate @@ -2536,7 +2536,7 @@ project │ │ │ │ ├── key: (1) │ │ │ │ └── fd: (1)-->(3) │ │ │ └── projections - │ │ │ └── 0 [as="project_const_col_@6":10] + │ │ │ └── 0 [as="lookup_join_const_col_@6":10] │ │ └── filters (true) │ └── aggregations │ └── sum [as=sum:8, outer=(3)] @@ -4156,7 +4156,7 @@ update watch_item │ │ ├── key: (7) │ │ ├── fd: ()-->(5,8), (7)-->(9), (4)==(7), (7)==(4) │ │ ├── project - │ │ │ ├── columns: "project_const_col_@5":29!null wl_id:7!null wl_c_id:8!null watch_list.crdb_internal_mvcc_timestamp:9 + │ │ │ ├── columns: "lookup_join_const_col_@5":29!null wl_id:7!null wl_c_id:8!null watch_list.crdb_internal_mvcc_timestamp:9 │ │ │ ├── key: (7) │ │ │ ├── fd: ()-->(8,29), (7)-->(9) │ │ │ ├── select @@ -4170,7 +4170,7 @@ update watch_item │ │ │ │ └── filters │ │ │ │ └── wl_c_id:8 = 0 [outer=(8), constraints=(/8: [/0 - /0]; tight), fd=()-->(8)] │ │ │ └── projections - │ │ │ └── 'SYMB' [as="project_const_col_@5":29] + │ │ │ └── 'SYMB' [as="lookup_join_const_col_@5":29] │ │ └── filters (true) │ └── projections │ └── 'SYMB' [as=wi_s_symb_new:10] diff --git a/pkg/sql/opt/xform/testdata/external/tpce-no-stats b/pkg/sql/opt/xform/testdata/external/tpce-no-stats index 9c604a8e0b8d..ec6d64ebde33 100644 --- a/pkg/sql/opt/xform/testdata/external/tpce-no-stats +++ b/pkg/sql/opt/xform/testdata/external/tpce-no-stats @@ -1269,7 +1269,7 @@ limit │ ├── fd: ()-->(7), (1,2)-->(3,4), (1)==(6), (6)==(1) │ ├── limit hint: 20.00 │ ├── project - │ │ ├── columns: "project_const_col_@7":12!null hh_h_t_id:1!null hh_t_id:2!null hh_before_qty:3!null hh_after_qty:4!null + │ │ ├── columns: "lookup_join_const_col_@7":12!null hh_h_t_id:1!null hh_t_id:2!null hh_before_qty:3!null hh_after_qty:4!null │ │ ├── key: (1,2) │ │ ├── fd: ()-->(12), (1,2)-->(3,4) │ │ ├── limit hint: 200.00 @@ -1279,7 +1279,7 @@ limit │ │ │ ├── fd: (1,2)-->(3,4) │ │ │ └── limit hint: 200.00 │ │ └── projections - │ │ └── 0 [as="project_const_col_@7":12] + │ │ └── 0 [as="lookup_join_const_col_@7":12] │ └── filters (true) └── 20 diff --git a/pkg/sql/opt/xform/testdata/external/trading b/pkg/sql/opt/xform/testdata/external/trading index 7032edb4867e..1a7770a32315 100644 --- a/pkg/sql/opt/xform/testdata/external/trading +++ b/pkg/sql/opt/xform/testdata/external/trading @@ -710,7 +710,7 @@ project │ ├── ordering: -(3|12) opt(1,2,4,10,11) [actual: -3] │ ├── limit hint: 20.00 │ ├── project - │ │ ├── columns: "project_const_col_@10":18!null "project_const_col_@11":19!null transactiondetails.dealerid:1!null transactiondetails.isbuy:2!null transactiondate:3!null cardid:4!null quantity:5!null sellprice:6!null buyprice:7!null + │ │ ├── columns: "lookup_join_const_col_@11":19!null "lookup_join_const_col_@10":18!null transactiondetails.dealerid:1!null transactiondetails.isbuy:2!null transactiondate:3!null cardid:4!null quantity:5!null sellprice:6!null buyprice:7!null │ │ ├── stats: [rows=478.646617] │ │ ├── key: (3,5) │ │ ├── fd: ()-->(1,2,4,18,19), (3,5)-->(6,7) @@ -732,8 +732,8 @@ project │ │ │ ├── ordering: -3 opt(1,2,4) [actual: -3] │ │ │ └── limit hint: 100.00 │ │ └── projections - │ │ ├── 1 [as="project_const_col_@10":18] - │ │ └── false [as="project_const_col_@11":19] + │ │ ├── false [as="lookup_join_const_col_@11":19] + │ │ └── 1 [as="lookup_join_const_col_@10":18] │ └── filters (true) └── 20 @@ -843,7 +843,7 @@ project │ │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6), (1,20-22)-->(18,19) │ │ │ │ ├── ordering: +1 │ │ │ │ ├── project - │ │ │ │ │ ├── columns: "project_const_col_@18":39!null "project_const_col_@19":40!null id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null + │ │ │ │ │ ├── columns: "lookup_join_const_col_@19":40!null "lookup_join_const_col_@18":39!null id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null │ │ │ │ │ ├── immutable │ │ │ │ │ ├── stats: [rows=19000, distinct(1)=19000, null(1)=0, distinct(2)=13000, null(2)=0, distinct(5)=829, null(5)=0, distinct(6)=5601.15328, null(6)=0, distinct(39)=1, null(39)=0, distinct(40)=1, null(40)=0] │ │ │ │ │ ├── key: (1) @@ -865,8 +865,8 @@ project │ │ │ │ │ │ └── filters │ │ │ │ │ │ └── (name:2, setname:4, number:5) > ('Shock', '7E', 248) [outer=(2,4,5), immutable, constraints=(/2/4/5: [/'Shock'/'7E'/249 - ]; tight)] │ │ │ │ │ └── projections - │ │ │ │ │ ├── 1 [as="project_const_col_@18":39] - │ │ │ │ │ └── false [as="project_const_col_@19":40] + │ │ │ │ │ ├── false [as="lookup_join_const_col_@19":40] + │ │ │ │ │ └── 1 [as="lookup_join_const_col_@18":39] │ │ │ │ └── filters │ │ │ │ └── (transactiondate:20 >= '2020-02-28 00:00:00+00:00') AND (transactiondate:20 <= '2020-03-01 00:00:00+00:00') [outer=(20), constraints=(/20: [/'2020-02-28 00:00:00+00:00' - /'2020-03-01 00:00:00+00:00']; tight)] │ │ │ ├── scan cardsinfo @@ -1110,27 +1110,41 @@ limit │ ├── fd: (10)-->(15) │ ├── project │ │ ├── columns: quantity:14!null accountname:10!null - │ │ ├── inner-join (hash) + │ │ ├── inner-join (lookup inventorydetails) │ │ │ ├── columns: id:6!null quantity:7!null dealerid:8!null cardid:9!null accountname:10!null inventorydetails.quantity:11!null + │ │ │ ├── key columns: [16 6 17] = [8 9 10] + │ │ │ ├── lookup columns are key │ │ │ ├── fd: (8-10)-->(11), (6)==(9), (9)==(6) - │ │ │ ├── select - │ │ │ │ ├── columns: dealerid:8!null cardid:9!null accountname:10!null inventorydetails.quantity:11!null - │ │ │ │ ├── key: (8-10) - │ │ │ │ ├── fd: (8-10)-->(11) - │ │ │ │ ├── scan inventorydetails - │ │ │ │ │ ├── columns: dealerid:8!null cardid:9!null accountname:10!null inventorydetails.quantity:11!null - │ │ │ │ │ ├── constraint: /8/9/10: [/1 - /5] - │ │ │ │ │ ├── key: (8-10) - │ │ │ │ │ └── fd: (8-10)-->(11) - │ │ │ │ └── filters - │ │ │ │ └── accountname:10 IN ('account-1', 'account-2', 'account-3') [outer=(10), constraints=(/10: [/'account-1' - /'account-1'] [/'account-2' - /'account-2'] [/'account-3' - /'account-3']; tight)] - │ │ │ ├── values - │ │ │ │ ├── columns: id:6!null quantity:7!null - │ │ │ │ ├── cardinality: [2 - 2] - │ │ │ │ ├── (42948, 3) - │ │ │ │ └── (24924, 4) - │ │ │ └── filters - │ │ │ └── cardid:9 = id:6 [outer=(6,9), constraints=(/6: (/NULL - ]; /9: (/NULL - ]), fd=(6)==(9), (9)==(6)] + │ │ │ ├── inner-join (cross) + │ │ │ │ ├── columns: id:6!null quantity:7!null "lookup_join_const_col_@8":16!null "lookup_join_const_col_@10":17!null + │ │ │ │ ├── cardinality: [30 - 30] + │ │ │ │ ├── multiplicity: left-rows(one-or-more), right-rows(one-or-more) + │ │ │ │ ├── inner-join (cross) + │ │ │ │ │ ├── columns: id:6!null quantity:7!null "lookup_join_const_col_@8":16!null + │ │ │ │ │ ├── cardinality: [10 - 10] + │ │ │ │ │ ├── multiplicity: left-rows(one-or-more), right-rows(one-or-more) + │ │ │ │ │ ├── values + │ │ │ │ │ │ ├── columns: "lookup_join_const_col_@8":16!null + │ │ │ │ │ │ ├── cardinality: [5 - 5] + │ │ │ │ │ │ ├── (1,) + │ │ │ │ │ │ ├── (2,) + │ │ │ │ │ │ ├── (3,) + │ │ │ │ │ │ ├── (4,) + │ │ │ │ │ │ └── (5,) + │ │ │ │ │ ├── values + │ │ │ │ │ │ ├── columns: id:6!null quantity:7!null + │ │ │ │ │ │ ├── cardinality: [2 - 2] + │ │ │ │ │ │ ├── (42948, 3) + │ │ │ │ │ │ └── (24924, 4) + │ │ │ │ │ └── filters (true) + │ │ │ │ ├── values + │ │ │ │ │ ├── columns: "lookup_join_const_col_@10":17!null + │ │ │ │ │ ├── cardinality: [3 - 3] + │ │ │ │ │ ├── ('account-1',) + │ │ │ │ │ ├── ('account-2',) + │ │ │ │ │ └── ('account-3',) + │ │ │ │ └── filters (true) + │ │ │ └── filters (true) │ │ └── projections │ │ └── CASE WHEN quantity:7 < inventorydetails.quantity:11 THEN quantity:7 ELSE inventorydetails.quantity:11 END [as=quantity:14, outer=(7,11)] │ └── aggregations @@ -1475,7 +1489,7 @@ update cardsinfo [as=ci] │ │ ├── key columns: [39 17] = [28 29] │ │ ├── fd: ()-->(16), (17)-->(18-24,26,27), (24)-->(17-23), (17)==(26), (26)==(17) │ │ ├── project - │ │ │ ├── columns: "project_const_col_@28":39!null ci.dealerid:16!null ci.cardid:17!null buyprice:18!null sellprice:19!null discount:20!null desiredinventory:21!null actualinventory:22!null maxinventory:23!null ci.version:24!null c:26!null q:27!null + │ │ │ ├── columns: "lookup_join_const_col_@28":39!null ci.dealerid:16!null ci.cardid:17!null buyprice:18!null sellprice:19!null discount:20!null desiredinventory:21!null actualinventory:22!null maxinventory:23!null ci.version:24!null c:26!null q:27!null │ │ │ ├── cardinality: [0 - 2] │ │ │ ├── key: (17) │ │ │ ├── fd: ()-->(16,39), (17)-->(18-24,26,27), (24)-->(17-23), (17)==(26), (26)==(17) @@ -1492,7 +1506,7 @@ update cardsinfo [as=ci] │ │ │ │ │ ├── cardinality: [0 - 2] │ │ │ │ │ ├── fd: ()-->(16), (17)-->(18-24), (24)-->(17-23), (17)==(26), (26)==(17) │ │ │ │ │ ├── project - │ │ │ │ │ │ ├── columns: "project_const_col_@16":37!null c:26!null q:27!null + │ │ │ │ │ │ ├── columns: "lookup_join_const_col_@16":37!null c:26!null q:27!null │ │ │ │ │ │ ├── cardinality: [2 - 2] │ │ │ │ │ │ ├── fd: ()-->(37) │ │ │ │ │ │ ├── values @@ -1501,7 +1515,7 @@ update cardsinfo [as=ci] │ │ │ │ │ │ │ ├── (42948, 3) │ │ │ │ │ │ │ └── (24924, 4) │ │ │ │ │ │ └── projections - │ │ │ │ │ │ └── 1 [as="project_const_col_@16":37] + │ │ │ │ │ │ └── 1 [as="lookup_join_const_col_@16":37] │ │ │ │ │ └── filters (true) │ │ │ │ └── aggregations │ │ │ │ ├── first-agg [as=buyprice:18, outer=(18)] @@ -1525,7 +1539,7 @@ update cardsinfo [as=ci] │ │ │ │ └── const-agg [as=ci.dealerid:16, outer=(16)] │ │ │ │ └── ci.dealerid:16 │ │ │ └── projections - │ │ │ └── 1 [as="project_const_col_@28":39] + │ │ │ └── 1 [as="lookup_join_const_col_@28":39] │ │ └── filters (true) │ └── aggregations │ ├── sum-int [as=sum_int:34, outer=(31)] diff --git a/pkg/sql/opt/xform/testdata/external/trading-mutation b/pkg/sql/opt/xform/testdata/external/trading-mutation index 34a77d2f4a75..7641f09129d8 100644 --- a/pkg/sql/opt/xform/testdata/external/trading-mutation +++ b/pkg/sql/opt/xform/testdata/external/trading-mutation @@ -714,7 +714,7 @@ project │ ├── ordering: -(3|14) opt(1,2,4,12,13) [actual: -3] │ ├── limit hint: 20.00 │ ├── project - │ │ ├── columns: "project_const_col_@12":22!null "project_const_col_@13":23!null transactiondetails.dealerid:1!null transactiondetails.isbuy:2!null transactiondate:3!null cardid:4!null quantity:5!null sellprice:6!null buyprice:7!null + │ │ ├── columns: "lookup_join_const_col_@13":23!null "lookup_join_const_col_@12":22!null transactiondetails.dealerid:1!null transactiondetails.isbuy:2!null transactiondate:3!null cardid:4!null quantity:5!null sellprice:6!null buyprice:7!null │ │ ├── stats: [rows=478.646617] │ │ ├── key: (3,5) │ │ ├── fd: ()-->(1,2,4,22,23), (3,5)-->(6,7) @@ -736,8 +736,8 @@ project │ │ │ ├── ordering: -3 opt(1,2,4) [actual: -3] │ │ │ └── limit hint: 100.00 │ │ └── projections - │ │ ├── 1 [as="project_const_col_@12":22] - │ │ └── false [as="project_const_col_@13":23] + │ │ ├── false [as="lookup_join_const_col_@13":23] + │ │ └── 1 [as="lookup_join_const_col_@12":22] │ └── filters (true) └── 20 @@ -847,7 +847,7 @@ project │ │ │ │ ├── fd: (1)-->(2-6), (2,4,5)~~>(1,3,6), (1,24-26)-->(22,23) │ │ │ │ ├── ordering: +1 │ │ │ │ ├── project - │ │ │ │ │ ├── columns: "project_const_col_@22":45!null "project_const_col_@23":46!null id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null + │ │ │ │ │ ├── columns: "lookup_join_const_col_@23":46!null "lookup_join_const_col_@22":45!null id:1!null name:2!null rarity:3 setname:4 number:5!null isfoil:6!null │ │ │ │ │ ├── immutable │ │ │ │ │ ├── stats: [rows=19000, distinct(1)=19000, null(1)=0, distinct(2)=13000, null(2)=0, distinct(5)=829, null(5)=0, distinct(6)=5601.15328, null(6)=0, distinct(45)=1, null(45)=0, distinct(46)=1, null(46)=0] │ │ │ │ │ ├── key: (1) @@ -869,8 +869,8 @@ project │ │ │ │ │ │ └── filters │ │ │ │ │ │ └── (name:2, setname:4, number:5) > ('Shock', '7E', 248) [outer=(2,4,5), immutable, constraints=(/2/4/5: [/'Shock'/'7E'/249 - ]; tight)] │ │ │ │ │ └── projections - │ │ │ │ │ ├── 1 [as="project_const_col_@22":45] - │ │ │ │ │ └── false [as="project_const_col_@23":46] + │ │ │ │ │ ├── false [as="lookup_join_const_col_@23":46] + │ │ │ │ │ └── 1 [as="lookup_join_const_col_@22":45] │ │ │ │ └── filters │ │ │ │ └── (transactiondate:24 >= '2020-02-28 00:00:00+00:00') AND (transactiondate:24 <= '2020-03-01 00:00:00+00:00') [outer=(24), constraints=(/24: [/'2020-02-28 00:00:00+00:00' - /'2020-03-01 00:00:00+00:00']; tight)] │ │ │ ├── scan cardsinfo @@ -1114,27 +1114,41 @@ limit │ ├── fd: (10)-->(17) │ ├── project │ │ ├── columns: quantity:16!null accountname:10!null - │ │ ├── inner-join (hash) + │ │ ├── inner-join (lookup inventorydetails) │ │ │ ├── columns: id:6!null quantity:7!null dealerid:8!null cardid:9!null accountname:10!null inventorydetails.quantity:11!null + │ │ │ ├── key columns: [18 6 19] = [8 9 10] + │ │ │ ├── lookup columns are key │ │ │ ├── fd: (8-10)-->(11), (6)==(9), (9)==(6) - │ │ │ ├── select - │ │ │ │ ├── columns: dealerid:8!null cardid:9!null accountname:10!null inventorydetails.quantity:11!null - │ │ │ │ ├── key: (8-10) - │ │ │ │ ├── fd: (8-10)-->(11) - │ │ │ │ ├── scan inventorydetails - │ │ │ │ │ ├── columns: dealerid:8!null cardid:9!null accountname:10!null inventorydetails.quantity:11!null - │ │ │ │ │ ├── constraint: /8/9/10: [/1 - /5] - │ │ │ │ │ ├── key: (8-10) - │ │ │ │ │ └── fd: (8-10)-->(11) - │ │ │ │ └── filters - │ │ │ │ └── accountname:10 IN ('account-1', 'account-2', 'account-3') [outer=(10), constraints=(/10: [/'account-1' - /'account-1'] [/'account-2' - /'account-2'] [/'account-3' - /'account-3']; tight)] - │ │ │ ├── values - │ │ │ │ ├── columns: id:6!null quantity:7!null - │ │ │ │ ├── cardinality: [2 - 2] - │ │ │ │ ├── (42948, 3) - │ │ │ │ └── (24924, 4) - │ │ │ └── filters - │ │ │ └── cardid:9 = id:6 [outer=(6,9), constraints=(/6: (/NULL - ]; /9: (/NULL - ]), fd=(6)==(9), (9)==(6)] + │ │ │ ├── inner-join (cross) + │ │ │ │ ├── columns: id:6!null quantity:7!null "lookup_join_const_col_@8":18!null "lookup_join_const_col_@10":19!null + │ │ │ │ ├── cardinality: [30 - 30] + │ │ │ │ ├── multiplicity: left-rows(one-or-more), right-rows(one-or-more) + │ │ │ │ ├── inner-join (cross) + │ │ │ │ │ ├── columns: id:6!null quantity:7!null "lookup_join_const_col_@8":18!null + │ │ │ │ │ ├── cardinality: [10 - 10] + │ │ │ │ │ ├── multiplicity: left-rows(one-or-more), right-rows(one-or-more) + │ │ │ │ │ ├── values + │ │ │ │ │ │ ├── columns: "lookup_join_const_col_@8":18!null + │ │ │ │ │ │ ├── cardinality: [5 - 5] + │ │ │ │ │ │ ├── (1,) + │ │ │ │ │ │ ├── (2,) + │ │ │ │ │ │ ├── (3,) + │ │ │ │ │ │ ├── (4,) + │ │ │ │ │ │ └── (5,) + │ │ │ │ │ ├── values + │ │ │ │ │ │ ├── columns: id:6!null quantity:7!null + │ │ │ │ │ │ ├── cardinality: [2 - 2] + │ │ │ │ │ │ ├── (42948, 3) + │ │ │ │ │ │ └── (24924, 4) + │ │ │ │ │ └── filters (true) + │ │ │ │ ├── values + │ │ │ │ │ ├── columns: "lookup_join_const_col_@10":19!null + │ │ │ │ │ ├── cardinality: [3 - 3] + │ │ │ │ │ ├── ('account-1',) + │ │ │ │ │ ├── ('account-2',) + │ │ │ │ │ └── ('account-3',) + │ │ │ │ └── filters (true) + │ │ │ └── filters (true) │ │ └── projections │ │ └── CASE WHEN quantity:7 < inventorydetails.quantity:11 THEN quantity:7 ELSE inventorydetails.quantity:11 END [as=quantity:16, outer=(7,11)] │ └── aggregations @@ -1491,7 +1505,7 @@ update cardsinfo [as=ci] │ │ ├── key columns: [53 21] = [36 37] │ │ ├── fd: ()-->(20), (21)-->(22-32,34,35), (28)-->(21-27,29-32), (21)==(34), (34)==(21) │ │ ├── project - │ │ │ ├── columns: "project_const_col_@36":53!null ci.dealerid:20!null ci.cardid:21!null buyprice:22!null sellprice:23!null discount:24!null desiredinventory:25!null actualinventory:26!null maxinventory:27!null ci.version:28!null ci.discountbuyprice:29 notes:30 oldinventory:31 ci.extra:32 c:34!null q:35!null + │ │ │ ├── columns: "lookup_join_const_col_@36":53!null ci.dealerid:20!null ci.cardid:21!null buyprice:22!null sellprice:23!null discount:24!null desiredinventory:25!null actualinventory:26!null maxinventory:27!null ci.version:28!null ci.discountbuyprice:29 notes:30 oldinventory:31 ci.extra:32 c:34!null q:35!null │ │ │ ├── cardinality: [0 - 2] │ │ │ ├── key: (21) │ │ │ ├── fd: ()-->(20,53), (21)-->(22-32,34,35), (28)-->(21-27,29-32), (21)==(34), (34)==(21) @@ -1508,7 +1522,7 @@ update cardsinfo [as=ci] │ │ │ │ │ ├── cardinality: [0 - 2] │ │ │ │ │ ├── fd: ()-->(20), (21)-->(22-32), (28)-->(21-27,29-32), (21)==(34), (34)==(21) │ │ │ │ │ ├── project - │ │ │ │ │ │ ├── columns: "project_const_col_@20":51!null c:34!null q:35!null + │ │ │ │ │ │ ├── columns: "lookup_join_const_col_@20":51!null c:34!null q:35!null │ │ │ │ │ │ ├── cardinality: [2 - 2] │ │ │ │ │ │ ├── fd: ()-->(51) │ │ │ │ │ │ ├── values @@ -1517,7 +1531,7 @@ update cardsinfo [as=ci] │ │ │ │ │ │ │ ├── (42948, 3) │ │ │ │ │ │ │ └── (24924, 4) │ │ │ │ │ │ └── projections - │ │ │ │ │ │ └── 1 [as="project_const_col_@20":51] + │ │ │ │ │ │ └── 1 [as="lookup_join_const_col_@20":51] │ │ │ │ │ └── filters (true) │ │ │ │ └── aggregations │ │ │ │ ├── first-agg [as=buyprice:22, outer=(22)] @@ -1549,7 +1563,7 @@ update cardsinfo [as=ci] │ │ │ │ └── const-agg [as=ci.dealerid:20, outer=(20)] │ │ │ │ └── ci.dealerid:20 │ │ │ └── projections - │ │ │ └── 1 [as="project_const_col_@36":53] + │ │ │ └── 1 [as="lookup_join_const_col_@36":53] │ │ └── filters (true) │ └── aggregations │ ├── sum-int [as=sum_int:44, outer=(39)] diff --git a/pkg/sql/opt/xform/testdata/physprops/ordering b/pkg/sql/opt/xform/testdata/physprops/ordering index 155fa568e160..2ee8d04d5346 100644 --- a/pkg/sql/opt/xform/testdata/physprops/ordering +++ b/pkg/sql/opt/xform/testdata/physprops/ordering @@ -2047,7 +2047,7 @@ distinct-on ├── fd: ()-->(7), (1)==(4,8), (4)==(1,8), (8)==(1,4) ├── ordering: +(1|4|8) opt(7) [actual: +1] ├── project - │ ├── columns: "project_const_col_@7":11!null a:1!null b:4!null + │ ├── columns: "lookup_join_const_col_@7":11!null a:1!null b:4!null │ ├── fd: ()-->(11), (1)==(4), (4)==(1) │ ├── ordering: +(1|4) [actual: +1] │ ├── inner-join (lookup t44469_b@secondary) @@ -2061,7 +2061,7 @@ distinct-on │ │ │ └── ordering: +1 │ │ └── filters (true) │ └── projections - │ └── 1 [as="project_const_col_@7":11] + │ └── 1 [as="lookup_join_const_col_@7":11] └── filters (true) # Regression test for #47041: factor check constraints into the (canonical) diff --git a/pkg/sql/opt/xform/testdata/rules/groupby b/pkg/sql/opt/xform/testdata/rules/groupby index 5c3641043285..0d7a898453d8 100644 --- a/pkg/sql/opt/xform/testdata/rules/groupby +++ b/pkg/sql/opt/xform/testdata/rules/groupby @@ -1720,7 +1720,7 @@ memo (optimized, ~4KB, required=[presentation: u:2,v:3,w:4] [ordering: +4]) memo SELECT (SELECT w FROM kuvw WHERE v=1 AND x=u) FROM xyz ORDER BY x+1, x ---- -memo (optimized, ~25KB, required=[presentation: w:10] [ordering: +11,+1]) +memo (optimized, ~27KB, required=[presentation: w:10] [ordering: +11,+1]) ├── G1: (project G2 G3 x) │ ├── [presentation: w:10] [ordering: +11,+1] │ │ ├── best: (sort G1) diff --git a/pkg/sql/opt/xform/testdata/rules/join b/pkg/sql/opt/xform/testdata/rules/join index cd078eda57d0..6c65b6d96e65 100644 --- a/pkg/sql/opt/xform/testdata/rules/join +++ b/pkg/sql/opt/xform/testdata/rules/join @@ -3225,6 +3225,120 @@ inner-join (lookup bool_col) │ └── filters (true) └── filters (true) +# Use constant CHECK constraints to generate lookup join keys. +exec-ddl +CREATE TABLE abcd_check (a INT, b INT NOT NULL, c INT, d INT, CHECK (b IN (10, 20)), INDEX (a, b, c)) +---- + +opt expect=GenerateLookupJoins +SELECT * FROM small INNER JOIN abcd_check ON a=m +---- +inner-join (lookup abcd_check) + ├── columns: m:1!null n:2 a:5!null b:6!null c:7 d:8 + ├── key columns: [9] = [9] + ├── lookup columns are key + ├── fd: (1)==(5), (5)==(1) + ├── inner-join (lookup abcd_check@secondary) + │ ├── columns: m:1!null n:2 a:5!null b:6!null c:7 abcd_check.rowid:9!null + │ ├── key columns: [1 11] = [5 6] + │ ├── fd: (9)-->(5-7), (1)==(5), (5)==(1) + │ ├── inner-join (cross) + │ │ ├── columns: m:1 n:2 "lookup_join_const_col_@6":11!null + │ │ ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more) + │ │ ├── scan small + │ │ │ └── columns: m:1 n:2 + │ │ ├── values + │ │ │ ├── columns: "lookup_join_const_col_@6":11!null + │ │ │ ├── cardinality: [2 - 2] + │ │ │ ├── (10,) + │ │ │ └── (20,) + │ │ └── filters (true) + │ └── filters (true) + └── filters (true) + +opt expect=GenerateLookupJoinsWithFilter +SELECT * FROM small INNER JOIN abcd_check ON a=m AND c=30 +---- +inner-join (lookup abcd_check) + ├── columns: m:1!null n:2 a:5!null b:6!null c:7!null d:8 + ├── key columns: [9] = [9] + ├── lookup columns are key + ├── fd: ()-->(7), (1)==(5), (5)==(1) + ├── inner-join (lookup abcd_check@secondary) + │ ├── columns: m:1!null n:2 a:5!null b:6!null c:7!null abcd_check.rowid:9!null + │ ├── key columns: [1 11 12] = [5 6 7] + │ ├── fd: ()-->(7), (9)-->(5,6), (1)==(5), (5)==(1) + │ ├── project + │ │ ├── columns: "lookup_join_const_col_@7":12!null m:1 n:2 "lookup_join_const_col_@6":11!null + │ │ ├── fd: ()-->(12) + │ │ ├── inner-join (cross) + │ │ │ ├── columns: m:1 n:2 "lookup_join_const_col_@6":11!null + │ │ │ ├── multiplicity: left-rows(one-or-more), right-rows(zero-or-more) + │ │ │ ├── scan small + │ │ │ │ └── columns: m:1 n:2 + │ │ │ ├── values + │ │ │ │ ├── columns: "lookup_join_const_col_@6":11!null + │ │ │ │ ├── cardinality: [2 - 2] + │ │ │ │ ├── (10,) + │ │ │ │ └── (20,) + │ │ │ └── filters (true) + │ │ └── projections + │ │ └── 30 [as="lookup_join_const_col_@7":12] + │ └── filters (true) + └── filters (true) + +# Use computed column expressions to generate lookup join keys. +exec-ddl +CREATE TABLE abcd_comp (a INT, b INT AS (d % 4) STORED, c INT, d INT, INDEX (a, b, c)) +---- + +opt expect=GenerateLookupJoinsWithFilter +SELECT * FROM small INNER JOIN abcd_comp ON a=m AND d=5 +---- +inner-join (lookup abcd_comp) + ├── columns: m:1!null n:2 a:5!null b:6 c:7 d:8!null + ├── key columns: [9] = [9] + ├── lookup columns are key + ├── fd: ()-->(8), (1)==(5), (5)==(1) + ├── inner-join (lookup abcd_comp@secondary) + │ ├── columns: m:1!null n:2 a:5!null b:6!null c:7 abcd_comp.rowid:9!null + │ ├── key columns: [1 11] = [5 6] + │ ├── fd: ()-->(6), (9)-->(5,7), (1)==(5), (5)==(1) + │ ├── project + │ │ ├── columns: "lookup_join_const_col_@6":11!null m:1 n:2 + │ │ ├── fd: ()-->(11) + │ │ ├── scan small + │ │ │ └── columns: m:1 n:2 + │ │ └── projections + │ │ └── 1 [as="lookup_join_const_col_@6":11] + │ └── filters (true) + └── filters + └── d:8 = 5 [outer=(8), constraints=(/8: [/5 - /5]; tight), fd=()-->(8)] + +opt expect=GenerateLookupJoinsWithFilter +SELECT * FROM small INNER JOIN abcd_comp ON a=m AND d=5 AND c=30 +---- +inner-join (lookup abcd_comp) + ├── columns: m:1!null n:2 a:5!null b:6 c:7!null d:8!null + ├── key columns: [9] = [9] + ├── lookup columns are key + ├── fd: ()-->(7,8), (1)==(5), (5)==(1) + ├── inner-join (lookup abcd_comp@secondary) + │ ├── columns: m:1!null n:2 a:5!null b:6!null c:7!null abcd_comp.rowid:9!null + │ ├── key columns: [1 11 12] = [5 6 7] + │ ├── fd: ()-->(6,7), (9)-->(5), (1)==(5), (5)==(1) + │ ├── project + │ │ ├── columns: "lookup_join_const_col_@7":12!null "lookup_join_const_col_@6":11!null m:1 n:2 + │ │ ├── fd: ()-->(11,12) + │ │ ├── scan small + │ │ │ └── columns: m:1 n:2 + │ │ └── projections + │ │ ├── 30 [as="lookup_join_const_col_@7":12] + │ │ └── 1 [as="lookup_join_const_col_@6":11] + │ └── filters (true) + └── filters + └── d:8 = 5 [outer=(8), constraints=(/8: [/5 - /5]; tight), fd=()-->(8)] + exec-ddl CREATE TABLE t(pk INT PRIMARY KEY, col0 INT, col1 INT, col2 INT, col4 INT, UNIQUE INDEX (col2)) ----