MySQL与SQLFiddle使用@变量结果不一致,@prev_value始终为NULL求排查
问题分析与解决方案
首先,咱们来拆解你遇到的问题:在本地MySQL中执行这条带用户变量的查询时,@prev_value始终显示为NULL,但在SQLFiddle里结果正常,核心原因是MySQL对SELECT子句中列的计算顺序是严格从左到右的,而你的查询列顺序刚好搞反了赋值和输出的时机。
为什么@prev_value一直是NULL?
看你的查询语句里的SELECT部分:
SELECT *, @row_num, @prev_value, @row_num := IF(@prev_value=bla.name,@row_num+1,1) as RowNumber, @prev_value:=bla.name
MySQL会按照从左到右的顺序计算每一列:
- 先输出
@prev_value——这时候@prev_value还是初始的NULL(因为后面的@prev_value:=bla.name还没执行) - 然后计算
RowNumber,这时候@prev_value依然是NULL,所以第一次判断@prev_value=bla.name不成立,RowNumber被设为1 - 最后才执行
@prev_value:=bla.name,把当前行的name赋值给变量,但这个赋值的结果不会影响前面已经输出的@prev_value列
而SQLFiddle可能因为执行计划或者版本细节(比如某些旧版本MySQL的优化行为不同),导致变量赋值的时机提前了,所以能正常显示。但这其实是不符合MySQL官方规范的——官方明确说明,SELECT子句中用户变量的赋值和引用顺序不保证,但实际大多数版本都是按左到右执行。
修复方案:调整列的顺序,先赋值再输出
把@prev_value:=bla.name的赋值操作放在@prev_value输出列的前面,这样当输出@prev_value时,变量已经被当前行的值赋值了:
SELECT *, @row_num, @prev_value:=bla.name AS current_name, -- 先完成变量赋值 @prev_value, -- 现在就能拿到当前行的name值了 @row_num := IF(@prev_value=bla.name,@row_num+1,1) as RowNumber FROM (SELECT * FROM TEST ORDER BY name, TEST.POINTS DESC, TEST.TIME ASC) bla, (SELECT @row_num := 1, @prev_value := NULL) x
或者,如果你不需要单独输出@prev_value,只是用来计算行号,也可以简化成这样(把变量赋值藏在计算逻辑里,避免提前输出未赋值的变量):
SELECT *, @row_num := IF(@prev_value = bla.name, @row_num + 1, 1) AS RowNumber, @prev_value := bla.name -- 仅用来更新变量,无需单独显示 FROM (SELECT * FROM TEST ORDER BY name, TEST.POINTS DESC, TEST.TIME ASC) bla, (SELECT @row_num := 1, @prev_value := NULL) x
另外,从MySQL 8.0开始,官方推荐使用ROW_NUMBER()窗口函数来实现这种分组行号的需求,比用户变量更可靠,语法也更清晰:
SELECT *, ROW_NUMBER() OVER (PARTITION BY name ORDER BY points DESC, time ASC) AS RowNumber FROM test;
这个写法完全不需要用户变量,也不会有顺序依赖的问题,是更现代的解决方案。
内容的提问来源于stack exchange,提问作者Nico68
相关产品推荐
相关产品推荐

