【问题标题】:Create a row for every day in a date range?为日期范围内的每一天创建一行?
【发布时间】:2019-02-12 13:42:28
【问题描述】:

我有一张这样的桌子:

+----+---------+------------+
| id |  price  |    date    |
+----+---------+------------+
| 1  | 340     | 2018-09-02 |
| 2  | 325     | 2018-09-05 |
| 3  | 358     | 2018-09-08 |
+----+---------+------------+

我需要制作一个每天都有一行的视图。像这样的:

+----+---------+------------+
| id |  price  |    date    |
+----+---------+------------+
| 1  | 340     | 2018-09-02 |
| 1  | 340     | 2018-09-03 |
| 1  | 340     | 2018-09-04 |
| 2  | 325     | 2018-09-05 |
| 2  | 325     | 2018-09-06 |
| 2  | 325     | 2018-09-07 |
| 3  | 358     | 2018-09-08 |
+----+---------+------------+

我可以使用带有循环的 PHP (foreach) 并创建一个临时变量来保存以前的价格,直到有新的日期。

但我需要创建一个视图......所以我应该使用纯 SQL 来做......知道我该怎么做吗?

【问题讨论】:

  • 你用的是哪个版本的mysql?
  • 我用mariadb 10.2.14@trincot
  • 等等,为什么预期结果中的 id 与源数据不同?它是否需要是基于吨日期顺序的行号?
  • @RaymondNijland 好吧,我不关心 ids .. 最好不要重复。
  • 创建一个包含所有感兴趣日期的日历表或 cte。外连接。

标签: mysql sql mariadb


【解决方案1】:

您可以使用递归 CTE 来生成“间隙”中的记录。为避免“填充”最后一个日期之后的无限间隙,请首先获取源数据中的最大日期,并确保在递归中不要绕过该日期。

我已致电您的餐桌tbl

with recursive cte as (
    select     id, 
               price, 
               date, 
               (select max(date) date from tbl) mx
    from       tbl
    union all
    select     cte.id,
               cte.price, 
               date_add(cte.date, interval 1 day),
               cte.mx
    from       cte
    left join  tbl
           on  tbl.date = date_add(cte.date, interval 1 day)
    where      tbl.id is null
           and cte.date <> cte.mx
)
select   id, 
         price, 
         date
from     cte
order by 3;

demo with mysql 8

【讨论】:

  • 非常感谢.. 就两个问题,CTE 代表什么? (google 相信 Chronic Traumatic Encephalopathy 在这种情况下毫无意义)with 在 MySQL 中做了什么?
  • "Common Table Expression" -- 这确实是with 关键字之后的内容。注意:我不确定您的 MariaDB 版本是否支持 with recursive 语法。我希望它...
  • 您能解释一下order by 子句中的3 是什么吗?另一个数据集(超过 3 行)会怎样?
  • 这只是要排序的列号 (date)。与行无关。
【解决方案2】:

由于您使用的是 MariaDB,所以比较简单:

MariaDB [test]> SELECT  '2019-01-01' + INTERVAL seq-1 DAY  FROM seq_1_to_31;
+-----------------------------------+
| '2019-01-01' + INTERVAL seq-1 DAY |
+-----------------------------------+
| 2019-01-01                        |
| 2019-01-02                        |
| 2019-01-03                        |
| 2019-01-04                        |
| 2019-01-05                        |
| 2019-01-06                        |
(etc)

在这方面有一些变化,您可以生成大量日期,然后使用 WHERE 来切分您需要的日期。并将LEFT JOIN 与“左侧”的序列“派生表”一起使用。

在查询中使用类似上述的东西作为派生表。

【讨论】:

    【解决方案3】:

    这是一种无需分析函数即可工作的方法。这个答案使用日历表连接方法。下面的第一个 CTE 是其余查询所基于的基表。我们使用相关子查询在 CTE 中找到比当前日期更早的最近日期,该日期具有非 NULL 价格。这是找出那些从日历表中输入但未出现在原始数据集中的日期的idprice 值的基础。

    WITH cte AS (    
        SELECT cal.date, t.price, t.id
        FROM
        (
            SELECT '2018-09-02' AS date UNION ALL
            SELECT '2018-09-03' UNION ALL
            SELECT '2018-09-04' UNION ALL
            SELECT '2018-09-05' UNION ALL
            SELECT '2018-09-06' UNION ALL
            SELECT '2018-09-07' UNION ALL
            SELECT '2018-09-08'
        ) cal
        LEFT JOIN yourTable t
            ON cal.date = t.date
    ),
    cte2 AS (
        SELECT
            t1.date,
            t1.price,
            t1.id,
            (SELECT MAX(t2.date) FROM cte t2
             WHERE t2.date <= t1.date AND t2.price IS NOT NULL) AS nearest_date
        FROM cte t1
    )
    
    SELECT
        (SELECT t2.id FROM yourTable t2 WHERE t2.date = t1.nearest_date) id,
        (SELECT t2.price FROM yourTable t2 WHERE t2.date = t1.nearest_date) price,
        t1.date
    FROM cte2 t1
    ORDER BY
        t1.date;
    

    Demo

    注意:要在 MySQL 8+ 之前的版本上进行这项工作,您需要内联上述 CTE。这会导致冗长的代码,但它仍然可以工作。

    【讨论】:

    • 鉴于这是 CTE,UNION ALL 位似乎有点多余。
    • @Strawberry Trincot 的答案可能会被标记,但如果有人可能想要一个不依赖分析函数或递归的解决方案,那么我的答案是一个选项。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-01
    • 2020-03-29
    • 2013-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多