计算 30 天 bin 中的行数

Posted

技术标签:

【中文标题】计算 30 天 bin 中的行数【英文标题】:Count the number of rows in 30 day bins 【发布时间】:2012-12-14 22:18:09 【问题描述】:

我表中的每一行都有一个日期时间戳,我希望从现在开始查询数据库,以计算最近 30 天、前 30 天等有多少行。直到有一个 30 天的 bin 回到表的开头。

我已经通过使用 Python 并进行了多次查询成功地执行了此查询。但我几乎可以肯定,它可以在一个 mysql 查询中完成。

【问题讨论】:

用表结构器解释一些 inut 数据和输出数据 “30 天垃圾箱”是什么意思?据我所知,我认为我的回答应该没问题,但是如果没有一些示例数据,我无法确定它是否完全符合您的需要 当您说 滚动 30 天的箱子时,您是指不重叠的 30 天箱子还是重叠的 30 天箱子?例如,如果第一个 bin 是从 2012-12-02 到 2012-12-31,下一个 bin 是从 2012-11-02 到 2012-11-01,还是从 2012-12-01 到 2012- 12-30? 不重叠的 30 天垃圾箱。 还有其他人认为“滚动”意味着重叠的垃圾箱吗?我不愿意提出修改建议,因为我不确定这种理解是否具有普遍性,但我主要从金融领域知道这种方式,例如“滚动回报”。 【参考方案1】:

请您尝试以下方法:

SELECT Count(*)
FROM
  yourtable
where
  dateColumn between Now() and Now() - Interval 30 Day

它需要一些循环,以获得更好的答案来隔离所有 30 天的间隔。因为您还需要在表中的 min(Date) 和最后一个循环日期之间有 30 天的间隔:) 或者到至少另一个带有每个 30 天间隔日期的表,然后加入。

这里只是按每个日历月计算。不完全是您所需要的。

SELECT
  extract(month from datecolumn),
  count(*)
FROM
  yourtable
GROUP BY
  extract(month from datecolumn);

考虑到我的后一条评论和 Stefan 的评论,这是一个很长的代码,但结果正确。基于我自己的示例数据并与interval 兼容MYSQL。如果您需要与 SQL Server 一起使用,请使用DateADD 或等效函数。

SQLFIDDLE

样本数据:

ID_MAIN  FIELD1  FILTER
----------------------------------------
1        red     August, 05 2012 00:00:00+0000
2        blue    September, 15 2012 00:00:00+0000
3        pink    September, 20 2012 00:00:00+0000
4        blue    September, 27 2012 00:00:00+0000
5        blue    October, 02 2012 00:00:00+0000
6        blue    October, 16 2012 00:00:00+0000
7        blue    October, 22 2012 00:00:00+0000
8        pink    November, 12 2012 00:00:00+0000
9        pink    November, 28 2012 00:00:00+0000
10       pink    December, 01 2012 00:00:00+0000
11       pink    December, 08 2012 00:00:00+0000
12       pink    December, 22 2012 00:00:00+0000

查询:

set @i:= 0;
SELECT MIN(filter) INTO @mindt
FROM MAIN
;
select
  count(a.id_main),
  y.dateInterval,
  (y.dateInterval - interval 29 day) as lowerBound
from
  main a join (
    SELECT date_format(Now(),'%Y-%m-%d') as dateInterval
    from dual
    union all
    select x.dateInterval
    from (
      SELECT
        date_format(
          DATE(DATE_ADD(Now(),
                        INTERVAL @i:=@i-29 DAY)),'%Y-%m-%d') AS dateInterval
      FROM Main, (SELECT @i:=0) r
      HAVING datediff(dateInterval,@mindt) >= 30
      order by dateInterval desc) as x) as y
  on a.filter <= y.dateInterval 
     and a.filter > (y.dateInterval - interval 29 day)
group by y.dateInterval
order by y.dateInterval desc
;

结果:

COUNT(A.ID_MAIN)    DATEINTERVAL    LOWERBOUND
----------------------------------------------
2                   2012-12-30  2012-12-01
3                   2012-12-01  2012-11-02
2                   2012-11-02  2012-10-04
4                   2012-10-04  2012-09-05

【讨论】:

NOW() 应该替换为某个变量,因为 seanieb 还希望获得 60 天前、90 天前等 30 天的间隔。 谢谢@Stefan 也许Extract(month 可能是一个更好的主意;) 按月做是微不足道的。问题是关于滚动 30 天桶/间隔。 @seanieb 因为你还没有接受 Gamal 的回答我已经更新了我的帖子。 @bonCodigo 我没有否决您的答案,我只是对其进行了编辑以使其看起来更好,但我得到的间隔与您的不同:(0, '2012-12-01', '2012- 12-30'), (1, '2012-11-01', '2012-11-30'), (2, '2012-10-02', '2012-10-31') 等等计数不匹配,您正在计算 11 行但实际上有 12【参考方案2】:

试试这个:

SELECT 
  DATE_FORMAT(t1.`Date`, '%Y-%m-%d'),
  COUNT(t2.Id)
FROM 
(
  SELECT SUBDATE(CURDATE(), ID) `Date`
  FROM
  (
    SELECT  t2.digit * 10 + t1.digit + 1 AS id
    FROM         TEMP AS t1
    CROSS JOIN TEMP AS t2
  ) t 
  WHERE Id <= 30 
) t1
LEFT JOIN YOURTABLE t2 ON DATE(t1.`Date`) = DATE(t2.dateStampColumn)
GROUP BY t1.`Date`;

SQL Fiddle Demo

但是,您需要像这样创建一个临时表Temp

CREATE TABLE TEMP 
(Digit int);
INSERT INTO Temp VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9);

【讨论】:

使用 Date() 比使用 Date_Format() 更好,因此与日期范围比较时仍然没有问题 :) 例如 Date(t1.Date) :) @JW。是的,你是对的。我只是用它来格式化输出。 这不可能,根据 SQL Fiddle 结果。该数据集中显然有很多 30 天的窗口,其中包含许多匹配的行,但您永远不会显示超过两个。 @PhilFrost 感谢您的批评。你能告诉我这怎么从不显示超过两个吗?此查询将为您提供从现在开始的最后 30 天,每天都有 COUNT(id),它将显示此表中没有该日期条目的日期。试试这个updated fiddle,我刚刚更新了示例数据,您会发现日期2012-12-06 在示例数据中有6 个条目,因此如果这就是您的意思,它将有count = 6。 @MahmoudGamal:当我阅读这个问题时,它要求计算 30 天窗口中的行数,例如 2012-12-02 和 2012-12-31 之间,但您的查询正在计数只是每一天的行数。【参考方案3】:

如果你只需要计算至少有一行的间隔,你可以使用这个:

select
  datediff(curdate(), `date`) div 30 as block,
  count(*) as rows_per_block
from
  your_table
group by
  block

这也显示了开始日期和结束日期:

select
  datediff(curdate(), d) div 30 as block,
  date_sub(curdate(),
           INTERVAL (datediff(curdate(), `date`) div 30)*30 DAY) as start_block,
  date_sub(curdate(),
           INTERVAL (1+datediff(curdate(), `date`) div 30)*30-1 DAY) as end_block,
  count(*)
from your_table
group by block

但如果您还需要显示所有间隔,您可以使用这样的解决方案:

select
  num,
  date_sub(curdate(),
           INTERVAL (num+1)*30-1 DAY) as start_block,
  date_sub(curdate(),
           INTERVAL num*30 DAY) as end_block,
  count(`date`)
from
  numbers left join your_table
  on `date` between date_sub(curdate(),
           INTERVAL (num+1)*30-1 DAY)  and
  date_sub(curdate(),
           INTERVAL num*30 DAY)
where num<=(datediff(curdate(), (select min(`date`) from your_table) ) div 30)
group by num

但这要求您已经准备好numbers 表,或者请参阅fiddle here 以获取没有数字表的解决方案。

【讨论】:

我假设有人根据我最初的回答对我投了反对票。不过,后者的更新似乎更符合 OP 的需求。如果您发现我的回答有任何缺陷,请发表评论以改进:)【参考方案4】:

创建一个存储过程以按 30 天计算行数。

首先运行此过程,然后在要生成数据时调用相同的过程。

DELIMITER $$

DROP PROCEDURE IF EXISTS `sp_CountDataByDays`$$

CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_CountDataByDays`()
BEGIN 
    CREATE TEMPORARY TABLE daterange (
            id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 
            fromDate DATE, 
            toDate DATE, 
            PRIMARY KEY (`id`)
    ); 

    SELECT DATEDIFF(CURRENT_DATE(), dteCol) INTO @noOfDays 
    FROM yourTable ORDER BY dteCol LIMIT 1;

    SET @counter = -1;
    WHILE (@noOfDays > @counter) DO 
        INSERT daterange (toDate, fromDate) 
        VALUES (DATE_SUB(CURRENT_DATE(), INTERVAL @counter DAY), DATE_SUB(CURRENT_DATE(), INTERVAL @counter:=@counter + 30 DAY));
    END WHILE;

    SELECT d.id, d.fromdate, d.todate, COUNT(d.id) rowcnt 
    FROM daterange d  
    INNER JOIN yourTable a ON a.dteCol BETWEEN d.fromdate AND d.todate 
    GROUP BY d.id;

    DROP TABLE daterange;
END$$

DELIMITER ;

然后CALL程序:

CALL sp_CountDataByDays();

你得到如下输出:

ID  From Date   To Date     Row Count
1   2012-12-06  2013-01-05  17668
2   2012-11-06  2012-12-06  2845
3   2012-10-07  2012-11-06  2276
4   2012-09-07  2012-10-07  4561
5   2012-08-08  2012-09-07  5415
6   2012-07-09  2012-08-08  8954
7   2012-06-09  2012-07-09  4387
8   2012-05-10  2012-06-09  7911
9   2012-04-10  2012-05-10  7935
10  2012-03-11  2012-04-10  2566

【讨论】:

【参考方案5】:

没有存储过程、临时表、只有一个查询,以及给定日期列索引的高效执行计划:

select

  subdate(
    '2012-12-31',
    floor(dateDiff('2012-12-31', dateStampColumn) / 30) * 30 + 30 - 1
  ) as "period starting",

  subdate(
    '2012-12-31',
    floor(dateDiff('2012-12-31', dateStampColumn) / 30) * 30
  ) as "period ending",

  count(*)

from
  YOURTABLE
group by floor(dateDiff('2012-12-31', dateStampColumn) / 30);

这里发生的事情应该很明显,除了这个咒语:

floor(dateDiff('2012-12-31', dateStampColumn) / 30)

该表达式出现多次,其计算结果为 30 天前的周期数 dateStampColumn 是。 dateDiff 以天数为单位返回差值,除以 30 得到 30 天的时间段,然后将其全部提供给 floor() 以将其舍入为整数。一旦我们有了这个数字,我们就可以GROUP BY它,然后我们做一些数学运算,将这个数字转换回这个时期的开始和结束日期。

如果您愿意,请将 '2012-12-31' 替换为 now()。以下是一些示例数据:

CREATE TABLE YOURTABLE
    (`Id` int, `dateStampColumn` datetime);

INSERT INTO YOURTABLE
    (`Id`, `dateStampColumn`)
VALUES
    (1, '2012-10-15 02:00:00'),
    (1, '2012-10-17 02:00:00'),
    (1, '2012-10-30 02:00:00'),
    (1, '2012-10-31 02:00:00'),
    (1, '2012-11-01 02:00:00'),
    (1, '2012-11-02 02:00:00'),
    (1, '2012-11-18 02:00:00'),
    (1, '2012-11-19 02:00:00'),
    (1, '2012-11-21 02:00:00'),
    (1, '2012-11-25 02:00:00'),
    (1, '2012-11-25 02:00:00'),
    (1, '2012-11-26 02:00:00'),
    (1, '2012-11-26 02:00:00'),
    (1, '2012-11-24 02:00:00'),
    (1, '2012-11-23 02:00:00'),
    (1, '2012-11-28 02:00:00'),
    (1, '2012-11-29 02:00:00'),
    (1, '2012-11-30 02:00:00'),
    (1, '2012-12-01 02:00:00'),
    (1, '2012-12-02 02:00:00'),
    (1, '2012-12-15 02:00:00'),
    (1, '2012-12-17 02:00:00'),
    (1, '2012-12-18 02:00:00'),
    (1, '2012-12-19 02:00:00'),
    (1, '2012-12-21 02:00:00'),
    (1, '2012-12-25 02:00:00'),
    (1, '2012-12-25 02:00:00'),
    (1, '2012-12-26 02:00:00'),
    (1, '2012-12-26 02:00:00'),
    (1, '2012-12-24 02:00:00'),
    (1, '2012-12-23 02:00:00'),
    (1, '2012-12-31 02:00:00'),
    (1, '2012-12-30 02:00:00'),
    (1, '2012-12-28 02:00:00'),
    (1, '2012-12-28 02:00:00'),
    (1, '2012-12-30 02:00:00');

结果:

period starting     period ending   count(*)
2012-12-02          2012-12-31      17
2012-11-02          2012-12-01      14
2012-10-03          2012-11-01      5

周期端点包括在内。

在SQL Fiddle 中玩这个。

有一点潜在的愚蠢之处在于,任何 30 天的零匹配行都不会包含在结果中。如果您可以将其与期间表结合起来,则可以将其消除。然而,MySQL 没有像 PostgreSQL 的 generate_series() 这样的东西,所以你必须在你的应用程序中处理它或者尝试this clever hack。

【讨论】:

以上是关于计算 30 天 bin 中的行数的主要内容,如果未能解决你的问题,请参考以下文章

为不同时间段的每个相关记录计算 db 中的行数

如何显示行数为 1 或更多的行数中的所有记录?

计算本周 SQLite 数据库中的行数

计算终端输出中的行数

根据另一个表中的条件计算表中的行数

Node.js:计算文件中的行数