diff --git a/mysql-test/r/join_nested.result b/mysql-test/r/join_nested.result index 84b6ff640e9..6ddd39cbfec 100644 --- a/mysql-test/r/join_nested.result +++ b/mysql-test/r/join_nested.result @@ -1870,4 +1870,99 @@ f4 NULL NULL DROP TABLE t1,t2,t3,t4,t5; +# +# MDEV-7992: Nested left joins + 'not exists' optimization +# +CREATE TABLE t1( +K1 INT PRIMARY KEY, +Name VARCHAR(15) +); +INSERT INTO t1 VALUES +(1,'T1Row1'), (2,'T1Row2'); +CREATE TABLE t2( +K2 INT PRIMARY KEY, +K1r INT, +rowTimestamp DATETIME, +Event VARCHAR(15) +); +INSERT INTO t2 VALUES +(1, 1, '2015-04-13 10:42:11' ,'T1Row1Event1'), +(2, 1, '2015-04-13 10:42:12' ,'T1Row1Event2'), +(3, 1, '2015-04-13 10:42:12' ,'T1Row1Event3'); +SELECT t1a.*, t2a.*, +t2i.K2 AS K2B, t2i.K1r AS K1rB, +t2i.rowTimestamp AS rowTimestampB, t2i.Event AS EventB +FROM +t1 t1a JOIN t2 t2a ON t2a.K1r = t1a.K1 +LEFT JOIN +( t1 t1i LEFT JOIN t2 t2i ON t2i.K1r = t1i.K1) +ON (t1i.K1 = 1) AND +(((t2i.K1r = t1a.K1 AND t2i.rowTimestamp > t2a.rowTimestamp ) OR +(t2i.rowTimestamp = t2a.rowTimestamp AND t2i.K2 > t2a.K2)) +OR (t2i.K2 IS NULL)) +WHERE +t2a.K1r = 1 AND t2i.K2 IS NULL; +K1 Name K2 K1r rowTimestamp Event K2B K1rB rowTimestampB EventB +1 T1Row1 3 1 2015-04-13 10:42:12 T1Row1Event3 NULL NULL NULL NULL +EXPLAIN EXTENDED SELECT t1a.*, t2a.*, +t2i.K2 AS K2B, t2i.K1r AS K1rB, +t2i.rowTimestamp AS rowTimestampB, t2i.Event AS EventB +FROM +t1 t1a JOIN t2 t2a ON t2a.K1r = t1a.K1 +LEFT JOIN +( t1 t1i LEFT JOIN t2 t2i ON t2i.K1r = t1i.K1) +ON (t1i.K1 = 1) AND +(((t2i.K1r = t1a.K1 AND t2i.rowTimestamp > t2a.rowTimestamp ) OR +(t2i.rowTimestamp = t2a.rowTimestamp AND t2i.K2 > t2a.K2)) +OR (t2i.K2 IS NULL)) +WHERE +t2a.K1r = 1 AND t2i.K2 IS NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1a const PRIMARY PRIMARY 4 const 1 100.00 +1 SIMPLE t2a ALL NULL NULL NULL NULL 3 100.00 Using where +1 SIMPLE t1i const PRIMARY PRIMARY 4 const 1 100.00 Using index +1 SIMPLE t2i ALL NULL NULL NULL NULL 3 100.00 Using where; Not exists +Warnings: +Note 1003 select 1 AS `K1`,'T1Row1' AS `Name`,`test`.`t2a`.`K2` AS `K2`,`test`.`t2a`.`K1r` AS `K1r`,`test`.`t2a`.`rowTimestamp` AS `rowTimestamp`,`test`.`t2a`.`Event` AS `Event`,`test`.`t2i`.`K2` AS `K2B`,`test`.`t2i`.`K1r` AS `K1rB`,`test`.`t2i`.`rowTimestamp` AS `rowTimestampB`,`test`.`t2i`.`Event` AS `EventB` from `test`.`t1` `t1a` join `test`.`t2` `t2a` left join (`test`.`t1` `t1i` left join `test`.`t2` `t2i` on((`test`.`t2i`.`K1r` = 1))) on(((`test`.`t1i`.`K1` = 1) and (((`test`.`t2i`.`K1r` = 1) and (`test`.`t2i`.`rowTimestamp` > `test`.`t2a`.`rowTimestamp`)) or ((`test`.`t2i`.`rowTimestamp` = `test`.`t2a`.`rowTimestamp`) and (`test`.`t2i`.`K2` > `test`.`t2a`.`K2`)) or isnull(`test`.`t2i`.`K2`)))) where ((`test`.`t2a`.`K1r` = 1) and isnull(`test`.`t2i`.`K2`)) +CREATE VIEW v1 AS +SELECT t2i.* +FROM t1 as t1i LEFT JOIN t2 as t2i ON t2i.K1r = t1i.K1 +WHERE t1i.K1 = 1 ; +SELECT +t1a.*, t2a.*, t2b.K2 as K2B, t2b.K1r as K1rB, +t2b.rowTimestamp as rowTimestampB, t2b.Event as EventB +FROM +t1 as t1a JOIN t2 as t2a ON t2a.K1r = t1a.K1 +LEFT JOIN +v1 as t2b +ON ((t2b.K1r = t1a.K1 AND t2b.rowTimestamp > t2a.rowTimestamp) OR +(t2b.rowTimestamp = t2a.rowTimestamp AND t2b.K2 > t2a.K2)) +OR (t2b.K2 IS NULL) +WHERE +t1a.K1 = 1 AND +t2b.K2 IS NULL; +K1 Name K2 K1r rowTimestamp Event K2B K1rB rowTimestampB EventB +1 T1Row1 3 1 2015-04-13 10:42:12 T1Row1Event3 NULL NULL NULL NULL +EXPLAIN EXTENDED SELECT +t1a.*, t2a.*, t2b.K2 as K2B, t2b.K1r as K1rB, +t2b.rowTimestamp as rowTimestampB, t2b.Event as EventB +FROM +t1 as t1a JOIN t2 as t2a ON t2a.K1r = t1a.K1 +LEFT JOIN +v1 as t2b +ON ((t2b.K1r = t1a.K1 AND t2b.rowTimestamp > t2a.rowTimestamp) OR +(t2b.rowTimestamp = t2a.rowTimestamp AND t2b.K2 > t2a.K2)) +OR (t2b.K2 IS NULL) +WHERE +t1a.K1 = 1 AND +t2b.K2 IS NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1a const PRIMARY PRIMARY 4 const 1 100.00 +1 SIMPLE t2a ALL NULL NULL NULL NULL 3 100.00 Using where +1 SIMPLE t1i const PRIMARY PRIMARY 4 const 1 100.00 Using index +1 SIMPLE t2i ALL NULL NULL NULL NULL 3 100.00 Using where; Not exists +Warnings: +Note 1003 select 1 AS `K1`,'T1Row1' AS `Name`,`t2a`.`K2` AS `K2`,`t2a`.`K1r` AS `K1r`,`t2a`.`rowTimestamp` AS `rowTimestamp`,`t2a`.`Event` AS `Event`,`test`.`t2i`.`K2` AS `K2B`,`test`.`t2i`.`K1r` AS `K1rB`,`test`.`t2i`.`rowTimestamp` AS `rowTimestampB`,`test`.`t2i`.`Event` AS `EventB` from `test`.`t1` `t1a` join `test`.`t2` `t2a` left join (`test`.`t1` `t1i` left join `test`.`t2` `t2i` on((`test`.`t2i`.`K1r` = 1))) on(((`test`.`t1i`.`K1` = 1) and (((`test`.`t2i`.`K1r` = 1) and (`test`.`t2i`.`rowTimestamp` > `t2a`.`rowTimestamp`)) or ((`test`.`t2i`.`rowTimestamp` = `t2a`.`rowTimestamp`) and (`test`.`t2i`.`K2` > `t2a`.`K2`)) or isnull(`test`.`t2i`.`K2`)))) where ((`t2a`.`K1r` = 1) and isnull(`test`.`t2i`.`K2`)) +DROP VIEW v1; +DROP TABLE t1,t2; set optimizer_search_depth= @tmp_mdev621; diff --git a/mysql-test/r/join_nested_jcl6.result b/mysql-test/r/join_nested_jcl6.result index 3b47645ca79..bac8e1cb7db 100644 --- a/mysql-test/r/join_nested_jcl6.result +++ b/mysql-test/r/join_nested_jcl6.result @@ -1881,6 +1881,101 @@ f4 NULL NULL DROP TABLE t1,t2,t3,t4,t5; +# +# MDEV-7992: Nested left joins + 'not exists' optimization +# +CREATE TABLE t1( +K1 INT PRIMARY KEY, +Name VARCHAR(15) +); +INSERT INTO t1 VALUES +(1,'T1Row1'), (2,'T1Row2'); +CREATE TABLE t2( +K2 INT PRIMARY KEY, +K1r INT, +rowTimestamp DATETIME, +Event VARCHAR(15) +); +INSERT INTO t2 VALUES +(1, 1, '2015-04-13 10:42:11' ,'T1Row1Event1'), +(2, 1, '2015-04-13 10:42:12' ,'T1Row1Event2'), +(3, 1, '2015-04-13 10:42:12' ,'T1Row1Event3'); +SELECT t1a.*, t2a.*, +t2i.K2 AS K2B, t2i.K1r AS K1rB, +t2i.rowTimestamp AS rowTimestampB, t2i.Event AS EventB +FROM +t1 t1a JOIN t2 t2a ON t2a.K1r = t1a.K1 +LEFT JOIN +( t1 t1i LEFT JOIN t2 t2i ON t2i.K1r = t1i.K1) +ON (t1i.K1 = 1) AND +(((t2i.K1r = t1a.K1 AND t2i.rowTimestamp > t2a.rowTimestamp ) OR +(t2i.rowTimestamp = t2a.rowTimestamp AND t2i.K2 > t2a.K2)) +OR (t2i.K2 IS NULL)) +WHERE +t2a.K1r = 1 AND t2i.K2 IS NULL; +K1 Name K2 K1r rowTimestamp Event K2B K1rB rowTimestampB EventB +1 T1Row1 3 1 2015-04-13 10:42:12 T1Row1Event3 NULL NULL NULL NULL +EXPLAIN EXTENDED SELECT t1a.*, t2a.*, +t2i.K2 AS K2B, t2i.K1r AS K1rB, +t2i.rowTimestamp AS rowTimestampB, t2i.Event AS EventB +FROM +t1 t1a JOIN t2 t2a ON t2a.K1r = t1a.K1 +LEFT JOIN +( t1 t1i LEFT JOIN t2 t2i ON t2i.K1r = t1i.K1) +ON (t1i.K1 = 1) AND +(((t2i.K1r = t1a.K1 AND t2i.rowTimestamp > t2a.rowTimestamp ) OR +(t2i.rowTimestamp = t2a.rowTimestamp AND t2i.K2 > t2a.K2)) +OR (t2i.K2 IS NULL)) +WHERE +t2a.K1r = 1 AND t2i.K2 IS NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1a const PRIMARY PRIMARY 4 const 1 100.00 +1 SIMPLE t2a ALL NULL NULL NULL NULL 3 100.00 Using where +1 SIMPLE t1i const PRIMARY PRIMARY 4 const 1 100.00 Using index +1 SIMPLE t2i ALL NULL NULL NULL NULL 3 100.00 Using where; Not exists +Warnings: +Note 1003 select 1 AS `K1`,'T1Row1' AS `Name`,`test`.`t2a`.`K2` AS `K2`,`test`.`t2a`.`K1r` AS `K1r`,`test`.`t2a`.`rowTimestamp` AS `rowTimestamp`,`test`.`t2a`.`Event` AS `Event`,`test`.`t2i`.`K2` AS `K2B`,`test`.`t2i`.`K1r` AS `K1rB`,`test`.`t2i`.`rowTimestamp` AS `rowTimestampB`,`test`.`t2i`.`Event` AS `EventB` from `test`.`t1` `t1a` join `test`.`t2` `t2a` left join (`test`.`t1` `t1i` left join `test`.`t2` `t2i` on((`test`.`t2i`.`K1r` = 1))) on(((`test`.`t1i`.`K1` = 1) and (((`test`.`t2i`.`K1r` = 1) and (`test`.`t2i`.`rowTimestamp` > `test`.`t2a`.`rowTimestamp`)) or ((`test`.`t2i`.`rowTimestamp` = `test`.`t2a`.`rowTimestamp`) and (`test`.`t2i`.`K2` > `test`.`t2a`.`K2`)) or isnull(`test`.`t2i`.`K2`)))) where ((`test`.`t2a`.`K1r` = 1) and isnull(`test`.`t2i`.`K2`)) +CREATE VIEW v1 AS +SELECT t2i.* +FROM t1 as t1i LEFT JOIN t2 as t2i ON t2i.K1r = t1i.K1 +WHERE t1i.K1 = 1 ; +SELECT +t1a.*, t2a.*, t2b.K2 as K2B, t2b.K1r as K1rB, +t2b.rowTimestamp as rowTimestampB, t2b.Event as EventB +FROM +t1 as t1a JOIN t2 as t2a ON t2a.K1r = t1a.K1 +LEFT JOIN +v1 as t2b +ON ((t2b.K1r = t1a.K1 AND t2b.rowTimestamp > t2a.rowTimestamp) OR +(t2b.rowTimestamp = t2a.rowTimestamp AND t2b.K2 > t2a.K2)) +OR (t2b.K2 IS NULL) +WHERE +t1a.K1 = 1 AND +t2b.K2 IS NULL; +K1 Name K2 K1r rowTimestamp Event K2B K1rB rowTimestampB EventB +1 T1Row1 3 1 2015-04-13 10:42:12 T1Row1Event3 NULL NULL NULL NULL +EXPLAIN EXTENDED SELECT +t1a.*, t2a.*, t2b.K2 as K2B, t2b.K1r as K1rB, +t2b.rowTimestamp as rowTimestampB, t2b.Event as EventB +FROM +t1 as t1a JOIN t2 as t2a ON t2a.K1r = t1a.K1 +LEFT JOIN +v1 as t2b +ON ((t2b.K1r = t1a.K1 AND t2b.rowTimestamp > t2a.rowTimestamp) OR +(t2b.rowTimestamp = t2a.rowTimestamp AND t2b.K2 > t2a.K2)) +OR (t2b.K2 IS NULL) +WHERE +t1a.K1 = 1 AND +t2b.K2 IS NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1a const PRIMARY PRIMARY 4 const 1 100.00 +1 SIMPLE t2a ALL NULL NULL NULL NULL 3 100.00 Using where +1 SIMPLE t1i const PRIMARY PRIMARY 4 const 1 100.00 Using index +1 SIMPLE t2i ALL NULL NULL NULL NULL 3 100.00 Using where; Not exists +Warnings: +Note 1003 select 1 AS `K1`,'T1Row1' AS `Name`,`t2a`.`K2` AS `K2`,`t2a`.`K1r` AS `K1r`,`t2a`.`rowTimestamp` AS `rowTimestamp`,`t2a`.`Event` AS `Event`,`test`.`t2i`.`K2` AS `K2B`,`test`.`t2i`.`K1r` AS `K1rB`,`test`.`t2i`.`rowTimestamp` AS `rowTimestampB`,`test`.`t2i`.`Event` AS `EventB` from `test`.`t1` `t1a` join `test`.`t2` `t2a` left join (`test`.`t1` `t1i` left join `test`.`t2` `t2i` on((`test`.`t2i`.`K1r` = 1))) on(((`test`.`t1i`.`K1` = 1) and (((`test`.`t2i`.`K1r` = 1) and (`test`.`t2i`.`rowTimestamp` > `t2a`.`rowTimestamp`)) or ((`test`.`t2i`.`rowTimestamp` = `t2a`.`rowTimestamp`) and (`test`.`t2i`.`K2` > `t2a`.`K2`)) or isnull(`test`.`t2i`.`K2`)))) where ((`t2a`.`K1r` = 1) and isnull(`test`.`t2i`.`K2`)) +DROP VIEW v1; +DROP TABLE t1,t2; set optimizer_search_depth= @tmp_mdev621; CREATE TABLE t5 (a int, b int, c int, PRIMARY KEY(a), KEY b_i (b)); CREATE TABLE t6 (a int, b int, c int, PRIMARY KEY(a), KEY b_i (b)); diff --git a/mysql-test/t/join_nested.test b/mysql-test/t/join_nested.test index 7b7d9236835..e60b7827f75 100644 --- a/mysql-test/t/join_nested.test +++ b/mysql-test/t/join_nested.test @@ -1309,5 +1309,74 @@ LEFT JOIN t4 AS alias5 JOIN t5 ON alias5.f5 ON alias2.f3 ON alias1.f2; DROP TABLE t1,t2,t3,t4,t5; -set optimizer_search_depth= @tmp_mdev621; +--echo # +--echo # MDEV-7992: Nested left joins + 'not exists' optimization +--echo # + +CREATE TABLE t1( + K1 INT PRIMARY KEY, + Name VARCHAR(15) +); + +INSERT INTO t1 VALUES + (1,'T1Row1'), (2,'T1Row2'); + + +CREATE TABLE t2( + K2 INT PRIMARY KEY, + K1r INT, + rowTimestamp DATETIME, + Event VARCHAR(15) +); + +INSERT INTO t2 VALUES + (1, 1, '2015-04-13 10:42:11' ,'T1Row1Event1'), + (2, 1, '2015-04-13 10:42:12' ,'T1Row1Event2'), + (3, 1, '2015-04-13 10:42:12' ,'T1Row1Event3'); + +let $q1= +SELECT t1a.*, t2a.*, + t2i.K2 AS K2B, t2i.K1r AS K1rB, + t2i.rowTimestamp AS rowTimestampB, t2i.Event AS EventB +FROM + t1 t1a JOIN t2 t2a ON t2a.K1r = t1a.K1 + LEFT JOIN + ( t1 t1i LEFT JOIN t2 t2i ON t2i.K1r = t1i.K1) + ON (t1i.K1 = 1) AND + (((t2i.K1r = t1a.K1 AND t2i.rowTimestamp > t2a.rowTimestamp ) OR + (t2i.rowTimestamp = t2a.rowTimestamp AND t2i.K2 > t2a.K2)) + OR (t2i.K2 IS NULL)) +WHERE +t2a.K1r = 1 AND t2i.K2 IS NULL; + +eval $q1; +eval EXPLAIN EXTENDED $q1; + +CREATE VIEW v1 AS + SELECT t2i.* + FROM t1 as t1i LEFT JOIN t2 as t2i ON t2i.K1r = t1i.K1 + WHERE t1i.K1 = 1 ; + +let $q2= +SELECT + t1a.*, t2a.*, t2b.K2 as K2B, t2b.K1r as K1rB, + t2b.rowTimestamp as rowTimestampB, t2b.Event as EventB +FROM + t1 as t1a JOIN t2 as t2a ON t2a.K1r = t1a.K1 + LEFT JOIN + v1 as t2b + ON ((t2b.K1r = t1a.K1 AND t2b.rowTimestamp > t2a.rowTimestamp) OR + (t2b.rowTimestamp = t2a.rowTimestamp AND t2b.K2 > t2a.K2)) + OR (t2b.K2 IS NULL) +WHERE + t1a.K1 = 1 AND + t2b.K2 IS NULL; + +eval $q2; +eval EXPLAIN EXTENDED $q2; + +DROP VIEW v1; +DROP TABLE t1,t2; + +set optimizer_search_depth= @tmp_mdev621; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index d9433a4167a..4133c8258a2 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -7793,8 +7793,6 @@ get_best_combination(JOIN *join) form=join->table[tablenr]=j->table; used_tables|= form->map; form->reginfo.join_tab=j; - if (!*j->on_expr_ref) - form->reginfo.not_exists_optimize=0; // Only with LEFT JOIN DBUG_PRINT("info",("type: %d", j->type)); if (j->type == JT_CONST) goto loop_end; // Handled in make_join_stat.. @@ -8625,7 +8623,10 @@ make_outerjoin_info(JOIN *join) tab->cond_equal= tbl->cond_equal; if (embedding && !embedding->is_active_sjm()) tab->first_upper= embedding->nested_join->first_nested; - } + } + else if (!embedding) + tab->table->reginfo.not_exists_optimize= 0; + for ( ; embedding ; embedding= embedding->embedding) { if (embedding->is_active_sjm()) @@ -8635,7 +8636,10 @@ make_outerjoin_info(JOIN *join) } /* Ignore sj-nests: */ if (!(embedding->on_expr && embedding->outer_join)) + { + tab->table->reginfo.not_exists_optimize= 0; continue; + } NESTED_JOIN *nested_join= embedding->nested_join; if (!nested_join->counter) { @@ -8651,17 +8655,10 @@ make_outerjoin_info(JOIN *join) } if (!tab->first_inner) tab->first_inner= nested_join->first_nested; - if (tab->table->reginfo.not_exists_optimize) - tab->first_inner->table->reginfo.not_exists_optimize= 1; if (++nested_join->counter < nested_join->n_tables) break; /* Table tab is the last inner table for nested join. */ nested_join->first_nested->last_inner= tab; - if (tab->first_inner->table->reginfo.not_exists_optimize) - { - for (JOIN_TAB *join_tab= tab->first_inner; join_tab <= tab; join_tab++) - join_tab->table->reginfo.not_exists_optimize= 1; - } } } DBUG_RETURN(FALSE); @@ -17099,32 +17096,41 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, first_unmatched->found= 1; for (JOIN_TAB *tab= first_unmatched; tab <= join_tab; tab++) { + /* + Check whether 'not exists' optimization can be used here. + If tab->table->reginfo.not_exists_optimize is set to true + then WHERE contains a conjunctive predicate IS NULL over + a non-nullable field of tab. When activated this predicate + will filter out all records with matches for the left part + of the outer join whose inner tables start from the + first_unmatched table and include table tab. To safely use + 'not exists' optimization we have to check that the + IS NULL predicate is really activated, i.e. all guards + that wrap it are in the 'open' state. + */ + bool not_exists_opt_is_applicable= + tab->table->reginfo.not_exists_optimize; + for (JOIN_TAB *first_upper= first_unmatched->first_upper; + not_exists_opt_is_applicable && first_upper; + first_upper= first_upper->first_upper) + { + if (!first_upper->found) + not_exists_opt_is_applicable= false; + } /* Check all predicates that has just been activated. */ /* Actually all predicates non-guarded by first_unmatched->found will be re-evaluated again. It could be fixed, but, probably, it's not worth doing now. */ - /* - not_exists_optimize has been created from a - select_cond containing 'is_null'. This 'is_null' - predicate is still present on any 'tab' with - 'not_exists_optimize'. Furthermore, the usual rules - for condition guards also applies for - 'not_exists_optimize' -> When 'is_null==false' we - know all cond. guards are open and we can apply - the 'not_exists_optimize'. - */ - DBUG_ASSERT(!(tab->table->reginfo.not_exists_optimize && - !tab->select_cond)); - if (tab->select_cond && !tab->select_cond->val_int()) { /* The condition attached to table tab is false */ - if (tab == join_tab) { found= 0; + if (not_exists_opt_is_applicable) + DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS); } else { @@ -17133,21 +17139,10 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, not to the last table of the current nest level. */ join->return_tab= tab; - } - - if (tab->table->reginfo.not_exists_optimize) - { - /* - When not_exists_optimize is set: No need to further - explore more rows of 'tab' for this partial result. - Any found 'tab' matches are known to evaluate to 'false'. - Returning .._NO_MORE_ROWS will skip rem. 'tab' rows. - */ - DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS); - } - else if (tab != join_tab) - { - DBUG_RETURN(NESTED_LOOP_OK); + if (not_exists_opt_is_applicable) + DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS); + else + DBUG_RETURN(NESTED_LOOP_OK); } } }