mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
LEFT JOIN optimization: Change LEFT JOIN to normal join if possible
mysql-test/r/select.result: Added test for LEFT JOIN optimization mysql-test/t/select.test: Added test for LEFT JOIN optimization sql/item.h: LEFT JOIN optimization sql/item_cmpfunc.cc: LEFT JOIN optimization sql/item_cmpfunc.h: LEFT JOIN optimization sql/item_func.cc: LEFT JOIN optimization sql/item_func.h: LEFT JOIN optimization sql/item_strfunc.cc: LEFT JOIN optimization sql/sql_base.cc: Heart of LEFT JOIN optimization
This commit is contained in:
@ -2569,16 +2569,46 @@ fld1 fld1
|
||||
250503 250505
|
||||
250504 250505
|
||||
250505 250505
|
||||
insert into t2 (fld1, companynr) values (999999,99);
|
||||
select t2.companynr,companyname from t2 left join t4 using (companynr) where t4.companynr is null;
|
||||
companynr companyname
|
||||
99 NULL
|
||||
select count(*) from t2 left join t4 using (companynr) where t4.companynr is not null;
|
||||
count(*)
|
||||
1199
|
||||
explain select t2.companynr,companyname from t2 left join t4 using (companynr) where t4.companynr is null;
|
||||
table type possible_keys key key_len ref rows Extra
|
||||
t2 ALL NULL NULL NULL NULL 1199
|
||||
t2 ALL NULL NULL NULL NULL 1200
|
||||
t4 eq_ref PRIMARY PRIMARY 1 test.t2.companynr 1 Using where; Not exists
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr is null;
|
||||
table type possible_keys key key_len ref rows Extra
|
||||
t4 ALL NULL NULL NULL NULL 12
|
||||
t2 ALL NULL NULL NULL NULL 1199 Using where; Not exists
|
||||
t2 ALL NULL NULL NULL NULL 1200 Using where; Not exists
|
||||
delete from t2 where fld1=999999;
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0;
|
||||
table type possible_keys key key_len ref rows Extra
|
||||
t2 ALL NULL NULL NULL NULL 1199 Using where
|
||||
t4 eq_ref PRIMARY PRIMARY 1 test.t2.companynr 1
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 or t2.companynr < 0;
|
||||
table type possible_keys key key_len ref rows Extra
|
||||
t2 ALL NULL NULL NULL NULL 1199 Using where
|
||||
t4 eq_ref PRIMARY PRIMARY 1 test.t2.companynr 1
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 and t4.companynr > 0;
|
||||
table type possible_keys key key_len ref rows Extra
|
||||
t2 ALL NULL NULL NULL NULL 1199 Using where
|
||||
t4 eq_ref PRIMARY PRIMARY 1 test.t2.companynr 1 Using where
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 or t2.companynr is null;
|
||||
table type possible_keys key key_len ref rows Extra
|
||||
t4 ALL NULL NULL NULL NULL 12
|
||||
t2 ALL NULL NULL NULL NULL 1199 Using where
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 or t2.companynr < 0 or t4.companynr > 0;
|
||||
table type possible_keys key key_len ref rows Extra
|
||||
t4 ALL PRIMARY NULL NULL NULL 12
|
||||
t2 ALL NULL NULL NULL NULL 1199 Using where
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where ifnull(t2.companynr,1)>0;
|
||||
table type possible_keys key key_len ref rows Extra
|
||||
t4 ALL NULL NULL NULL NULL 12
|
||||
t2 ALL NULL NULL NULL NULL 1199 Using where
|
||||
select distinct t2.companynr,t4.companynr from t2,t4 where t2.companynr=t4.companynr+1;
|
||||
companynr companynr
|
||||
37 36
|
||||
|
@ -1527,10 +1527,24 @@ select t2.fld1,t22.fld1 from t2,t2 t22 where t2.fld1 >= 250501 and t2.fld1 <= 25
|
||||
#
|
||||
# Test of left join.
|
||||
#
|
||||
insert into t2 (fld1, companynr) values (999999,99);
|
||||
|
||||
select t2.companynr,companyname from t2 left join t4 using (companynr) where t4.companynr is null;
|
||||
select count(*) from t2 left join t4 using (companynr) where t4.companynr is not null;
|
||||
explain select t2.companynr,companyname from t2 left join t4 using (companynr) where t4.companynr is null;
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr is null;
|
||||
delete from t2 where fld1=999999;
|
||||
|
||||
#
|
||||
# Test left join optimization
|
||||
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0;
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 or t2.companynr < 0;
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 and t4.companynr > 0;
|
||||
# Following can't be optimized
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 or t2.companynr is null;
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where t2.companynr > 0 or t2.companynr < 0 or t4.companynr > 0;
|
||||
explain select t2.companynr,companyname from t4 left join t2 using (companynr) where ifnull(t2.companynr,1)>0;
|
||||
|
||||
#
|
||||
# Joins with forms.
|
||||
|
17
sql/item.h
17
sql/item.h
@ -71,7 +71,24 @@ public:
|
||||
virtual double val_result() { return val(); }
|
||||
virtual longlong val_int_result() { return val_int(); }
|
||||
virtual String *str_result(String* tmp) { return val_str(tmp); }
|
||||
/* bit map of tables used by item */
|
||||
virtual table_map used_tables() const { return (table_map) 0L; }
|
||||
/*
|
||||
Return table map of tables that can't be NULL tables (tables that are
|
||||
used in a context where if they would contain a NULL row generated
|
||||
by a LEFT or RIGHT join, the item would not be true).
|
||||
This expression is used on WHERE item to determinate if a LEFT JOIN can be
|
||||
converted to a normal join.
|
||||
Generally this function should return used_tables() if the function
|
||||
would return null if any of the arguments are null
|
||||
As this is only used in the beginning of optimization, the value don't
|
||||
have to be updated in update_used_tables()
|
||||
*/
|
||||
virtual table_map not_null_tables() const { return used_tables(); }
|
||||
/*
|
||||
Returns true if this is a simple constant item like an integer, not
|
||||
a constant expression
|
||||
*/
|
||||
virtual bool basic_const_item() const { return 0; }
|
||||
virtual Item *new_item() { return 0; } /* Only for const items */
|
||||
virtual cond_result eq_cmp_result() const { return COND_OK; }
|
||||
|
@ -293,9 +293,11 @@ void Item_func_interval::fix_length_and_dec()
|
||||
}
|
||||
maybe_null=0; max_length=2;
|
||||
used_tables_cache|= item->used_tables();
|
||||
not_null_tables_cache&= item->not_null_tables();
|
||||
with_sum_func= with_sum_func || item->with_sum_func;
|
||||
}
|
||||
|
||||
|
||||
void Item_func_interval::split_sum_func(List<Item> &fields)
|
||||
{
|
||||
if (item->with_sum_func && item->type() != SUM_FUNC_ITEM)
|
||||
@ -1074,6 +1076,7 @@ void Item_func_in::fix_length_and_dec()
|
||||
maybe_null= item->maybe_null;
|
||||
max_length=2;
|
||||
used_tables_cache|= item->used_tables();
|
||||
not_null_tables_cache&= item->not_null_tables();
|
||||
const_item_cache&= item->const_item();
|
||||
}
|
||||
|
||||
@ -1174,14 +1177,21 @@ Item_cond::fix_fields(THD *thd,TABLE_LIST *tables)
|
||||
char buff[sizeof(char*)]; // Max local vars in function
|
||||
used_tables_cache=0;
|
||||
const_item_cache=0;
|
||||
/*
|
||||
and_table_cache is the value that Item_cond_or() returns for
|
||||
not_null_tables()
|
||||
*/
|
||||
and_tables_cache= ~(table_map) 0;
|
||||
|
||||
if (thd && check_stack_overrun(thd,buff))
|
||||
return 0; // Fatal error flag is set!
|
||||
while ((item=li++))
|
||||
{
|
||||
table_map tmp_table_map;
|
||||
while (item->type() == Item::COND_ITEM &&
|
||||
((Item_cond*) item)->functype() == functype())
|
||||
{ // Identical function
|
||||
|
||||
li.replace(((Item_cond*) item)->list);
|
||||
((Item_cond*) item)->list.empty();
|
||||
#ifdef DELETE_ITEMS
|
||||
@ -1194,8 +1204,11 @@ Item_cond::fix_fields(THD *thd,TABLE_LIST *tables)
|
||||
if (item->fix_fields(thd,tables))
|
||||
return 1; /* purecov: inspected */
|
||||
used_tables_cache|= item->used_tables();
|
||||
with_sum_func= with_sum_func || item->with_sum_func;
|
||||
tmp_table_map= item->not_null_tables();
|
||||
not_null_tables_cache|= tmp_table_map;
|
||||
and_tables_cache&= tmp_table_map;
|
||||
const_item_cache&= item->const_item();
|
||||
with_sum_func= with_sum_func || item->with_sum_func;
|
||||
if (item->maybe_null)
|
||||
maybe_null=1;
|
||||
}
|
||||
@ -1234,12 +1247,14 @@ Item_cond::used_tables() const
|
||||
return used_tables_cache;
|
||||
}
|
||||
|
||||
|
||||
void Item_cond::update_used_tables()
|
||||
{
|
||||
used_tables_cache=0;
|
||||
const_item_cache=1;
|
||||
List_iterator_fast<Item> li(list);
|
||||
Item *item;
|
||||
|
||||
used_tables_cache=0;
|
||||
const_item_cache=1;
|
||||
while ((item=li++))
|
||||
{
|
||||
item->update_used_tables();
|
||||
@ -1348,12 +1363,16 @@ Item *and_expressions(Item *a, Item *b, Item **org_item)
|
||||
{
|
||||
Item_cond *res;
|
||||
if ((res= new Item_cond_and(a, (Item*) b)))
|
||||
{
|
||||
res->used_tables_cache= a->used_tables() | b->used_tables();
|
||||
res->not_null_tables_cache= a->not_null_tables() | b->not_null_tables();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
if (((Item_cond_and*) a)->add((Item*) b))
|
||||
return 0;
|
||||
((Item_cond_and*) a)->used_tables_cache|= b->used_tables();
|
||||
((Item_cond_and*) a)->not_null_tables_cache|= b->not_null_tables();
|
||||
return a;
|
||||
}
|
||||
|
||||
@ -1489,6 +1508,8 @@ Item_func_regex::fix_fields(THD *thd,TABLE_LIST *tables)
|
||||
max_length=1; decimals=0;
|
||||
binary=args[0]->binary || args[1]->binary;
|
||||
used_tables_cache=args[0]->used_tables() | args[1]->used_tables();
|
||||
not_null_tables_cache= (args[0]->not_null_tables() |
|
||||
args[1]->not_null_tables());
|
||||
const_item_cache=args[0]->const_item() && args[1]->const_item();
|
||||
if (!regex_compiled && args[1]->const_item())
|
||||
{
|
||||
|
@ -204,7 +204,7 @@ public:
|
||||
enum Item_result result_type () const { return cached_result_type; }
|
||||
void fix_length_and_dec();
|
||||
const char *func_name() const { return "ifnull"; }
|
||||
unsigned int size_of() { return sizeof(*this);}
|
||||
table_map not_null_tables() const { return 0; }
|
||||
};
|
||||
|
||||
|
||||
@ -224,7 +224,7 @@ public:
|
||||
}
|
||||
void fix_length_and_dec();
|
||||
const char *func_name() const { return "if"; }
|
||||
unsigned int size_of() { return sizeof(*this);}
|
||||
table_map not_null_tables() const { return 0; }
|
||||
};
|
||||
|
||||
|
||||
@ -239,7 +239,7 @@ public:
|
||||
enum Item_result result_type () const { return cached_result_type; }
|
||||
void fix_length_and_dec();
|
||||
const char *func_name() const { return "nullif"; }
|
||||
unsigned int size_of() { return sizeof(*this);}
|
||||
table_map not_null_tables() const { return 0; }
|
||||
};
|
||||
|
||||
|
||||
@ -254,9 +254,10 @@ public:
|
||||
void fix_length_and_dec();
|
||||
enum Item_result result_type () const { return cached_result_type; }
|
||||
const char *func_name() const { return "coalesce"; }
|
||||
unsigned int size_of() { return sizeof(*this);}
|
||||
table_map not_null_tables() const { return 0; }
|
||||
};
|
||||
|
||||
|
||||
class Item_func_case :public Item_func
|
||||
{
|
||||
Item * first_expr, *else_expr;
|
||||
@ -270,6 +271,7 @@ public:
|
||||
String *val_str(String *);
|
||||
void fix_length_and_dec();
|
||||
void update_used_tables();
|
||||
table_map not_null_tables() const { return 0; }
|
||||
enum Item_result result_type () const { return cached_result_type; }
|
||||
const char *func_name() const { return "case"; }
|
||||
void print(String *str);
|
||||
@ -479,10 +481,12 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
table_map not_null_tables() const { return 0; }
|
||||
optimize_type select_optimize() const { return OPTIMIZE_NULL; }
|
||||
unsigned int size_of() { return sizeof(*this);}
|
||||
};
|
||||
|
||||
|
||||
class Item_func_isnotnull :public Item_bool_func
|
||||
{
|
||||
public:
|
||||
@ -495,9 +499,10 @@ public:
|
||||
}
|
||||
const char *func_name() const { return "isnotnull"; }
|
||||
optimize_type select_optimize() const { return OPTIMIZE_NULL; }
|
||||
unsigned int size_of() { return sizeof(*this);}
|
||||
table_map not_null_tables() const { return 0; }
|
||||
};
|
||||
|
||||
|
||||
class Item_func_like :public Item_bool_func2
|
||||
{
|
||||
char escape;
|
||||
@ -572,6 +577,8 @@ class Item_cond :public Item_bool_func
|
||||
protected:
|
||||
List<Item> list;
|
||||
bool abort_on_null;
|
||||
table_map and_tables_cache;
|
||||
|
||||
public:
|
||||
/* Item_cond() is only used to create top level items */
|
||||
Item_cond() : Item_bool_func(), abort_on_null(1) { const_item_cache=0; }
|
||||
@ -611,6 +618,7 @@ public:
|
||||
enum Functype functype() const { return COND_OR_FUNC; }
|
||||
longlong val_int();
|
||||
const char *func_name() const { return "or"; }
|
||||
table_map not_null_tables() const { return and_tables_cache; }
|
||||
};
|
||||
|
||||
|
||||
|
@ -61,7 +61,7 @@ Item_func::fix_fields(THD *thd,TABLE_LIST *tables)
|
||||
Item **arg,**arg_end;
|
||||
char buff[STACK_BUFF_ALLOC]; // Max argument in function
|
||||
binary=0;
|
||||
used_tables_cache=0;
|
||||
used_tables_cache= not_null_tables_cache= 0;
|
||||
const_item_cache=1;
|
||||
|
||||
if (thd && check_stack_overrun(thd,buff))
|
||||
@ -79,6 +79,7 @@ Item_func::fix_fields(THD *thd,TABLE_LIST *tables)
|
||||
binary=1;
|
||||
with_sum_func= with_sum_func || item->with_sum_func;
|
||||
used_tables_cache|= item->used_tables();
|
||||
not_null_tables_cache|= item->not_null_tables();
|
||||
const_item_cache&= item->const_item();
|
||||
}
|
||||
}
|
||||
@ -122,6 +123,13 @@ table_map Item_func::used_tables() const
|
||||
return used_tables_cache;
|
||||
}
|
||||
|
||||
|
||||
table_map Item_func::not_null_tables() const
|
||||
{
|
||||
return not_null_tables_cache;
|
||||
}
|
||||
|
||||
|
||||
void Item_func::print(String *str)
|
||||
{
|
||||
str->append(func_name());
|
||||
|
@ -34,7 +34,7 @@ protected:
|
||||
Item **args,*tmp_arg[2];
|
||||
public:
|
||||
uint arg_count;
|
||||
table_map used_tables_cache;
|
||||
table_map used_tables_cache, not_null_tables_cache;
|
||||
bool const_item_cache;
|
||||
enum Functype { UNKNOWN_FUNC,EQ_FUNC,EQUAL_FUNC,NE_FUNC,LT_FUNC,LE_FUNC,
|
||||
GE_FUNC,GT_FUNC,FT_FUNC,
|
||||
@ -97,6 +97,7 @@ public:
|
||||
bool fix_fields(THD *,struct st_table_list *);
|
||||
void make_field(Send_field *field);
|
||||
table_map used_tables() const;
|
||||
table_map not_null_tables() const;
|
||||
void update_used_tables();
|
||||
bool eq(const Item *item, bool binary_cmp) const;
|
||||
virtual optimize_type select_optimize() const { return OPTIMIZE_NONE; }
|
||||
@ -588,7 +589,8 @@ public:
|
||||
void split_sum_func(List<Item> &fields);
|
||||
void update_used_tables()
|
||||
{
|
||||
item->update_used_tables() ; Item_func::update_used_tables();
|
||||
item->update_used_tables();
|
||||
Item_func::update_used_tables();
|
||||
used_tables_cache|= item->used_tables();
|
||||
const_item_cache&= item->const_item();
|
||||
}
|
||||
@ -597,6 +599,7 @@ public:
|
||||
{
|
||||
maybe_null=0; max_length=3;
|
||||
used_tables_cache|= item->used_tables();
|
||||
not_null_tables_cache&= item->not_null_tables();
|
||||
const_item_cache&= item->const_item();
|
||||
with_sum_func= with_sum_func || item->with_sum_func;
|
||||
}
|
||||
@ -736,6 +739,7 @@ public:
|
||||
return res;
|
||||
}
|
||||
Item_result result_type () const { return udf.result_type(); }
|
||||
table_map not_null_tables() const { return 0; }
|
||||
unsigned int size_of() { return sizeof(*this);}
|
||||
};
|
||||
|
||||
@ -969,6 +973,7 @@ public:
|
||||
}
|
||||
enum Functype functype() const { return FT_FUNC; }
|
||||
void update_used_tables() {}
|
||||
table_map not_null_tables() const { return 0; }
|
||||
bool fix_fields(THD *thd,struct st_table_list *tlist);
|
||||
bool eq(const Item *, bool binary_cmp) const;
|
||||
longlong val_int() { return val()!=0.0; }
|
||||
|
@ -607,6 +607,7 @@ void Item_func_concat_ws::fix_length_and_dec()
|
||||
maybe_null=1;
|
||||
}
|
||||
used_tables_cache|= separator->used_tables();
|
||||
not_null_tables_cache&= separator->not_null_tables();
|
||||
const_item_cache&= separator->const_item();
|
||||
with_sum_func= with_sum_func || separator->with_sum_func;
|
||||
}
|
||||
@ -1510,6 +1511,7 @@ void Item_func_elt::fix_length_and_dec()
|
||||
maybe_null=1; // NULL if wrong first arg
|
||||
with_sum_func= with_sum_func || item->with_sum_func;
|
||||
used_tables_cache|= item->used_tables();
|
||||
not_null_tables_cache&= item->not_null_tables();
|
||||
const_item_cache&= item->const_item();
|
||||
}
|
||||
|
||||
@ -1592,6 +1594,7 @@ void Item_func_make_set::fix_length_and_dec()
|
||||
for (uint i=1 ; i < arg_count ; i++)
|
||||
max_length+=args[i]->max_length;
|
||||
used_tables_cache|= item->used_tables();
|
||||
not_null_tables_cache&= item->not_null_tables();
|
||||
const_item_cache&= item->const_item();
|
||||
with_sum_func= with_sum_func || item->with_sum_func;
|
||||
}
|
||||
|
@ -1903,7 +1903,7 @@ bool setup_tables(TABLE_LIST *tables)
|
||||
|
||||
table->used_fields=0;
|
||||
table->const_table=0;
|
||||
table->outer_join=table->null_row=0;
|
||||
table->null_row=0;
|
||||
table->status=STATUS_NO_RECORD;
|
||||
table->keys_in_use_for_query= table->keys_in_use;
|
||||
table->used_keys= table->keys_for_keyread;
|
||||
@ -2027,6 +2027,7 @@ insert_fields(THD *thd,TABLE_LIST *tables, const char *db_name,
|
||||
|
||||
int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds)
|
||||
{
|
||||
table_map not_null_tables= 0;
|
||||
DBUG_ENTER("setup_conds");
|
||||
thd->set_query_id=1;
|
||||
thd->cond_count=0;
|
||||
@ -2036,6 +2037,7 @@ int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds)
|
||||
thd->where="where clause";
|
||||
if ((*conds)->fix_fields(thd,tables))
|
||||
DBUG_RETURN(1);
|
||||
not_null_tables= (*conds)->not_null_tables();
|
||||
}
|
||||
|
||||
/* Check if we are using outer joins */
|
||||
@ -2049,9 +2051,15 @@ int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds)
|
||||
DBUG_RETURN(1);
|
||||
thd->cond_count++;
|
||||
|
||||
/* If it's a normal join, add the ON/USING expression to the WHERE */
|
||||
if (!table->outer_join)
|
||||
/*
|
||||
If it's a normal join or a LEFT JOIN which can be optimized away
|
||||
add the ON/USING expression to the WHERE
|
||||
*/
|
||||
if (!table->outer_join ||
|
||||
((table->table->map & not_null_tables) &&
|
||||
!(specialflag & SPECIAL_NO_NEW_FUNC)))
|
||||
{
|
||||
table->outer_join= 0;
|
||||
if (!(*conds=and_conds(*conds, table->on_expr)))
|
||||
DBUG_RETURN(1);
|
||||
table->on_expr=0;
|
||||
|
Reference in New Issue
Block a user