我试图在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 ' |
+---------+------+--------------------------------------------------------------------+
答案 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
and
具有最高优先级,因此首先评估其左侧的参数,这是name
的值name
的值转换为布尔值,因为它必须参与布尔运算。与示例数据中一样,没有名称表示有效数字,此值为0 and
操作的结果,因此正确的参数未评估,因此不会执行赋值@rank := 1
。@prev_name
获得值0。在您的查询中,这意味着比较case name when @prev_name ...
几乎不可能是真的:第一次不是真的,因为@prev_name
是null
,之后它将始终为0。如果name
的值为“0”或“1”,则会出现例外情况:在这种情况下,您将获得排名增加。
现在反向表达式:
@rank := 1 and @prev_name := name
评估如下:
and
具有最高优先级,因此首先评估其左侧的参数,这是1 and
操作的结果,因此必须评估正确的参数@prev_name
的值为name
and
操作的结果为0 @rank
在这种情况下,@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功能,这使得排名成为更可靠的任务。