Bug #50995 Having clause on subquery result produces incorrect results.
Submitted: 8 Feb 2010 14:19 Modified: 20 Jun 2010 17:51
Reporter: Adam Trotter Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S1 (Critical)
Version:5.1.42, 5.1.43, 5.1.45-bzr OS:Any
Assigned to: Sergei Glukhov CPU Architecture:Any
Tags: regression

[8 Feb 2010 14:19] Adam Trotter
Description:
If a query is written with a subquery which references data in the outer query, and the outer query is using an index to resolve the where clause and has an order by and having clause against the results of the subquery, the having clause will produce incorrect results.

The issue was discovered on 5.1.42 running on CentOS, and is repeatable on 5.1.43 running on Windows 7.

If we ignore the index, we get the correct results.  Also, if a group by is added, or the order by is removed, we get correct results.

Finally, if we run the original query again, but, the data types on the temp table are char, we get correct results.  (I have tested, bigint, int, tinyint, and decimal types for the fields, they are all broken).  With a char data type, the issue seems to be fixed only because a temporary table is being used to resolve the query.

How to repeat:
drop temporary table if exists t1;
create temporary table t1
(
 id1 bigint unsigned not null,
 id2 bigint unsigned not null,
 index t1_id2_index(id2)
);

insert into t1 set id1=1, id2=1;
insert into t1 set id1=2, id2=1;
insert into t1 set id1=3, id2=1;

select bug.id1,
       (select 0
        from dual
        where bug.id1=bug.id1
       ) as amount
from t1 as bug
where bug.id2 = 1
having amount > 0
order by bug.id1;
[8 Feb 2010 14:19] Adam Trotter
Explain / Query Results

Attachment: mysql_bug_doc.sql (application/octet-stream, text), 7.78 KiB.

[8 Feb 2010 15:28] Valeriy Kravchuk
Verified just as described with recent 5.1.45 from bzr:

77-52-24-143:5.1 openxs$ bin/mysql -uroot test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.1.45-debug Source distribution

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> drop temporary table if exists t1;
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql> create temporary table t1
    -> (
    ->  id1 bigint unsigned not null,
    ->  id2 bigint unsigned not null,
    ->  index t1_id2_index(id2)
    -> );
Query OK, 0 rows affected (0.01 sec)

mysql> insert into t1 set id1=1, id2=1;
Query OK, 1 row affected (0.00 sec)

mysql> insert into t1 set id1=2, id2=1;
Query OK, 1 row affected (0.00 sec)

mysql> insert into t1 set id1=3, id2=1;
Query OK, 1 row affected (0.00 sec)

mysql> select bug.id1,
    ->        (select 0
    ->         from dual
    ->         where bug.id1=bug.id1
    ->        ) as amount
    -> from t1 as bug
    -> where bug.id2 = 1
    -> having amount > 0
    -> order by bug.id1;
+-----+--------+
| id1 | amount |
+-----+--------+
|   1 |      0 |
|   2 |      0 |
|   3 |      0 |
+-----+--------+
3 rows in set (0.01 sec)

mysql> explain select bug.id1,
    ->        (select 0
    ->         from dual
    ->         where bug.id1=bug.id1
    ->        ) as amount
    -> from t1 as bug
    -> where bug.id2 = 1
    -> having amount > 0
    -> order by bug.id1;
+----+--------------------+-------+------+---------------+--------------+---------+-------+------+-----------------------------+
| id | select_type        | table | type | possible_keys | key          | key_len | ref   | rows | Extra                       |
+----+--------------------+-------+------+---------------+--------------+---------+-------+------+-----------------------------+
|  1 | PRIMARY            | bug   | ref  | t1_id2_index  | t1_id2_index | 8       | const |    2 | Using where; Using filesort |
|  2 | DEPENDENT SUBQUERY | NULL  | NULL | NULL          | NULL         | NULL    | NULL  | NULL | No tables used              |
+----+--------------------+-------+------+---------------+--------------+---------+-------+------+-----------------------------+
2 rows in set (0.00 sec)

mysql> explain select bug.id1,        (select 0         from dual         where bug.id1=bug.id1        ) as amount from t1 as bug ignore index(t1_id2_index) where bug.id2 = 1 having amount > 0 order by bug.id1;
+----+--------------------+-------+------+---------------+------+---------+------+------+-----------------------------+
| id | select_type        | table | type | possible_keys | key  | key_len | ref  | rows | Extra                       |
+----+--------------------+-------+------+---------------+------+---------+------+------+-----------------------------+
|  1 | PRIMARY            | bug   | ALL  | NULL          | NULL | NULL    | NULL |    3 | Using where; Using filesort |
|  2 | DEPENDENT SUBQUERY | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used              |
+----+--------------------+-------+------+---------------+------+---------+------+------+-----------------------------+
2 rows in set (0.00 sec)

mysql> select bug.id1,        (select 0         from dual         where bug.id1=bug.id1        ) as amount from t1 as bug ignore index(t1_id2_index) where bug.id2 = 1 having amount > 0 order by bug.id1;
Empty set (0.01 sec)

5.0.91 is NOT affected, so this is a regression comparing to 5.0:

77-52-24-143:5.0 openxs$ bin/mysql -uroot test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.0.91-debug Source distribution

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> drop temporary table if exists t1;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> create temporary table t1
    -> (
    ->  id1 bigint unsigned not null,
    ->  id2 bigint unsigned not null,
    ->  index t1_id2_index(id2)
    -> );
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t1 set id1=1, id2=1;
Query OK, 1 row affected (0.00 sec)

mysql> insert into t1 set id1=2, id2=1;
Query OK, 1 row affected (0.00 sec)

mysql> insert into t1 set id1=3, id2=1;
Query OK, 1 row affected (0.00 sec)

mysql> select bug.id1,
    ->        (select 0
    ->         from dual
    ->         where bug.id1=bug.id1
    ->        ) as amount
    -> from t1 as bug
    -> where bug.id2 = 1
    -> having amount > 0
    -> order by bug.id1;
Empty set (0.00 sec)
[8 Feb 2010 15:34] Valeriy Kravchuk
5.1.37 is NOT affected also, so this is a recent regression!

77-52-24-143:mysql openxs$ bin/mysql -uroot test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.1.37 MySQL Community Server (GPL)

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create temporary table t1 (  id1 bigint unsigned not null,  id2 bigint unsigned not null,  index t1_id2_index(id2) );
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t1 set id1=1, id2=1;
Query OK, 1 row affected (0.00 sec)

mysql> insert into t1 set id1=2, id2=1;
Query OK, 1 row affected (0.00 sec)

mysql> insert into t1 set id1=3, id2=1;
Query OK, 1 row affected (0.00 sec)

mysql> select bug.id1,
    ->        (select 0
    ->         from dual
    ->         where bug.id1=bug.id1
    ->        ) as amount
    -> from t1 as bug
    -> where bug.id2 = 1
    -> having amount > 0
    -> order by bug.id1;
Empty set (0.00 sec)
[11 Feb 2010 14:51] Adam Trotter
5.1.41 does not have this issue.  I compiled a debug build of 5.1.42 and ultimately determined the problem was a change in sql_select.cc between 5.1.41 and 5.1.42.  I can't find the change documented in a bug fix anywhere.

In 5.1.41, lines 2125 to 2136 of sql_select.cc was:

	else					// This should never happen
	{
	  if (!(curr_table->select->cond=
		new Item_cond_and(curr_table->select->cond,
				  sort_table_cond)))
	    DBUG_VOID_RETURN;
	  /*
	    Item_cond_and do not need fix_fields for execution, its parameters
	    are fixed or do not need fix_fields, too
	  */
	  curr_table->select->cond->quick_fix_field();
	}

In 5.1.42, this was changed to:

	else
	{
	  if (!(curr_table->select->cond=
		new Item_cond_and(curr_table->select->cond,
				  sort_table_cond)))
	    DBUG_VOID_RETURN;
	  curr_table->select->cond->fix_fields(thd, 0);
	}

Reverting 

curr_table->select->cond->fix_fields(thd, 0);

to

curr_table->select->cond->quick_fix_field();

seems to fix the issue in both 5.1.42 and 5.1.43.
[16 Feb 2010 11:26] Sergei Glukhov
This problem is result of Bug#48052 fix.
[16 Feb 2010 15:05] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/100517

3344 Sergey Glukhov	2010-02-16
      Bug#50995 Having clause on subquery result produces incorrect results.
      This is the result of bug#48052 fix.
      The problem is that we can not use cond->fix_fields(thd, 0) here as
      it will break condition if it's AND condition. In our case fix_fields()
      cuts off 'having' condition.
      The fix:
      reverted fix_fields() to quick_fix_field() and
      updated cond->used_tables_cache with appropriate value.
     @ mysql-test/r/having.result
        test result
     @ mysql-test/t/having.test
        test case
     @ sql/sql_select.cc
        reverted fix_fields() to quick_fix_field() and
        updated cond->used_tables_cache with appropriate value.
[23 Feb 2010 13:16] Martin Hansson
Review sent by email.
[25 Feb 2010 12:43] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/101443

3354 Sergey Glukhov	2010-02-25
      Bug#50995 Having clause on subquery result produces incorrect results.
      The problem is that cond->fix_fields(thd, 0) breaks condition(cuts off 'having').
      The bug actually not in fix_fields but in the base_list_iterator::replace(&list) function.
      The base_list::last points to the 'next' member of the last element in list.
      This code in the base_list_iterator::replace:
       if ((list->last == &current->next) && (new_list.elements > 1))
         list->last= new_list.last;
      should extend the list when we actually append on list to another. But because
      the 'next' member is the first in the list_node structure the 'last' is the same
      as &current->next when current is one prior to the last element in list.
      All this leads to stripping off last part of AND/OR expression while optimizing
      AND-OR condition tree.
      Another probelm is fixed here is that we should not add an item to condition if
      this item is 0.
     @ mysql-test/r/having.result
        test result
     @ mysql-test/t/having.test
        test case
     @ sql/item_cmpfunc.h
        added ASSERT to make sure that we do not add zero item
     @ sql/sql_list.h
        The base_list::last points to the 'next' member of the last element in list.
        This code in the base_list_iterator::replace:
         if ((list->last == &current->next) && (new_list.elements > 1))
           list->last= new_list.last;
        should extend the list when we actually append on list to another. But because
        the 'next' member is the first in the list_node structure the 'last' is the same
        as &current->next when current is one prior to the last element in list.
        The fix is to move list_node *next from first position in struct.
     @ sql/sql_select.cc
        skip adding an item to condition if
        this item is 0 or empty list.
[26 Feb 2010 11:39] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/101597

3358 Sergey Glukhov	2010-02-26
      Bug#50995 Having clause on subquery result produces incorrect results.
      The problem is that cond->fix_fields(thd, 0) breaks
      condition(cuts off 'having'). The reason of that is
      that NULL valued Item pointer is present in the
      middle of Item list and it breaks the Item processing
      loop.
     @ mysql-test/r/having.result
        test case
     @ mysql-test/t/having.test
        test case
     @ sql/item_cmpfunc.h
        added ASSERT to make sure that we do not add NULL valued Item pointer
     @ sql/sql_select.cc
        skip adding an item to condition if Item pointer is NULL.
        skip adding a list to condition if this list is empty.
[1 Mar 2010 8:45] Bugs System
Pushed into 5.1.45 (revid:joro@sun.com-20100301083827-xnimmrjg6bh33o1o) (version source revid:sergey.glukhov@sun.com-20100226113925-mxwn1hfxe3l8khc4) (merge vers: 5.1.45) (pib:16)
[2 Mar 2010 14:33] Bugs System
Pushed into 6.0.14-alpha (revid:alik@sun.com-20100302142746-u1gxdf5yk2bjrq3e) (version source revid:alik@sun.com-20100301095421-4cz64ibem1h2quve) (merge vers: 6.0.14-alpha) (pib:16)
[2 Mar 2010 14:38] Bugs System
Pushed into 5.5.3-m2 (revid:alik@sun.com-20100302072233-t3uqgjzdukt1pyhe) (version source revid:alexey.kopytov@sun.com-20100226130631-8czhisohzf6jyo2x) (merge vers: 5.5.3-m2) (pib:16)
[2 Mar 2010 14:43] Bugs System
Pushed into mysql-next-mr (revid:alik@sun.com-20100302072432-k8xvfkgcggkwgi94) (version source revid:alik@sun.com-20100301093944-a4rvrmqqco6c0qao) (pib:16)
[8 Mar 2010 1:30] Paul DuBois
Noted in 5.1.45, 5.5.3, 6.0.14 changelogs.

Referring to a subquery result in a HAVING clause could produce
incorrect results.
[9 Apr 2010 14:52] Paul DuBois
Noted in 5.1.43sp1 changelog.
[12 Apr 2010 10:17] Bugs System
Pushed into 5.1.47 (revid:build@mysql.com-20100412101220-adgau1r18kwgthps) (version source revid:build@mysql.com-20100412101220-adgau1r18kwgthps) (merge vers: 5.1.47) (pib:16)
[28 May 2010 5:49] Bugs System
Pushed into mysql-next-mr (revid:alik@sun.com-20100524190136-egaq7e8zgkwb9aqi) (version source revid:vasil.dimov@oracle.com-20100331130613-8ja7n0vh36a80457) (pib:16)
[28 May 2010 6:19] Bugs System
Pushed into 6.0.14-alpha (revid:alik@sun.com-20100524190941-nuudpx60if25wsvx) (version source revid:alik@sun.com-20100422150658-fkhgnwwkyugtxrmu) (merge vers: 6.0.14-alpha) (pib:16)
[28 May 2010 6:46] Bugs System
Pushed into 5.5.5-m3 (revid:alik@sun.com-20100524185725-c8k5q7v60i5nix3t) (version source revid:alexey.kopytov@sun.com-20100429203306-tg0wz4y2xyx8edrl) (merge vers: 5.5.5-m3) (pib:16)
[30 May 2010 0:26] Paul DuBois
Already fixed.
[17 Jun 2010 11:50] Bugs System
Pushed into 5.1.47-ndb-7.0.16 (revid:martin.skold@mysql.com-20100617114014-bva0dy24yyd67697) (version source revid:vasil.dimov@oracle.com-20100331130613-8ja7n0vh36a80457) (merge vers: 5.1.46) (pib:16)
[17 Jun 2010 12:27] Bugs System
Pushed into 5.1.47-ndb-6.2.19 (revid:martin.skold@mysql.com-20100617115448-idrbic6gbki37h1c) (version source revid:vasil.dimov@oracle.com-20100331130613-8ja7n0vh36a80457) (merge vers: 5.1.46) (pib:16)
[17 Jun 2010 13:15] Bugs System
Pushed into 5.1.47-ndb-6.3.35 (revid:martin.skold@mysql.com-20100617114611-61aqbb52j752y116) (version source revid:vasil.dimov@oracle.com-20100331130613-8ja7n0vh36a80457) (merge vers: 5.1.46) (pib:16)