对mysql中的评估顺序感到困惑

时间:2017-05-19 04:05:10

标签: mysql logical-operators partition

我试图在mysql中编写自己的窗口函数,但发现了一些我不理解的东西,如下所示。

表员工有13行,

+----+------+--------+
| id | name | salary |
+----+------+--------+
|  1 | a    |    123 |
|  2 | a    |    234 |
|  4 | a    |    254 |
|  8 | a    |    724 |
|  9 | a    |    432 |
| 12 | a    |    123 |
|  3 | b    |    124 |
| 10 | b    |    333 |
| 11 | b    |     11 |
| 13 | b    |    628 |
|  5 | c    |    111 |
|  6 | c    |    777 |
|  7 | c    |    666 |
+----+------+--------+

因此,如果我使用以下查询,

select name, salary, 
      (case name when @prev_name  then  @rank := @rank + 1
       else @prev_name := name and @rank := 1 end) + 1 as Rank
from employee , (select @rank := 0,  @prev_name := null ) r  
order by name ;

结果

+------+--------+------+
| name | salary | Rank |
+------+--------+------+
| a    |    123 |    1 |
| a    |    234 |    2 |
| a    |    254 |    3 |
| a    |    724 |    4 |
| a    |    432 |    5 |
| a    |    123 |    6 |
| b    |    124 |    7 |
| b    |    333 |    8 |
| b    |     11 |    9 |
| b    |    628 |   10 |
| c    |    111 |   11 |
| c    |    777 |   12 |
| c    |    666 |   13 |
+------+--------+------+

有14个警告,

+---------+------+--------------------------------------------------------------------+
| Level   | Code | Message                                                            |
+---------+------+--------------------------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'c                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'c                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'c                             ' |
+---------+------+--------------------------------------------------------------------+

但是如果我改变else子句中的顺序,

select name, salary, 
      (case name when @prev_name  then  @rank := @rank + 1
       else @rank := 1 and @prev_name := name end) + 1 as Rank
from employee , (select @rank := 0,  @prev_name := null ) r  
order by name ;

输出不同,

+------+--------+------+
| name | salary | Rank |
+------+--------+------+
| a    |    123 |    1 |
| a    |    234 |    2 |
| a    |    254 |    3 |
| a    |    724 |    4 |
| a    |    432 |    5 |
| a    |    123 |    6 |
| b    |    124 |    1 |
| b    |    333 |    2 |
| b    |     11 |    3 |
| b    |    628 |    4 |
| c    |    111 |    1 |
| c    |    777 |    2 |
| c    |    666 |    3 |
+------+--------+------+

没有警告。

我的猜测是与逻辑运算符快捷方式和赋值运算符的返回值有关。但我在网上找不到任何有用的信息。

在尝试不同的方式后,这变得更加有趣。

因此,以下查询有时会给出完全不同的结果,

set @prev_name := null; set @rank := 0; 
select name, salary, 
       (case name when @prev_name  then  @rank := @rank + 1 else @prev_name := name and @rank := 1 end) + 1 as Rank
from employee order by name ;

+------+--------+------+
| name | salary | Rank |
+------+--------+------+
| a    |    123 |    1 |
| a    |    234 |    1 |
| a    |    254 |    1 |
| a    |    724 |    1 |
| a    |    432 |    1 |
| a    |    123 |    1 |
| b    |    124 |    1 |
| b    |    333 |    1 |
| b    |     11 |    1 |
| b    |    628 |    1 |
| c    |    111 |    1 |
| c    |    777 |    1 |
| c    |    666 |    1 |
+------+--------+------+

有13个警告,

+---------+------+--------------------------------------------------------------------+
| Level   | Code | Message                                                            |
+---------+------+--------------------------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'c                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'c                             ' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'c                             ' |
+---------+------+--------------------------------------------------------------------+

2 个答案:

答案 0 :(得分:0)

我相信我对你所看到的内容有一个解释。考虑您的第一个查询,它不会按预期生成每个组的行号:

select name, salary, 
      (case name when @prev_name  then  @rank := @rank + 1
       else @prev_name := name and @rank := 1 end) + 1 as Rank
from employee , (select @rank := 0,  @prev_name := null ) r  
order by name ;

我假设分配@prev_name := name @rank := 1 两者都返回false。如果这是真的,那么else条件的另一部分将不需要评估,因为短路,因为已知结果是错误的。对于第一个查询,这意味着赋值@prev_name := name将在每一行发生。如果我们还假设@prev_name := null赋值在第一次赋值之前发生,那么它将解释为什么第一个结果集只是整个表的行号。这种情况是因为整个表中的每一行都会出现@rank := @rank + 1。另一方面,在您的第二个查询中:

select name, salary, 
      (case name when @prev_name  then  @rank := @rank + 1
       else @rank := 1 and @prev_name := name end) + 1 as Rank
from employee , (select @rank := 0,  @prev_name := null ) r  
order by name ;

在这种情况下,@rank := 1再次返回false,但现在不会发生@prev_name的分配。

但是,我不会写你做的查询,因为它似乎让我感到困惑。相反,我建议您使用以下查询:

SET @row_number = NULL;
SET @emp_name = NULL;

SELECT
    @row_number:=CASE WHEN @emp_name = name THEN @row_number + 1 ELSE 1 END AS Rank,
    @emp_name := name,
    salary
FROM employee
ORDER BY name

答案 1 :(得分:0)

我无法重现您的第一个结果,但您应该考虑以下几点:

  • select子句表达式用记录提供的顺序不依赖于order by子句,因为the sorting only happens after all result records have been evaluated。因此,即使所有其他逻辑都正确,结果也会有所不同。

  • 作业(:=)运算符有the lowest priority。这意味着and:=之前执行。

  • 由于赋值仅在左边有变量时才有意义,因此在赋值发生之前,此变量永远不会参与另一个操作。所以这是正常优先规则的一种例外。

  • 对于逻辑运算符,执行快捷方式评估(文档中未找到)。

  • 当字符串值参与逻辑运算符时,它们将转换为布尔值(0或1)

考虑这些规则,请参阅以下评估方式:

@prev_name := name and @rank := 1
  1. and具有最高优先级,因此首先评估其左侧的参数,这是name的值
  2. name的值转换为布尔值,因为它必须参与布尔运算。与示例数据中一样,没有名称表示有效数字,此值为0
  3. 由于这足以知道and操作的结果,因此正确的参数评估,因此不会执行赋值@rank := 1
  4. 最后执行最左侧的分配,@prev_name获得值0。
  5. 在您的查询中,这意味着比较case name when @prev_name ...几乎不可能是真的:第一次不是真的,因为@prev_namenull,之后它将始终为0。如果name的值为“0”或“1”,则会出现例外情况:在这种情况下,您将获得排名增加。

    现在反向表达式:

    @rank := 1 and @prev_name := name
    

    评估如下:

    1. and具有最高优先级,因此首先评估其左侧的参数,这是1
    2. 由于现在还不足以知道and操作的结果,因此必须评估正确的参数
    3. 由于右侧是作业,因此必须先执行该作业,因此此时@prev_name的值为name
    4. 要使此值参与布尔运算,它将转换为一个数字,最可能为0(见上文):对于您给出的样本数据,它将为0。
    5. 这使and操作的结果为0
    6. 最后,将此值(0)分配给@rank
    7. 在这种情况下,@prev_name获得一个有用的值,如果记录集按照我们希望的顺序遍历,那么排名确实会增加。

      由于这种初始化,另一个问题可能出现:

      @prev_name := null
      

      这并不告诉MySql此变量具有哪种数据类型,因此它可能决定它是数字数据类型。这可能会导致奇怪的效果,@prev_name在整个查询中保持null,或者接受name值中第一个字符的字符代码。

      更好的方法

      为了更好地保护获取记录的顺序,您需要在子选择中获取它们,并使用order by子句提供它。

      只有在外部查询中,您才能依赖于特定的评估顺序 - 即使仍然没有文件证明。

      使用对数据类型毫无疑问的内容初始化变量,例如:

      @prev_name := ''
      

      您可以使用MySql if函数进行分配,同时返回一个值,如下所示:

      if(a := b, value, value)
      

      无论赋值返回为布尔结果,if都将返回 value

      综合所有这些,我建议这个问题:

      select name, salary, 
             @rank := if(name = @prev_name, @rank + 1, 
                         if(@prev_name := name, 1, 1)) as Rank
      from   (select name, salary from employee order by name) employee,
             (select @rank := 0, @prev_name := '') init
      order  by name;
      

      最后的评论

      其他数据库具有窗口/分析/ OLAP功能,这使得排名成为更可靠的任务。