mysql - 获取一段时间内的数据,按每个时间间隔的最新 related_id 分组

标签 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 中可能发生吗?这是正确的方法吗?提前致谢,抱歉,如果之前已经回答过这个问题,我还没有找到任何可以完全实现我正在寻找的东西。

最佳答案

这是一个棘手的问题,但使用 MySQL 并非无法解决:-) 它可能可以使用 Window functions with Frames 以更简洁的查询和可能的高性能方式解决。 ,可用于 MySQL version 8.0.2 and above 。不过,我们也可以使用Correlated Subqueries来解决这个问题。 ,混合使用CROSS JOINDerived 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  

View on DB Fiddle

<小时/>

结果:

| 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     |

View on DB Fiddle

关于mysql - 获取一段时间内的数据,按每个时间间隔的最新 related_id 分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53309843/

相关文章:

Mysql选择最大值与阈值

php - 查询 m-d-Y 时显示 SQL 结果中的年份

mysql - 我的应用程序无法连接到 mysql,你能建议测试一下吗?

php - 使用 php 的基于 Ajax jquery 的登录

mysql - 如何在两个表之间的一对多关系中限制许多的数量?

sql - mysql UNION 与 IN 子句

mysql - 查找拥有作者 X 所写的所有书籍的人名

mysql - 尝试创建 $now 文件夹并复制创建的文件夹中的 .sql 文件

mysql - 通过消除表中的重复项来显示序列号(rownum)以及 mysql 中的数据?

MYSQL 更新 - 日期字段关闭 1 天?