假设我们有一个名为 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。
答案 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中成为关键字,因此如果您打算升级,则不应将其用作别名。