MySQL如何填充范围内缺少的日期?

nqwrtyyt  于 2023-06-28  发布在  Mysql
关注(0)|答案(7)|浏览(108)

我有一个表,有两列,日期和分数。它最多有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  0
6.8.2010  0
7.8.2010  10
...

我需要从单个查询中得到:19,21,9,14,0,0,10,0,0,14...这意味着缺少的日期将用0填充。
我知道如何得到所有的值,并在服务器端语言迭代通过日期和失踪的空白。但这是否可能在mysql中做到,这样我就可以按日期对结果进行排序,并得到缺失的部分。
编辑:在这个表中有另一个名为UserID的列,所以我有30.000个用户,其中一些在这个表中得分。我每天删除的日期,如果日期< 30天前,因为我需要为每个用户的最后30天得分。原因是我在过去30天的用户活动的图形和绘制一个图表,我需要用逗号分隔的30个值。因此,我可以在查询中说“get me the USERID=10203 activity”,然后该查询将为我获得30个分数,过去30天中的每一天都有一个。我希望我现在更清楚了。

dw1jzc5e

dw1jzc5e1#

MySQL没有递归功能,所以你只能使用NUMBERS表技巧-
1.创建一个只包含递增数字的表-使用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;

1.使用以下命令填充表:

INSERT INTO `example`.`numbers`
  ( `id` )
VALUES
  ( NULL )

...您需要多少值都行。
1.使用DATE_ADD构造日期列表,根据www.example.com值增加天数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

1.基于时间部分左联接到数据表:

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`

如果要保持日期格式,请使用DATE_FORMAT函数:

DATE_FORMAT(`x`.`ts`, '%d.%m.%Y') AS `timestamp`
hjqgdpho

hjqgdpho2#

我不是一个球迷的其他答案,需要创建表等。此查询无需帮助表即可高效地执行此操作。

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,因为它是LEFT JOIN;这在select语句中是固定的)。我也是按日期订购的,只是因为。这是偏好,您也可以按分数排序。
ORDER BY之前,您可以轻松地与您在编辑时提到的用户信息表连接,以添加最后一个要求。
我希望这个版本的查询可以帮助到一些人。感谢阅读。

jobtbby3

jobtbby33#

自从这个问题被提出以来,时间已经过去了。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 < '2010-08-15'
)
select * from all_dates order by dt

然后你可以用你的表left join这个结果集来生成预期的输出:

with recursive all_dates(dt) as (
    select '2010-08-01' dt
    union all 
    select dt + interval 1 day from all_dates where dt < '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

Demo on DB Fiddle

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

请注意,非常容易使递归CTE适应于其他间隔或时段。举个例子,假设我们想要在2010年8月1日从4 AM到8 AM每15分钟一行;我们可以做到:

with recursive all_dates(dt) as (
    select '2010-08-01 04:00:00' dt
    union all 
    select dt + interval 15 minute from all_dates where dt < '2010-08-01 08:00:00'
)
...
mepcadol

mepcadol4#

您可以使用日历表来完成此操作。这是一个你创建一次并填充一个日期范围的表(例如2000-2050年每天一个数据集;这取决于您的数据)。然后,您可以对日历表进行表的外部连接。如果表中缺少日期,则返回0作为分数。

yqlxgs2m

yqlxgs2m5#

Michael Conard的回答很棒,但我需要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

这会将当前时间设置为上一轮的第15分钟:

FROM_UNIXTIME( FLOOR( UNIX_TIMESTAMP() / (15 * 60) ) * (15 * 60))

这将以15分钟的步长删除时间:

- INTERVAL 15 * (a.a + (10 * b.a) + (100 * c.a)) MINUTE

如果有更简单的方法,请告诉我。

sczxawaw

sczxawaw6#

您可以通过插入从开始日期到今天的用户直接

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

2skhul337#

如果你是加入这个日历表,那么你可能会使用这个。这可以帮助您生成表中最小和最大日期之间的所有缺失日期沿着表中的其他列。

select c.calendar_date,fact.column3,fact.column4,fact.column5 
from calendar c 
join (SELECT min(your_date) as mindt, max(your_date) as maxdt,column3,column4,column5
from your_table         
group by 3,4,5
) fact
on c.calendar_date between fact.mindt and fact.maxdt

相关问题