Bug #31255 User variables evaluated twice when used with control flow functions and join.
Submitted: 27 Sep 2007 18:28 Modified: 23 Jul 2008 4:59
Reporter: Benoit Heinrich Email Updates:
Status: Not a Bug Impact on me:
None 
Category:MySQL Server: General Severity:S1 (Critical)
Version:ALL OS:Any
Assigned to: CPU Architecture:Any
Tags: user variables control flow functions join twice

[27 Sep 2007 18:28] Benoit Heinrich
Description:
Hi,

When you use control flow functions and user variables in a select statement, then the user variables are interpreted twice.

!! This is true only if use joined tables !!

/Benoit

How to repeat:
/* First is to create the two tables */
create table a (ID int unsigned not null auto_increment, PRIMARY KEY (ID));
create table b (ID int unsigned not null auto_increment, IDA int unsigned not null, PRIMARY KEY (ID));

/* and to add some content */
insert into a values ();
insert into a values ();
insert into a values ();
insert into a values ();
insert into a values ();
insert into a values ();
insert into a values ();
insert into a values ();
insert into a values ();
insert into b (IDA) select * from a;
insert into b (IDA) select * from a;
insert into b (IDA) select * from a;
insert into b (IDA) select * from a;
insert into b (IDA) select * from a;
insert into b (IDA) select * from a;
insert into b (IDA) select * from a;
insert into b (IDA) select * from a;

/* Then the select with only one table works perfectly */
SET @count :=0;
select
    b.ID, @count
  from
    b 
  where
    case when (mod(b.ID,7) = 0 and @count < 10 and @count := @count+1) then 1 else 0 end;

/*
The result is the one we expect:

+----+--------+
| ID | @count |
+----+--------+
|  7 |      1 |
| 14 |      2 |
| 21 |      3 |
| 28 |      4 |
| 35 |      5 |
| 42 |      6 |
| 49 |      7 |
| 56 |      8 |
| 63 |      9 |
| 70 |     10 |
+----+--------+
10 rows in set (0.00 sec)
*/

/* Now we do the same but with the two table joined */

SET @count :=0;
select
    a.ID, @count
  from
    a inner join b on a.ID=b.IDA
  where
    case when (mod(b.ID,7) = 0 and @count < 10 and @count := @count+1) then 1 else 0 end;

/* 
Then here we see that is missing information, the @count seems to be interpreted twice for each row:

+----+--------+
| ID | @count |
+----+--------+
|  7 | 2      | 
|  5 | 4      | 
|  3 | 6      | 
|  1 | 8      | 
|  8 | 10     | 
+----+--------+
5 rows in set (0.00 sec)
*/

/* So just to be sure we haven't made an error somewhere with the join, I perform more or less the same statement except that I removed the 'case' */

SET @count :=0;
select
    a.ID, @count
  from
    a inner join b on a.ID=b.IDA
  where
    mod(b.ID,7) = 0 and @count < 10 and @count := @count+1;

/*
Here it works:

+----+--------+
| ID | @count |
+----+--------+
|  7 | 1      | 
|  5 | 2      | 
|  3 | 3      | 
|  1 | 4      | 
|  8 | 5      | 
|  6 | 6      | 
|  4 | 7      | 
|  2 | 8      | 
|  9 | 9      | 
|  7 | 10     | 
+----+--------+
10 rows in set (0.00 sec)
*/

/* I also tried the if function, just to be sure it does not come only from the case */
SET @count :=0;
select
    a.ID, @count
  from
    a inner join b on a.ID=b.IDA
  where
    if(mod(b.ID,7) = 0 and @count < 10 and @count := @count+1, 1, 0);

/*
And here we are, same problem:

+----+--------+
| ID | @count |
+----+--------+
|  7 | 2      | 
|  5 | 4      | 
|  3 | 6      | 
|  1 | 8      | 
|  8 | 10     | 
+----+--------+
5 rows in set (0.00 sec)
*/

Suggested fix:
Interpret only once the function expression per row.
[27 Sep 2007 20:33] Benoit Heinrich
Another interesting test to complete the report.

Because I'm trying to find a workaround for that kind of queries, I think I found one replacing the 'where' by a 'having':

SET @count :=0;
select
    a.ID, b.ID as bID, @count
  from
    a inner join b on a.ID=b.IDA
  having
    if(mod(bID,7) = 0 and @count < 10 and @count := @count+1, 1, 0);

/*
outputs:
+----+-----+--------+
| ID | bID | @count |
+----+-----+--------+
|  7 |   7 | 1      | 
|  5 |  14 | 2      | 
|  3 |  21 | 3      | 
|  1 |  28 | 4      | 
|  8 |  35 | 5      | 
|  6 |  42 | 6      | 
|  4 |  49 | 7      | 
|  2 |  56 | 8      | 
|  9 |  63 | 9      | 
|  7 |  70 | 10     | 
+----+-----+--------+
10 rows in set (0.00 sec)
*/

Hope it will help
/Benoit
[23 Jul 2008 4:59] Valeriy Kravchuk
Thank you for a problem report. I was able to repeat your findings on recent 5.1.26-rc and 5.1.28 versions. But according to the manual, http://dev.mysql.com/doc/refman/5.1/en/user-variables.html:

"The order of evaluation for user variables is undefined and may change based on the elements contained within a given query. In SELECT @a, @a := @a+1 ..., you might think that MySQL will evaluate @a first and then do an assignment second, but changing the query (for example, by adding a GROUP BY, HAVING, or ORDER BY clause) may change the order of evaluation.

The general rule is never to assign a value to a user variable in one part of a statement and use the same variable in some other part of the same statement. You might get the results you expect, but this is not guaranteed."

So, this is not a bug formally.