当需要统计连续时间的结果集的时候,比如说注册时间有一天没有用户注册,那么数据中便缺少了那一天的统计数据,这一天的数据可以通过代码解决,也可以使用建时间表的方法解决,当然也可以使用我下面的这个方法解决。
开始
最近同事需要一个根据时间聚合统计数据的查询结果集,并且最好是可以把没有记录的日期也可以补全,我搜索了一下只找到说建立一个日期表然后联表查询方法,但是多一张没用的表,太不优雅了,所以我打算自己试试写一条sql尝试将结果查询出来。
思考
由于需要连续的日期,我们想到可以通过MySQL的DATE_ADD或DATE_SUB来获得。那么函数中的type固定用DAY,但是expr我们怎么生成呢?这时候我们就可以设置一个变量不断递增来获取我们需要天数的日期,这样连续的日期就完成了,然后我们联合根据时间 group by 查询出来的结果集查询即可。
实践
有一张 user 表,有 user_id 和 create_time 两个字段,模拟查询每天注册人数,暂定查询今天起往前20天(示例简单为主,具体需求可各自扩展)
- 根据时间聚合查出结果集
1 | SELECT |
连续时间查询的结果集
1
2
3
4
5
6
7
8SET @i :=- 1;
SELECT
date_format( DATE_SUB( NOW( ), INTERVAL ( @i := @i + 1 ) DAY ), '%Y-%m-%d' ) AS `time`
FROM
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6) xc1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6) xc2
WHERE
@i <= 30sql 解释:(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6) 这里表示 6 次,FROM 两遍根据笛卡尔积生成 6 X 6 次,所以最大会连续到36,注意设置的 @i 查询的天数要小于等于 36 ,如果要查询40,则根据数量在后面其中一条后 UNION 7,则为 6 X 7 = 42 次,即可,其他数量自行修改。
联合 1 和 2 两个结果集的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27SET @i :=- 1;
SELECT
x.`time`,
-- IFNULL( d.create_time, x.`time` ) AS `create_time`,
IFNULL( d.total, 0 ) AS total
FROM
(
SELECT
date_format( DATE_SUB( NOW( ), INTERVAL ( @i := @i + 1 ) DAY ), '%Y-%m-%d' ) AS `time`
FROM
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6) xc1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6) xc2
WHERE
@i <= 30
) x
LEFT JOIN
(
SELECT
DATE( u.create_time ) AS create_time,
count( u.user_id ) AS total
FROM
`user` u
GROUP BY
DATE( u.create_time )
) d ON TO_DAYS( x.`time` ) = TO_DAYS( DATE( d.create_time ) )
ORDER BY
x.`time`
扩展
在最终语句中:
- NOW() 可以替换成你想要的任意时间;
- DATE_SUB 表示从 NOW() 时间起往前多少天的连续日期,如果需要往后的连续日期,改为 DATE_ADD 即可。
- 在获得连续日期的语句中,WHERE 条件中的
@i <= 20表示连续 20 天的日期,数值可自定义,选择自己想要查询的连续时间范围。
总结
确定好自己想要的结果形式,然后慢慢补全,直至获得最终的结果,利用分治法的思想,把一个大问题分解成几个小问题,一步一步解决,最终得出大问题的解。

