根据MySQL文档:
作为一般规则,您不应该为用户变量赋值并在同一语句中读取值。你可能会得到 你期望的结果,但这不能保证。
然而,在 High Perfomance MySQL 这本书中,有几个使用这种策略来提高查询性能的例子。
以下是反模式吗?如果有,是否有更好的方法来编写查询,同时保持良好的性能?
set @last = null;
select tick, count-@last as delta, @last:=count from measurement;
为了澄清,我的目标是找到这一行和最后一行之间的区别。我的表在tick上有一个主键,它是一个日期时间列。
更新
在尝试了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在第一次通过结果时计算不包含聚合函数的表达式,然后在第二次(分组)传递上计算聚合表达式。它似乎在第二次通过期间或之后评估案例表达,并使用该评估中第一次通过的预先计算的值。结果是第三行@delta始终是@delta的初始值(因为在分组传递之前不会进行赋值)。我尝试将组函数合并到@delta的行中,但无法使其按预期运行。所以我最终回到原始查询时没有遇到这个问题。
我仍然希望听到有关如何更好地处理此类查询的更多建议。
更新2:
很抱歉由于对这个问题缺乏回应,我到目前为止还没有机会进一步调查。
使用Shlomi的解决方案看起来我遇到了问题,因为我在读取@last变量时使用的是按功能分组,但是在设置它时却没有。我的代码看起来像这样:
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
和{{1 }}
最终我似乎通过在第一个表达式中包含聚合函数来找到修复。像这样:
@last
我对MySQL正在做的事情的理解纯粹基于测试和推测,因为我没有阅读源代码,但希望这将有助于其他可能遇到类似问题的人。
我将接受Shlomi的回答,因为它确实是一个很好的解决方案。请注意如何使用聚合函数。
答案 0 :(得分:3)
我已经深入研究了这个问题,并对上述内容进行了一些改进。
我在this post中提供了一个解决方案,它使用了可以预期顺序的函数。去年也考虑my talk。
诸如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
,我的解决方案返回0
和{{1}之间的差值}}。如果我使用count
,我需要以其他方式更改NULL
条件 - 确保他们<{1}}值 。