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

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

我有一个表,有两列,日期和分数。它最多有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的列,所以我有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 )
    
    …获取所需的任意多个值

  • 用于构建日期列表,根据NUMBERS.id值增加天数。将“2010-06-06”和“2010-06-14”替换为各自的开始和结束日期(但使用相同的格式,YYYY-MM-DD)-


    您可以通过使用日历表来实现这一点。这是一个您创建一次并填充日期范围的表(例如,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