【发布时间】:2019-07-08 16:24:55
【问题描述】:
我最近尝试使用用户定义的变量从结果集中返回的最后行中捕获一些信息。
我的意思是,例如,如果我有一个从 'Aaron' 到 'Zzarx' 的名字列表,
SELECT @n:=Name FROM people ORDER BY Name;
SELECT @n;
第二个SELECT 应该返回“Zzarx”。
这是一个简单的例子。它按预期工作;变量赋值可靠地以与行发送到客户端相同的顺序发生,因此最后一个赋值对应于最后返回的行。
但是当查询更复杂时,似乎会发生奇怪的事情:
SELECT DISTINCT IFNULL(@n:=Name,'unknown') FROM people ORDER BY <some non-indexed expression> LIMIT 10;
SELECT @n;
在 MariaDB v10.3.16 上执行类似的操作,我得到了 @n 的最终值(来自第二个 SELECT)不对应于第一个 SELECT返回的任何行!。 (注意 Name 是 NOT NULL 列,所以 IFNULL() 实际上是多余的,但仍然是触发此行为所必需的)。
请注意,它似乎仅在以下所有条件成立时才会发生:
SELECT DISTINCT-
ORDER BY不能使用索引 - 变量赋值发生在某个表达式中
我的理论是:
-
SELECT DISTINCT强制对返回的列表达式进行早期评估。 -
ORDER BY (non-indexed expression)在评估列数据后强制执行显式排序操作。 - SQL 引擎足够智能,可以识别简单的
SELECT @var := (expression)模式并仅在将行发送到客户端时评估@var,但如果@var:=...赋值嵌入在更大的表达式中,则无法进行优化,就像我的示例中的IFNULL()一样。
然而,这一切都只是猜测。 用户定义变量的手册页在这方面并没有真正说明任何有用的信息(MySQL 和 MariaDB 都没有)。
在我看来,在多行查询中使用@variable 从最后返回的行中捕获某些内容是一个有用且可能很常见的技巧,但现在我不确定是否或何时可以依赖它。同样,对于我看到的许多行编号和其他巧妙的方案,它们在SELECT 的结果集中使用@variables。
SO 上的某个人是否有任何关于这应该如何工作的明确信息,具体来说,在什么条件下,行变量赋值表达式的评估顺序可以保证与行的实际顺序相对应退了吗?
...因为这似乎是一件很重要的事情!
另一个稍微不那么病态的例子:
说表 t 有 1000 行:
SET @n:=0;
SELECT @n:=@n+1 FROM t ORDER BY 1 DESC LIMIT 5;
SELECT @n;
返回的结果集是:
1000
999
998
997
996
和
1000
请注意,@n 的最终值与返回的最后一行不对应,并且确实给出了查询的语义,在这种情况下它不能。
【问题讨论】:
-
...因为我只知道有人会打电话给我:“什么意思是 Zzarx 不是一个名字?我喜欢一个叫 Zzarx 的好家伙!!!”