MySQL中针对HAVING子句的临时自动增量列的奇怪行为

时间:2019-02-10 15:58:33

标签: mysql auto-increment temp-tables having

假设我们有一个名为 Test 的表,其中只有一列 col1 (Varchar(10))。 对于本示例,假定它具有以下数据:

col1
a
b
c
d

现在,我想在 col1 中选择数据并添加一个临时自动递增列,我们将其称为排名。以下查询可以完成此任务:

SELECT
(@cnt := @cnt + 1) AS Rank, Test.col1
FROM Test 
CROSS JOIN (SELECT @cnt := 0) AS tmp;

结果是

Rank col1
1    a
2    b
3    c
4    d

到目前为止没有问题。现在假设我们需要选择Rank大于1的行。我执行以下操作:

SELECT
(@cnt := @cnt + 1) AS Rank,  Test.col1
FROM Test 
CROSS JOIN (SELECT @cnt := 0) AS tmp
having Rank > 1;

结果变为

Rank col1
3    b
5    c
7    d

请注意,“排名”列中的第一项是3,而不是自然希望的2。有人可以指出原因吗?

  

为什么排名(临时自动递增)从2跳到3?

MySQL版本是5.6.32-78.1。

1 个答案:

答案 0 :(得分:1)

使用变量时,可能会发生很多不可预期的事情。其中一些列出在the manual中:

  

在SELECT语句中,仅在将每个选择表达式发送到客户端时才对其求值。这意味着在HAVING,GROUP BY或ORDER BY子句中,引用在选择表达式列表中分配了值的变量不能按预期工作

具体细节各不相同,但在您的情况下,您的增量将进行两次评估(一次在select中,一次评估为having),因此其基本行为类似于

SELECT (@cnt := @cnt + 1) AS Rank,  Test.col1
FROM Test 
CROSS JOIN (SELECT @cnt := 0) AS tmp
having (@cnt := @cnt + 1) > 1;

一种解决方法是强制MySQL预先评估表达式。

正确的解决方案(使用变量时应尽可能)是

select (
   SELECT (@cnt := @cnt + 1) AS Rank,  Test.col1
   FROM Test 
   CROSS JOIN (SELECT @cnt := 0) AS tmp
) x
where Rank > 1;

where Rank > 1(不带子查询)很可能是您原本打算执行的操作。

但是您也可以通过其他方式执行此操作。在您的示例中,您应该可以使用

SELECT (@cnt := @cnt + 1) AS Rank,  Test.col1
FROM Test 
CROSS JOIN (SELECT @cnt := 0) AS tmp
group by col1    
having Rank > 1;

除非col1是主键候选对象(例如,unique而不是null),否则MySQL需要实际评估表达式以执行group by。另一方面,如果col1 作为主键,MySQL可以优化group by,而您又回到了原始状态-如果这样做可以,则可能取决于您的MySQL版本,但是afaik MySQL 5.6应该这样做。

旁注:由于rank在MySQL 8中成为关键字,因此如果您打算升级,则不应将其用作别名。