在日期范围内按日期聚合数据,结果集中没有日期间隔

Posted

技术标签:

【中文标题】在日期范围内按日期聚合数据,结果集中没有日期间隔【英文标题】:Aggregating data by date in a date range without date gaps in result set 【发布时间】:2014-08-23 09:13:01 【问题描述】:

我有一张卖单的表格,我想列出两个日期之间每天的COUNT 卖单,不留日期间隔。

这是我目前拥有的:

SELECT COUNT(*) as Norders, DATE_FORMAT(date, "%M %e") as sdate 
FROM ORDERS 
WHERE date <= NOW() 
  AND date >= NOW() - INTERVAL 1 MONTH 
GROUP BY DAY(date) 
ORDER BY date ASC;

我得到的结果如下:

6     May 1
14    May 4
1     May 5
8     Jun 2
5     Jun 15

但我想得到的是:

6     May 1
0     May 2
0     May 3
14    May 4
1     May 5
0     May 6
0     May 7
0     May 8
.....
0     Jun 1
8     Jun 2
.....
5     Jun 15

这可能吗?

【问题讨论】:

你有一个包含所有日期的日历表吗? @GordonLinoff 不,我只有 ORDERs 表,其中仅包含订单创建日期的行。 @oliakaoil 是的,完全正确。这就是我想说的。 ORDERS 表不包含每一天的行,而只包含创建订单的日期。 【参考方案1】:

首先创建一个Calendar Table

SELECT coalesce(COUNT(O.*),0) as Norders, DATE_FORMAT(C.date, "%M %e") as sdate 
FROM Calendar C 
  LEFT JOIN ORDERS O ON C.date=O.date
WHERE O.date <= NOW() AND O.date >= NOW() - INTERVAL 1 MONTH 
GROUP BY DAY(date) 
ORDER BY date ASC;

【讨论】:

这需要有一个 Calendar 表,其中包含每年每个月的每一天的行,对吧? @alexandernst 是的,点击答案***.com/questions/5635594/…中发布的链接 效率不高,恕我直言。如果没有其他办法,我会把它作为 B 计划。 @alexandernst 我建议您对计数/数字/序列表的用途进行一些研究。问候【参考方案2】:

动态创建一系列日期并将其加入到您的订单表中:-

SELECT sub1.sdate, COUNT(ORDERS.id) as Norders
FROM
(
    SELECT DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i + tens.i * 10 + hundreds.i * 100 DAY), "%M %e") as sdate 
    FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)units
    CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)tens
    CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)hundreds
    WHERE DATE_SUB(NOW(), INTERVAL units.i + tens.i * 10 + hundreds.i * 100 DAY) BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()
) sub1
LEFT OUTER JOIN ORDERS
ON sub1.sdate = DATE_FORMAT(ORDERS.date, "%M %e")
GROUP BY sub1.sdate

这可以处理长达 1000 天的日期范围。

请注意,根据您用于日期的字段类型,它可以更容易地提高效率。

编辑 - 根据要求,获取每月的订单数量:-

SELECT aMonth, COUNT(ORDERS.id) as Norders
FROM
(
    SELECT DATE_FORMAT(DATE_SUB(NOW(), INTERVAL months.i MONTH), "%Y%m") as sdate, DATE_FORMAT(DATE_SUB(NOW(), INTERVAL months.i MONTH), "%M") as aMonth 
    FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11)months
    WHERE DATE_SUB(NOW(), INTERVAL months.i MONTH) BETWEEN DATE_SUB(NOW(), INTERVAL 12 MONTH) AND NOW()
) sub1
LEFT OUTER JOIN ORDERS
ON sub1.sdate = DATE_FORMAT(ORDERS.date, "%Y%m")
GROUP BY aMonth

【讨论】:

不错的技巧 :) 只是一个错误,它返回 1 而不是 0 在我没有任何卖单的日子。 不应该这样做,但它确实需要 COUNT(...) 来计算订单表中的一列(我假设 id),其值为 NULL(计数和一列没有订单时,名称将忽略空值)。如果您使用 COUNT(*),它将把没有订单的行计为 1。 啊,确实。你说得对。我只是写了 * 而不是 id。谢谢,效果很好! 就同一主题再问一个问题。如果我想显示过去 12 个月每月的订单 COUNT,我应该如何修改 sub1 子选择? 为此添加了建议。【参考方案3】:

您将需要生成一个虚拟(或物理)表,其中包含该范围内的每个日期。

这可以通过使用序列表来完成。

SELECT mintime + INTERVAL seq.seq DAY AS orderdate
  FROM (
        SELECT CURDATE() - INTERVAL 1 MONTH AS mintime,
               CURDATE() AS maxtime
          FROM obs
       ) AS minmax
  JOIN seq_0_to_999999 AS seq ON seq.seq < TIMESTAMPDIFF(DAY,mintime,maxtime)

然后,您将这个虚拟表加入到您的查询中,如下所示。

SELECT IFNULL(orders.Norders,0) AS Norders, /* show zero instead of null*/
       DATE_FORMAT(alldates.orderdate, "%M %e") as sdate 
  FROM (
        SELECT mintime + INTERVAL seq.seq DAY AS orderdate
          FROM (
                SELECT CURDATE() - INTERVAL 1 MONTH AS mintime,
                       CURDATE() AS maxtime
                  FROM obs
               ) AS minmax
          JOIN seq_0_to_999999 AS seq 
                        ON seq.seq < TIMESTAMPDIFF(DAY,mintime,maxtime)
       ) AS alldates
  LEFT JOIN (
    SELECT COUNT(*) as Norders, DATE(date) AS orderdate
      FROM ORDERS 
    WHERE date <= NOW() 
      AND date >= NOW() - INTERVAL 1 MONTH 
    GROUP BY DAY(date) 
       ) AS orders ON alldates.orderdate = orders.orderdate
ORDER BY alldates.orderdate ASC

请注意,您需要 LEFT JOIN,因此即使您的 ORDERS 表中没有数据,您的输出结果集中的行也会被保留。

你从哪里得到这个序列表seq_0_to_999999?你可以这样弄。

DROP TABLE IF EXISTS seq_0_to_9;
CREATE TABLE seq_0_to_9 AS
   SELECT 0 AS seq UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
    UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9;

DROP VIEW IF EXISTS seq_0_to_999;
CREATE VIEW seq_0_to_999 AS (
SELECT (a.seq + 10 * (b.seq + 10 * c.seq)) AS seq
  FROM seq_0_to_9 a
  JOIN seq_0_to_9 b
  JOIN seq_0_to_9 c
);

DROP VIEW IF EXISTS seq_0_to_999999;
CREATE VIEW seq_0_to_999999 AS (
SELECT (a.seq + (1000 * b.seq)) AS seq
  FROM seq_0_to_999 a
  JOIN seq_0_to_999 b
);

您可以在http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/找到所有这些的更详细解释

如果您使用 MariaDB 10+ 版,这些序列表是内置的。

【讨论】:

+1 指出 MariaDB 10+ 已内置序列表。

以上是关于在日期范围内按日期聚合数据,结果集中没有日期间隔的主要内容,如果未能解决你的问题,请参考以下文章

PySpark:在日期为字符串的范围内按日期字段过滤DataFrame

为日期范围按月返回多个总和

获取多个小时内按分钟时间范围内的 mySQL 行

根据 R 中的日期和小时以 15 分钟的间隔聚合数据

如何使用 Nest 客户端将日期范围应用于聚合查询

从具有特定日期范围的 SAS 数据集中删除行