我想创建一个时间线报告,为时间线中的每个日期显示数据集中最新N个数据点的移动平均值,该数据集包含一些度量和测量日期。我每天都有一张日历表来提供日期。我可以计算一个时间线来显示该日期之前的整体平均值,相当简单地使用相关子查询(实际情况比这复杂得多,但它基本上可以简化为此):
SELECT c.date
, ( SELECT AVERAGE(m.value)
FROM measures as m
WHERE m.measured_on_dt <= c.date
) as `average_to_date`
FROM calendar c
WHERE c.date between date1 AND date2 -- graph boundaries
ORDER BY c.date ASC
我花了几天时间阅读这篇文章并且我找不到任何好的解决方案。有人建议LIMIT可以在子查询中工作(LIMIT在子查询中支持当前版本的MySQL),但LIMIT适用于返回集,而不是进入聚合的行,因此添加它没有任何区别。
我也不能使用LIMIT编写非聚合SELECT,然后对其进行聚合,因为在FROM语句中不允许使用相关子查询。所以这(遗憾地)不起作用:
SELECT c.date
, SELECT AVERAGE(last_5.value)
FROM ( SELECT m.value
FROM measures as m
WHERE m.measured_on_dt <= c.date
ORDER BY m.measured_on_dt DESC
LIMIT 5
) as `last_5`
FROM calendar c
WHERE c.date between date1 AND date2 -- graph boundaries
ORDER BY c.date ASC
我认为我需要完全避免使用子查询方法,看看我是否使用带有用户变量的聪明连接/行编号技术然后聚合,但是当我正在研究时我认为我是问是否有人知道更好的方法?
更新: 好的,我有一个解决方案,我已经为这个例子简化了。它依赖于一些用户变量技巧来从日历日期向后编号。它还与日历表(而不是子查询)进行交叉产品,但这会产生令人遗憾的副作用,导致行编号技巧失败(用户变量在发送到客户端时进行评估,而不是在该行被评估)所以为了解决这个问题,我必须将查询嵌套一个级别,对结果进行排序,然后将行编号技巧应用于该集合,然后该集合起作用。
此查询仅返回有度量的日历日期,因此如果您想要整个时间轴,只需选择日历并将LEFT JOIN添加到此结果集。
set @day = 0;
set @num = 0;
set @LIMIT = 5;
SELECT date
, AVG(value) as recent_N_AVG
FROM
( SELECT *
, @num := if(@day = c.date, @num + 1, 1) as day_row_number
, @day := day as dummy
FROM
( SELECT c.full_date
, m.value
, m.measured_on_dt
FROM calendar c
JOIN measures as m
WHERE m.measured_on_dt <= c.full_date
AND c.full_date BETWEEN date1 AND date2
ORDER BY c.full_date ASC, measured_on_dt DESC
) as full_data
) as numbered
WHERE day_row_number <= @LIMIT
GROUP BY date
行编号技巧可以推广到更复杂的数据(我的测量有几个方面需要聚合)。
答案 0 :(得分:0)
如果您的时间表是连续的(每天1个值),您可以改善您的第一次尝试:
SELECT c.date,
( SELECT AVERAGE(m.value)
FROM measures as m
WHERE m.measured_on_dt
BETWEEN DATE_SUB(c.date, INTERVAL 5 day) AND c.date
) as `average_to_date`
FROM calendar c
WHERE c.date between date1 AND date2 -- graph boundaries
ORDER BY c.date ASC
如果你的时间轴上有洞,这将导致平均值少于5个值。