0

根据 MySQL 文档:

作为一般规则,您永远不应为用户变量赋值并在同一语句中读取该值。您可能会得到预期的结果,但这不能保证。

http://dev.mysql.com/doc/refman/5.6/en/user-variables.html

然而,在High Performance MySQL一书中,有几个使用这种策略来提高查询性能的例子。

以下是反模式吗?如果是,是否有更好的方法来编写查询,同时保持良好的性能?

set @last = null;
select tick, count-@last as delta, @last:=count from measurement;

为了澄清起见,我的目标是找出这一行和最后一行之间的区别。我的表在刻度上有一个主键,它是一个日期时间列。

更新:

在尝试了 Shlomi 的建议后,我恢复了原来的查询。事实证明,将 case 语句与聚合函数一起使用会产生意想不到的行为。参见例如:

case when (@delta := (max(measurement.count) - @lastCount)) AND 0 then null
when (@lastCount := measurement.count) AND 0 then null
else @delta end

似乎 mysql 在第一次遍历结果时评估不包含聚合函数的表达式,然后在第二次(分组)遍历时评估聚合表达式。它似乎在第二遍期间或之后评估 case 表达式,并在该评估中使用第一遍中的预先计算值。结果是第三行@delta 始终是@delta 的初始值(因为直到分组通过才发生分配)。我试图将一个组函数合并到 @delta 行中,但无法让它按预期运行。所以我最终回到了没有这个问题的原始查询。

我仍然希望听到更多关于如何更好地处理这样的查询的建议。

更新 2:

很抱歉没有对这个问题做出回应,直到现在我才有机会进一步调查。

使用 Shlomi 的解决方案,我似乎遇到了问题,因为我在读取 @last 变量时使用的是 group by 函数,但在设置它时却没有。我的代码看起来像这样:

CASE
    WHEN (@delta := count - @last) IS NULL THEN NULL
    WHEN (@last:= count ) IS NULL THEN NULL
    ELSE (CASE WHEN cumulative THEN @delta ELSE avg(count) END)
END AS delta

MySQL 似乎在第一遍处理不包含聚合函数的表达式,而在第二遍处理包含聚合函数的表达式。上面代码中的奇怪之处在于,即使cumulative计算结果为 true,MySQL 也必须看到子句AVG中的聚合函数,并决定在第二遍中ELSE计算整个内部表达式。CASE由于@delta在没有聚合函数的表达式中设置它似乎在第一次通过时被设置,并且在第二次通过时 MySQL 已经完成了对设置@delta和的行的评估@last

最终,我似乎通过在第一个表达式中包含聚合函数找到了解决方法。像这样的东西:

CASE
    WHEN (@delta := max(count) - @last) IS NULL THEN NULL
    WHEN (@last:= max(count) ) IS NULL THEN NULL
    ELSE (CASE WHEN cumulative THEN @delta ELSE avg(count) END)
END AS delta

由于我没有阅读源代码,因此我对 MySQL 所做的事情的理解纯粹基于测试和推测,但希望这将有助于其他可能遇到类似问题的人。

我将接受 Shlomi 的回答,因为它确实是一个很好的解决方案。请注意如何使用聚合函数。

4

1 回答 1

3

我已经深入研究了这个问题,并在上​​面写了一些改进。

我在这篇文章中提供了一个解决方案,它使用了可以预期顺序的函数。也考虑一下我去年的演讲。

诸如此类的构造和诸如此类的CASE函数COALESCE具有已知的潜在行为(至少在更改之前,对吧?)。

例如,CASE子句WHEN按定义顺序逐一检查条件。

考虑重写原始查询:

select 
  tick,
  CASE
    WHEN (@delta := count-@last) IS NULL THEN NULL
    WHEN (@last:=count ) IS NULL THEN NULL
    ELSE @delta
  END AS delta
from 
  measurement,
  (select @last := 0) s_init
;

CASE条款具有三个WHEN条件。它按顺序执行它们,直到遇到第一个成功的。我把它们写成前两个总是失败的。因此它执行第一个,然后转而执行第二个,最后返回第三个。总是

因此,我克服了期望求值顺序的问题,这是一个真实而真实的问题,当您开始添加更复杂的子句(例如GROUP BY,DISTINCT等)时最明显ORDER BY

最后一点,在结果集的第一行中,我的解决方案与您的解决方案不同-您的解决方案返回,而我的解决方案返回和NULL之间的增量。如果我使用过,我将需要以其他方式更改条件——确保它们在值上失败。0countNULLWHENNULL

于 2012-07-21T05:06:43.297 回答