Mysql限制每组的行奇怪的结果

时间:2011-09-02 15:41:07

标签: mysql group-by having having-clause

我想获得每个symbolid的最新4个日期。我按如下方式调整了代码here

set @num := 0, @symbolid := '';
select symbolid, date,
@num := if(@symbolid = symbolid, @num + 1, 1) as row_number,
@symbolid := symbolid as dummy
from projections
group by symbolid, date desc
having row_number < 5

并获得以下结果:

symbolid                date   row_number   dummy       
1       '2011-09-01 00:00:00'       1       1
1       '2011-08-31 00:00:00'       3       1
1       '2011-08-30 00:00:00'       5       1
2       '2011-09-01 00:00:00'       1       2
2       '2011-08-31 00:00:00'       3       2
2       '2011-08-30 00:00:00'       5       2
3       '2011-09-01 00:00:00'       1       3
3       '2011-08-31 00:00:00'       3       3
3       '2011-08-30 00:00:00'       5       3
4       '2011-09-01 00:00:00'       1       4
...

显而易见的问题是,为什么每symbolid只能获得3行,为什么它们的编号为1,3,5?一些细节:

  1. 我尝试了强制索引而不是(如此处所示),并且两种方式都获得了相同的结果。
  2. 日期是正确的,即列表正确显示每symbolid的前3个日期,但row_number值已关闭
  3. 当我不使用“having”语句时,行号是正确的,即最近的日期是1,下一个最近的日期是2,等等。
  4. 显然row_number计算字段受“having”子句的影响,但我不知道如何修复它。

    我意识到我可以将“拥有”更改为“拥有row_number&lt; 7”(6与5相同),但它非常难看,并且想要知道如何使其“行为”。

3 个答案:

答案 0 :(得分:2)

我不是百分之百确定它为什么会这样(可能是因为逻辑SELECTORDER BY之前处理),但它应该按预期工作:

SELECT * 
FROM 
(
    select symbolid, date,
    @num := if(@symbolid = symbolid, @num + 1, 1) as row_number,
    @symbolid := symbolid as dummy
    from projections
    INNER JOIN (SELECT @symbolid:=0)c
    INNER JOIN (SELECT @num:=0)d
    group by symbolid, date desc

) a
WHERE row_number < 5

答案 1 :(得分:1)

用户定义的变量效果不佳(参考here

  

作为一般规则,您不应该为用户变量赋值并在同一语句中读取值。您可能会得到您期望的结果,但这不能保证。涉及用户变量的表达式的评估顺序是未定义的,可能会根据给定语句中包含的元素进行更改;此外,MySQL服务器版本之间的订单不保证相同。在SELECT @ a,@ a:= @ a + 1,...中,你可能会认为MySQL会首先评估@a然后再进行一次赋值。但是,更改语句(例如,通过添加GROUP BY,HAVING或ORDER BY子句)可能会导致MySQL选择具有不同评估顺序的执行计划。

这是我的建议

select symbolid, 
substring_index(group_concat(date order by date desc), ',', 4) as last_4_dates
from projections
group by symbolid

这种方法的缺点是它会将日期分组,
你需要在实际使用它之前爆炸。

答案 2 :(得分:0)

最终代码:

set @num := 0, @symbolid := '';
select d.* from
(
select symbolid, date,
  @num := if(@symbolid = symbolid, @num + 1, 1) as row_number,
  @symbolid := symbolid as dummy
from projections
order by symbolid, date desc
 ) d
where d.row_number < 5