Jer*_*ry2 59 mysql sql gaps-and-islands
我有一个包含2列,日期和分数的表格.它最多有30个条目,过去30天每个条目一个.
date score
-----------------
1.8.2010 19
2.8.2010 21
4.8.2010 14
7.8.2010 10
10.8.2010 14
Run Code Online (Sandbox Code Playgroud)
我的问题是缺少一些日期 - 我想看到:
date score
-----------------
1.8.2010 19
2.8.2010 21
3.8.2010 0
4.8.2010 14
5.8.2010 0
6.8.2010 0
7.8.2010 10
...
Run Code Online (Sandbox Code Playgroud)
我需要从单个查询得到:19,21,9,14,0,0,10,0,0,14 ......这意味着缺少的日期用0填充.
我知道如何获取所有的值,并在服务器端语言迭代日期和错过空白.但这是否可以在mysql中进行,因此我按日期对结果进行排序并获取缺失的部分.
编辑:在此表中有另一列名为UserID,因此我有30.000个用户,其中一些在此表中有分数.如果日期<30天以前,我每天都会删除日期,因为我需要每个用户最近30天的分数.原因是我正在制作过去30天内用户活动的图表,并绘制图表我需要用逗号分隔的30个值.所以我可以在查询中告诉我USERID = 10203活动,查询会得到30分,过去30天每一分.我希望我现在更清楚了.
OMG*_*ies 55
MySQL没有递归功能,所以你不得不使用NUMBERS表技巧 -
创建一个只保存递增数字的表 - 使用auto_increment很容易做到:
DROP TABLE IF EXISTS `example`.`numbers`;
CREATE TABLE `example`.`numbers` (
`id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Run Code Online (Sandbox Code Playgroud)使用以下方法填充表:
INSERT INTO `example`.`numbers`
( `id` )
VALUES
( NULL )
Run Code Online (Sandbox Code Playgroud)
......根据需要提供尽可能多的价值观.
使用DATE_ADD构建日期列表,根据NUMBERS.id值增加日期.将"2010-06-06"和"2010-06-14"替换为您的相应开始日期和结束日期(但使用相同的格式,YYYY-MM-DD) -
SELECT `x`.*
FROM (SELECT DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY)
FROM `numbers` `n`
WHERE DATE_ADD('2010-06-06', INTERVAL `n`.`id` -1 DAY) <= '2010-06-14' ) x
Run Code Online (Sandbox Code Playgroud)LEFT根据时间部分加入到您的数据表中:
SELECT `x`.`ts` AS `timestamp`,
COALESCE(`y`.`score`, 0) AS `cnt`
FROM (SELECT DATE_FORMAT(DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY), '%m/%d/%Y') AS `ts`
FROM `numbers` `n`
WHERE DATE_ADD('2010-06-06', INTERVAL `n`.`id` - 1 DAY) <= '2010-06-14') x
LEFT JOIN TABLE `y` ON STR_TO_DATE(`y`.`date`, '%d.%m.%Y') = `x`.`ts`
Run Code Online (Sandbox Code Playgroud)如果要保留日期格式,请使用DATE_FORMAT函数:
DATE_FORMAT(`x`.`ts`, '%d.%m.%Y') AS `timestamp`
Run Code Online (Sandbox Code Playgroud)
GMB*_*GMB 15
自从提出这个问题以来,时间已经过去了。MySQL 8.0 于 2018 年发布,增加了对递归公用表表达式的支持,这提供了一种优雅的、最先进的方法来解决这个问题。
以下查询可用于生成日期列表,例如 2010 年 8 月的前 15 天:
with recursive all_dates(dt) as (
-- anchor
select '2010-08-01' dt
union all
-- recursion with stop condition
select dt + interval 1 day from all_dates where dt + interval 1 day <= '2010-08-15'
)
select * from all_dates
Run Code Online (Sandbox Code Playgroud)
然后,您可以left join
将此结果集与您的表一起生成预期的输出:
with recursive all_dates(dt) as (
-- anchor
select '2010-08-01' dt
union all
-- recursion with stop condition
select dt + interval 1 day from all_dates where dt + interval 1 day <= '2010-08-15'
)
select d.dt date, coalesce(t.score, 0) score
from all_dates d
left join mytable t on t.date = d.dt
order by d.dt
Run Code Online (Sandbox Code Playgroud)
日期 | 分数 :--------- | ----: 2010-08-01 | 19 2010-08-02 | 21 2010-08-03 | 0 2010-08-04 | 14 2010-08-05 | 0 2010-08-06 | 0 2010-08-07 | 10 2010-08-08 | 0 2010-08-09 | 0 2010-08-10 | 14 2010-08-11 | 0 2010-08-12 | 0 2010-08-13 | 0 2010-08-14 | 0 2010-08-15 | 0
Sou*_*ink 14
您可以使用日历表来完成此操作.这是一个您创建一次并填充日期范围的表格(例如,2000-2050每天的一个数据集;这取决于您的数据).然后,您可以根据日历表创建表的外部联接.如果表中缺少日期,则返回0作为分数.
Mic*_*ard 12
我不是其他答案的粉丝,需要创建表格等等.没有帮助表,此查询可以有效地执行此操作.
SELECT
IF(score IS NULL, 0, score) AS score,
b.Days AS date
FROM
(SELECT a.Days
FROM (
SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Days
FROM (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS a
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS b
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS c
) a
WHERE a.Days >= curdate() - INTERVAL 30 DAY) b
LEFT JOIN your_table
ON date = b.Days
ORDER BY b.Days;
Run Code Online (Sandbox Code Playgroud)
所以让我们剖析一下.
SELECT
IF(score IS NULL, 0, score) AS score,
b.Days AS date
Run Code Online (Sandbox Code Playgroud)
if将检测没有得分的天数并将其设置为0. b.Days是您选择从当前日期获得的配置天数,最多为1000天.
(SELECT a.Days
FROM (
SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Days
FROM (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS a
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS b
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS c
) a
WHERE a.Days >= curdate() - INTERVAL 30 DAY) b
Run Code Online (Sandbox Code Playgroud)
这个子查询是我在stackoverflow上看到的.它可以有效地生成当前日期过去1000天的列表.最后WHERE子句中的间隔(当前为30)确定返回哪些天数; 最大值为1000.此查询可以很容易地修改为返回100年的日期值,但1000应该对大多数事情都有好处.
LEFT JOIN your_table
ON date = b.Days
ORDER BY b.Days;
Run Code Online (Sandbox Code Playgroud)
这是将包含分数的表格带入其中的部分.您可以与日期生成器查询中的所选日期范围进行比较,以便能够在需要时填写0(分数将设置为NULL
最初,因为它是a LEFT JOIN
;这在select语句中已修复).我也按日期订购,只是因为.这是首选,您也可以按分数订购.
在ORDER BY
您可以轻松加入关于您在编辑中提到的用户信息的表格之前,添加最后一个要求.
我希望这个版本的查询可以帮助某人.谢谢阅读.