我正在尝试编写一个查询,该查询将随着时间的推移检索累积结果,该查询仅针对每个相关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中是否可能发生这种情况,这是正确的解决方法吗?在此先感谢您,如果您之前已经回答过此问题,对不起,我还没有找到能完全满足我所要查找内容的东西。
答案 0 :(得分:2)
这是一个棘手的问题,但使用MySQL并非无法解决:-)可以使用Window functions with Frames以不太冗长的查询和可能有效的的方式解决 , MySQL version 8.0.2 and above中可用。但是,我们也可以使用CROSS JOIN
和Correlated 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
结果:
errors
我不喜欢使用两个相似的相关子查询来分别获取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()
答案 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;