我正在尝试编写一个查询,用于检索随时间推移的累积结果,该查询仅在每个相关 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 中可能发生吗?这是正确的方法吗?提前致谢,抱歉,如果之前已经回答过这个问题,我还没有找到任何可以完全实现我正在寻找的东西。
最佳答案
这是一个棘手的问题,但使用 MySQL 并非无法解决:-) 它可能可以使用 Window functions with Frames 以更简洁的查询和可能的高性能方式解决。 ,可用于 MySQL version 8.0.2 and above 。不过,我们也可以使用Correlated Subqueries来解决这个问题。 ,混合使用CROSS JOIN
和 Derived Tables 。我将分解该查询并尝试分步骤解释它。
由于您想要考虑前几个月的报告值,即使当月没有完成报告,因此我们的第一步是生成一个“主表”,其中基本上包含 user_id 的所有可能组合
和一个月的第一个日期
。这可以在查询本身内完成。
我们可以从 users
表中获取所有唯一的 user_id
值。并且,可以使用以下查询确定所有报告月份的开始日期。
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
现在,我们可以使用相关子查询来确定上面生成的表中每一行的错误
和警报
。我们将从 report_totals
表中查找 user_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
类似的子查询将用于确定警报
。
最后,我们将这个完整的结果集作为派生表,并对月份(该月的第一个日期)执行GROUP BY
,并计算SUM()
针对所有用户的警报
和错误
。
最终(且完整)查询如下所示:
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 |
<小时/>
编辑1:第一次优化
我不喜欢使用两个类似的相关子查询来分别获取错误
和警报
。但是,这是 MySQL 的一个限制,它不允许在此类子查询中使用多个操作数。因此,作为一种破解,我们可以 Concat()
使用一些分隔符将它们合并为单个字符串,例如 |
。这会将要使用的子查询减少为一个。
现在,在最外面的查询中,我们可以使用字符串函数,例如 Substring_Index()
和 Cast()
将相应的值提取为数字并相应地执行 Sum()
运算。
查询#2
SELECT dt.date,
Sum(Cast(Substring_index(dt.error_alerts, '|', 1) AS UNSIGNED)) AS
errors,
Sum(Cast(Substring_index(dt.error_alerts, '|', -1) AS UNSIGNED)) AS
alerts
FROM (SELECT all_mon.date,
u.id,
(SELECT Concat(rt1.errors, '|', 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 error_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 - 获取一段时间内的数据,按每个时间间隔的最新 related_id 分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53309843/