随时间获取数据,每个时间间隔按最新的related_id分组

时间:2018-11-14 22:43:51

标签: mysql

我正在尝试编写一个查询,该查询将随着时间的推移检索累积结果,该查询仅针对每个相关ID的每个时间间隔获取结果集的最新实例。

示例:

想象有一个用户表,每个用户都可以创建其中有问题的报告。问题汇总在report_totals表中,其中包含问题类别的总和。表格可能看起来像这样

users
id, email

reports
id, user_id, date

report_totals
id, report_id, errors, alerts

这是我要努力解决的问题,如果用户在当前时间间隔内未提交报告,则应使用前一个时间间隔的总和来回填该数据。假设我们有类似这样的数据

reports
1, 1, 2018-1-1
2, 2, 2018-1-1
3, 1, 2018-1-4
4, 1, 2018-2-1
5, 1, 2018-3-1
6, 2, 2018-3-1

report_totals
1, 1, 5, 5
2, 2, 3, 0
3, 3, 2, 0
4, 4, 10, 2
5, 5, 30, 15
6, 6, 1, 2

我想编写一个查询以返回如下所示的结果

date, errors, alerts
2018-1-1, 5, 0
2018-2-1, 13, 2
2018-3-1, 31, 17

报告间隔为1个月,因此它仅使用每个月的最新结果(对于每个用户)进行汇总,如果没有该用户的记录,则会从以前的间隔回填。

在MySQL中是否可能发生这种情况,这是正确的解决方法吗?在此先感谢您,如果您之前已经回答过此问题,对不起,我还没有找到能完全满足我所要查找内容的东西。

2 个答案:

答案 0 :(得分:2)

这是一个棘手的问题,但使用MySQL并非无法解决:-)可以使用Window functions with Frames以不太冗长的查询和可能有效的的方式解决 MySQL version 8.0.2 and above中可用。但是,我们也可以使用CROSS JOINCorrelated Subqueries的组合,使用Derived Tables解决此问题。我将细分查询并尝试逐步解释它。

由于您要考虑前几个月的报告值,即使在当月没有报告,所以我们的第一步将是生成一个“主表”,该表主要包含{{1}的所有可能组合}和user_id。这可以在查询本身内完成。

我们可以从first date of a month表中获取所有唯一的user_id值。而且,可以使用以下查询来确定所有报告月份的开始日期。

users

现在,有可能完全没有特定月份的报告。在这种情况下,您宁可使用主日历表。但是,出于实际目的,很少有一个月都没有报告的情况。

现在,我们可以使用SELECT MIN(DATE_FORMAT(date, '%Y-%m-01')) AS date, MONTH(date) AS month FROM reports GROUP BY month 获得所有可能的组合:

CROSS JOIN

现在,我们可以使用相关子查询为上面生成的表中的每一行确定(SELECT MIN(DATE_FORMAT(date, '%Y-%m-01')) AS date, MONTH(date) AS month FROM reports GROUP BY month) AS all_mon CROSS JOIN users AS u errors。我们将从alerts表中找到report_totals匹配的最新行,并且报告的 month 小于或等于当前的 month 强>。对于user_id,子查询如下:

errors

将使用类似的子查询来确定SELECT rt1.errors FROM report_totals AS rt1 JOIN reports AS r1 ON r1.id = rt1.report_id WHERE r1.user_id = u.id AND MONTH(r1.date) <= all_mon.month ORDER BY r1.date DESC LIMIT 1

最后,我们将把完整的结果集作为派生表,并在当月(该月的第一个日期)做一个alerts,并在{{1}上计算GROUP BY }和SUM()(针对所有用户)。

最终(和完整)查询如下:

alerts

View on DB Fiddle


结果:

errors

编辑1:首次优化

我不喜欢使用两个相似的相关子查询来分别获取SELECT dt.date, Sum(dt.errors) AS errors, Sum(dt.alerts) AS alerts FROM (SELECT all_mon.date, u.id, (SELECT rt1.errors FROM report_totals AS rt1 JOIN reports AS r1 ON r1.id = rt1.report_id WHERE r1.user_id = u.id AND Month(r1.date) <= all_mon.month ORDER BY r1.date DESC LIMIT 1) AS errors, (SELECT rt1.alerts FROM report_totals AS rt1 JOIN reports AS r1 ON r1.id = rt1.report_id WHERE r1.user_id = u.id AND Month(r1.date) <= all_mon.month ORDER BY r1.date DESC LIMIT 1) AS alerts FROM (SELECT Min(Date_format(date, '%Y-%m-01')) AS date, Month(date) AS month FROM reports GROUP BY month) AS all_mon CROSS JOIN users AS u) AS dt GROUP BY dt.date | date | errors | alerts | | ---------- | ------ | ------ | | 2018-01-01 | 5 | 0 | | 2018-02-01 | 13 | 2 | | 2018-03-01 | 31 | 17 | 。但是,这是MySQL的局限性,它不允许在此类子查询中使用多个操作数。因此,作为一种hack,我们可以使用errors之类的分隔符将它们Concat()合并为单个字符串。这样会将子查询减少为一个。

现在,在最外面的查询中,我们可以使用Substring_Index()Cast()之类的字符串函数将相应的值提取为数字并相应地执行alerts操作。

查询#2

|

结果

Sum()

View on DB Fiddle

答案 1 :(得分:0)

我认为以下查询将起作用:

select r.date,sum(rt.alerts),sum(rt.errors) from reports r join report_totals rt on r.id = rt.report_id group by r.date;