MySQL如何在范围内填充缺失的日期?
我有一个表,有两列,日期和分数。它最多有30个条目,在过去的30天中每一天MySQL如何在范围内填充缺失的日期?,mysql,sql,recursive-query,gaps-and-islands,date-arithmetic,Mysql,Sql,Recursive Query,Gaps And Islands,Date Arithmetic,我有一个表,有两列,日期和分数。它最多有30个条目,在过去的30天中每一天 date score ----------------- 1.8.2010 19 2.8.2010 21 4.8.2010 14 7.8.2010 10 10.8.2010 14 我的问题是缺少一些日期-我想看看: date score ----------------- 1.8.2010 19 2.8.2010 21 3.8.2010 0 4.8.2010 14 5.8.2010
date score
-----------------
1.8.2010 19
2.8.2010 21
4.8.2010 14
7.8.2010 10
10.8.2010 14
我的问题是缺少一些日期-我想看看:
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
...
我需要从单个查询中得到:19,21,9,14,0,0,10,0,0,14。。。这意味着丢失的日期用0填充
我知道如何用服务器端语言获取所有的值和值,并在日期间迭代,并忽略空格。但这在mysql中是否可行,这样我就可以按日期对结果进行排序,并获得缺失的部分
编辑:在这个表中有另一个名为UserID的列,所以我有30000个用户,其中一些用户的分数在这个表中。如果日期<30天前,我会每天删除日期,因为我需要每个用户的最后30天分数。原因是我正在绘制过去30天的用户活动图表,为了绘制图表,我需要用逗号分隔30个值。所以我可以说,在查询中,getme这个USERID=10203活动,查询将得到30个分数,过去30天中每一天一个。我希望我现在更清楚了。MySQL没有递归功能,所以您只能使用数字表技巧-
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;
INSERT INTO `example`.`numbers`
( `id` )
VALUES
( NULL )
…获取所需的任意多个值您可以通过使用日历表来实现这一点。这是一个您创建一次并填充日期范围的表(例如,2000-2050年每天一个数据集;这取决于您的数据)。然后,您可以将表与日历表进行外部联接。如果表格中缺少日期,则返回0作为分数。我不喜欢其他答案,需要创建表格等等。此查询在没有辅助表的情况下可以有效地执行
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;
让我们来分析一下
SELECT
IF(score IS NULL, 0, score) AS score,
b.Days AS date
if将检测没有得分的天数并将其设置为0。b、 天数是您从当前日期开始选择的已配置天数,最多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
这个子查询是我在stackoverflow上看到的。它有效地生成一个从当前日期开始的过去1000天的列表。末尾WHERE子句中的间隔(当前为30)确定返回的天数;最大值为1000。这个查询可以很容易地修改为返回100年的日期,但1000年应该适合大多数情况
LEFT JOIN your_table
ON date = b.Days
ORDER BY b.Days;
这是将包含分数的表带入其中的部分。您可以与日期生成器查询中选择的日期范围进行比较,以便能够在需要时填写0(分数最初将设置为NULL
,因为它是左连接
;这在select语句中是固定的)。我也按日期订购,只是因为。这是首选项,您也可以按分数排序
在按订购之前,您可以轻松地将您在编辑中提到的用户信息加入到表中,以添加最后的要求
我希望这个版本的查询对某人有所帮助。感谢阅读。迈克尔·科纳德的回答很好,但我需要15分钟的时间间隔,时间必须从每15分钟的顶部开始:
SELECT a.Days
FROM (
SELECT FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60)) - INTERVAL 15 * (a.a + (10 * b.a) + (100 * c.a)) MINUTE 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
FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60))
这会将当前时间设置为上一轮15分钟:
SELECT a.Days
FROM (
SELECT FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60)) - INTERVAL 15 * (a.a + (10 * b.a) + (100 * c.a)) MINUTE 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
FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60))
这将通过15分钟的步骤消除时间:
- INTERVAL 15 * (a.a + (10 * b.a) + (100 * c.a)) MINUTE
如果有更简单的方法,请告诉我。自从这个问题被提出以来,时间已经过去了。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
:
date | score
:--------- | ----:
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
日期|分数
:--------- | ----:
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
用户可以通过插入从开始日期到今天直接使用
with recursive all_dates(dt) as (
-- anchor
select '2021-01-01' dt
union all
-- recursion with stop condition
INSERT IGNORE INTO mytable (date,score) VALUES (dt + interval 1 day ,0 ) where dt + interval 1 day <= curdate()
)
select * from all_dates
递归所有日期(dt)为(
--锚定
选择“2021-01-01”dt
联合所有
--带停止条件的递归
将忽略插入mytable(日期,分数)值(dt+间隔1天,0)如果dt+interval 1天是的,这是可能的,但你为什么要这样做?我仍然不明白。如果你可以用绘制图形的任何东西来填补这些空白,那么就不要从数据库中获取不必要的数据,这样可以节省一些开销。但是,我必须为USERID选择数据,例如,我得到20行日期,并进行评分,然后我必须使用服务器端语言(ASP)进行循环要检查是否有30天前的日期,如果不是0,则生成数据库值…这不是比从数据库中获取30个值并仅构造字符串更耗时吗?可能是重复的,谢谢。这是的快速操作,您建议不要使用这种方法并进行服务器端计算吗?@Jerry2:我的偏好就是在数据库中做尽可能多的数据处理,缺少真正涉及的表示内容。我不羡慕在应用程序代码中做这件事,只要它是一次数据库访问…为了使用索引,条件(WHERE和ON子句)可以重写为WHERE n.id
和在y.date=date_格式(x.ts,'%d.%m.%y')上左联接表y
只要我添加WHERE子句,例如WHERE'y.'score'=2
,所有填写的日期都不会显示出来anymore@SebaM:这是因为WHERE
子句是在联接之后应用的。因此,从数据的全范围左联接开始,然后
date | score
:--------- | ----:
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
with recursive all_dates(dt) as (
-- anchor
select '2021-01-01' dt
union all
-- recursion with stop condition
INSERT IGNORE INTO mytable (date,score) VALUES (dt + interval 1 day ,0 ) where dt + interval 1 day <= curdate()
)
select * from all_dates