获取两个日期之间的日期列表


91

使用标准的mysql函数可以编写查询,该查询将返回两个日期之间的天数列表。

例如给定2009-01-01和2009-01-13,它将返回一个具有以下值的列表:

 2009-01-01 
 2009-01-02 
 2009-01-03
 2009-01-04 
 2009-01-05
 2009-01-06
 2009-01-07
 2009-01-08 
 2009-01-09
 2009-01-10
 2009-01-11
 2009-01-12
 2009-01-13

编辑:看来我还不清楚。我要生成此列表。我在数据库中存储了值(按日期时间),但希望将它们在左外部联接中汇总到上述日期列表中(我希望这种联接的某些右侧在几天内会为null并将对此进行处理) )。


2
我认为最好的解决方案在答案stackoverflow.com/a/2157776/466677
Marek Gregor

Answers:


68

我将使用此存储过程将所需的间隔生成到名为time_intervals的临时表中,然后进行联接并使用temp time_intervals聚合数据表表进行。

该过程可以生成您在其中指定的所有不同类型的间隔:

call make_intervals('2009-01-01 00:00:00','2009-01-10 00:00:00',1,'DAY')
.
select * from time_intervals  
.
interval_start      interval_end        
------------------- ------------------- 
2009-01-01 00:00:00 2009-01-01 23:59:59 
2009-01-02 00:00:00 2009-01-02 23:59:59 
2009-01-03 00:00:00 2009-01-03 23:59:59 
2009-01-04 00:00:00 2009-01-04 23:59:59 
2009-01-05 00:00:00 2009-01-05 23:59:59 
2009-01-06 00:00:00 2009-01-06 23:59:59 
2009-01-07 00:00:00 2009-01-07 23:59:59 
2009-01-08 00:00:00 2009-01-08 23:59:59 
2009-01-09 00:00:00 2009-01-09 23:59:59 
.
call make_intervals('2009-01-01 00:00:00','2009-01-01 02:00:00',10,'MINUTE')
. 
select * from time_intervals
.  
interval_start      interval_end        
------------------- ------------------- 
2009-01-01 00:00:00 2009-01-01 00:09:59 
2009-01-01 00:10:00 2009-01-01 00:19:59 
2009-01-01 00:20:00 2009-01-01 00:29:59 
2009-01-01 00:30:00 2009-01-01 00:39:59 
2009-01-01 00:40:00 2009-01-01 00:49:59 
2009-01-01 00:50:00 2009-01-01 00:59:59 
2009-01-01 01:00:00 2009-01-01 01:09:59 
2009-01-01 01:10:00 2009-01-01 01:19:59 
2009-01-01 01:20:00 2009-01-01 01:29:59 
2009-01-01 01:30:00 2009-01-01 01:39:59 
2009-01-01 01:40:00 2009-01-01 01:49:59 
2009-01-01 01:50:00 2009-01-01 01:59:59 
.
I specified an interval_start and interval_end so you can aggregate the 
data timestamps with a "between interval_start and interval_end" type of JOIN.
.
Code for the proc:
.
-- drop procedure make_intervals
.
CREATE PROCEDURE make_intervals(startdate timestamp, enddate timestamp, intval integer, unitval varchar(10))
BEGIN
-- *************************************************************************
-- Procedure: make_intervals()
--    Author: Ron Savage
--      Date: 02/03/2009
--
-- Description:
-- This procedure creates a temporary table named time_intervals with the
-- interval_start and interval_end fields specifed from the startdate and
-- enddate arguments, at intervals of intval (unitval) size.
-- *************************************************************************
   declare thisDate timestamp;
   declare nextDate timestamp;
   set thisDate = startdate;

   -- *************************************************************************
   -- Drop / create the temp table
   -- *************************************************************************
   drop temporary table if exists time_intervals;
   create temporary table if not exists time_intervals
      (
      interval_start timestamp,
      interval_end timestamp
      );

   -- *************************************************************************
   -- Loop through the startdate adding each intval interval until enddate
   -- *************************************************************************
   repeat
      select
         case unitval
            when 'MICROSECOND' then timestampadd(MICROSECOND, intval, thisDate)
            when 'SECOND'      then timestampadd(SECOND, intval, thisDate)
            when 'MINUTE'      then timestampadd(MINUTE, intval, thisDate)
            when 'HOUR'        then timestampadd(HOUR, intval, thisDate)
            when 'DAY'         then timestampadd(DAY, intval, thisDate)
            when 'WEEK'        then timestampadd(WEEK, intval, thisDate)
            when 'MONTH'       then timestampadd(MONTH, intval, thisDate)
            when 'QUARTER'     then timestampadd(QUARTER, intval, thisDate)
            when 'YEAR'        then timestampadd(YEAR, intval, thisDate)
         end into nextDate;

      insert into time_intervals select thisDate, timestampadd(MICROSECOND, -1, nextDate);
      set thisDate = nextDate;
   until thisDate >= enddate
   end repeat;

 END;

这篇文章底部的类似示例数据场景,我为SQL Server构建了类似的功能。


4
我个人希望与PostgreSQL中生成的序列相似的东西。
菲利普·惠兰

如果只生成一次数据,则甚至可以使用永久表并使用此脚本来填写缺少的日期。
Bimal Poudel,2015年

我必须在创建过程语句之前更改定界符,以使其通过phpMyAdmin起作用(以避免声明语句上的语法错误)[code] DECLARE // [/ code]
Defkon1 '16

此解决方案非常强大且灵活,不幸的是在mariaDB上,我收到错误#1067-运行调用make_intervals('2009-01-01 00:00:00','2009-01-10时,'interval_end'的默认值无效00:00:00',1,'DAY'); 我使用的版本是5.7.28
shelbypereira

28

对于MSSQL,您可以使用它。非常快。

您可以将其包装在表值函数或存储的proc中,并在开始日期和结束日期中将其解析为变量。

DECLARE @startDate DATETIME
DECLARE @endDate DATETIME

SET @startDate = '2011-01-01'
SET @endDate = '2011-01-31';

WITH dates(Date) AS 
(
    SELECT @startdate as Date
    UNION ALL
    SELECT DATEADD(d,1,[Date])
    FROM dates 
    WHERE DATE < @enddate
)

SELECT Date
FROM dates
OPTION (MAXRECURSION 0)
GO

2
OPTION(MAXRECURSION 0)的用途是什么?
湿婆

14

BIRT报告也存在类似的问题,因为我们想在没有数据的那些日子进行报告。由于这些日期没有条目,所以对我们来说,最简单的解决方案是创建一个存储所有日期的简单表,并使用该表来获取该日期的范围或联接以获取零值。

我们每个月都会开展一项工作,以确保该表在未来5年内都可以使用。该表是这样创建的:

create table all_dates (
    dt date primary key
);

毫无疑问,对于不同的DBMS而言,存在魔术般的棘手方法,但是我们始终选择最简单的解决方案。该表的存储要求极少,这使查询变得更加简单和可移植。从性能的角度来看,这种解决方案几乎总是更好的,因为它不需要对数据进行逐行计算。

另一个选项(我们之前已经使用过)是确保表格中每个日期都有一个条目。我们定期清除表,并为不存在的日期和/或时间添加零条目。在您的情况下,这可能不是一个选择,它取决于存储的数据。

如果您真的认为保留它很麻烦all_dates填充表,则可以使用存储过程来返回包含这些日期的数据集。这几乎肯定会比较慢,因为您每次都必须计算范围,而不是仅仅从表中提取预先计算的数据。

但是,老实说,您可以将该表填充1000年,而不会出现任何严重的数据存储问题-例如,365,000个16字节日期(例如)加上索引重复该日期的索引再加上20%的安全开销,我大致估计为大约14M [365,000 * 16 * 2 * 1.2 = 14,016,000字节]),这是事物方案中的一个小表。


12

您可以像这样使用MySQL的用户变量

SET @num = -1;
SELECT DATE_ADD( '2009-01-01', interval @num := @num+1 day) AS date_sequence, 
your_table.* FROM your_table
WHERE your_table.other_column IS NOT NULL
HAVING DATE_ADD('2009-01-01', interval @num day) <= '2009-01-13'

@num为-1,因为您是首次使用它时将其添加。另外,您不能使用“ HAVING date_sequence”,因为这会使用户变量每行增加两次。


8

答案中借用一个想法,您可以设置一个从0到9的表,并使用该表生成日期列表。

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

select adddate('2009-01-01', numlist.id) as `date` from
(SELECT n1.i + n10.i*10 + n100.i*100 AS id
   FROM num n1 cross join num as n10 cross join num as n100) as numlist
where adddate('2009-01-01', numlist.id) <= '2009-01-13';

这将允许您生成最多1000个日期的列表。如果需要更大,可以向内部查询添加另一个交叉联接。


该解决方案效果更好。但是它与UNIX_TIMESTAMP()有问题-它给出的结果类似于1231185600.000000; 小数点后的毫秒部分;同时-SELECT UNIX_TIMESTAMP(ADDDATE('2009-01-01',0))AS date; 不会导致该部分。
Bimal Poudel

3

对于Access(或任何SQL语言)

  1. 创建一个包含2个字段的表,我们将其称为tempRunDates:--
    Fields fromDatetoDate
    -然后仅插入1条记录,其中包含开始日期和结束日期。

  2. 创建另一个表:Time_Day_Ref
    -将日期列表(在excel中创建列表很容易)导入此表。
    -在我的案例中Greg_Dt,字段名称为,表示公历
    -我列出了从2009年1月1日到2020年1月1日的清单。

  3. 运行查询:

    SELECT Time_Day_Ref.GREG_DT
    FROM tempRunDates, Time_Day_Ref
    WHERE Time_Day_Ref.greg_dt>=tempRunDates.fromDate And greg_dt<=tempRunDates.toDate;

简单!


2

通常情况下,您会使用一张辅助号码表,通常为此目的而保留一些辅助号码表:

SELECT *
FROM (
    SELECT DATEADD(d, number - 1, '2009-01-01') AS dt
    FROM Numbers
    WHERE number BETWEEN 1 AND DATEDIFF(d, '2009-01-01', '2009-01-13') + 1
) AS DateRange
LEFT JOIN YourStuff
    ON DateRange.dt = YourStuff.DateColumn

我见过表值函数等的变化。

您还可以保留永久的日期列表。我们将其存储在数据仓库中以及一天中的时间列表。



2
CREATE FUNCTION [dbo].[_DATES]
(
    @startDate DATETIME,
    @endDate DATETIME
)
RETURNS 
@DATES TABLE(
    DATE1 DATETIME
)
AS
BEGIN
    WHILE @startDate <= @endDate
    BEGIN 
        INSERT INTO @DATES (DATE1)
            SELECT @startDate   
    SELECT @startDate = DATEADD(d,1,@startDate) 
    END
RETURN
END

2

优雅的解决方案,在MariaDB> = 10.3和MySQL> = 8.0中使用新的递归(公用表表达式)功能。

WITH RECURSIVE t as (
    select '2019-01-01' as dt
  UNION
    SELECT DATE_ADD(t.dt, INTERVAL 1 DAY) FROM t WHERE DATE_ADD(t.dt, INTERVAL 1 DAY) <= '2019-04-30'
)
select * FROM t;

上面的表格返回了'2019-01-01'和'2019-04-30'之间的日期。


“选择'2019-01-01'作为dt”这个选择器是什么?是否可以只选择一个from和to字段?
米格尔·史蒂文斯

1

我们在HRMS系统中使用了此功能,您会发现它很有用

SELECT CAST(DAYNAME(daydate) as CHAR) as dayname,daydate
    FROM
    (select CAST((date_add('20110101', interval H.i*100 + T.i*10 + U.i day) )as DATE) as daydate
      from erp_integers as H
    cross
      join erp_integers as T
    cross
      join erp_integers as U
     where date_add('20110101', interval H.i*100 + T.i*10 + U.i day ) <= '20110228'
    order
        by daydate ASC
        )Days

1

此解决方案与MySQL 5.0一起使用
创建表- mytable
该架构并不重要。重要的是其中的行数。
因此,您可以仅保留INT类型的一列,其中包含10行,值-1至10。

SQL:

set @tempDate=date('2011-07-01') - interval 1 day;
select
date(@tempDate := (date(@tempDate) + interval 1 day)) as theDate
from mytable x,mytable y
group by theDate
having theDate <= '2011-07-31';

限制:上述查询返回的最大日期数为
(rows in mytable)*(rows in mytable) = 10*10 = 100.

您可以通过更改sql中的表单部分来增加此范围:
从mytable x,mytable y,mytable z
这样,范围be 10*10*10 =1000依此类推。


0

创建一个使用两个参数a_begin和a_end的存储过程。在其中创建一个名为t的临时表,声明一个变量d,将_begin赋给d,然后在d中运行一个WHILE循环INSERTd并调用ADDDATE函数以增加值d。终于SELECT * FROM t


实际上,我认为永久表对于执行速度(具有最小的存储要求)会更好。将日期预先计算到表格中并在需要时提取,比在每次需要时都创建范围要快。
paxdiablo,

@Pax,这取决于所获得的速度提高是否值得表维护。例如,如果报表生成花费3秒,而运行WHILE循环生成日期花费0.001秒,则我认为性能提升是可忽略的。克努斯:过早的优化是万恶之源。
尤金·横田

@ eed3si9n,过早的优化,是的,不是所有的优化:-)如果您的示例是正确的,那么是的,我同意您的观点,但是应该确定性。如果您按照我的建议将表维护生成1000年(一次性成本),表维护将不复存在,但是那几秒钟会累加起来。
paxdiablo,

0

我会用类似的东西:

DECLARE @DATEFROM AS DATETIME
DECLARE @DATETO AS DATETIME
DECLARE @HOLDER TABLE(DATE DATETIME)

SET @DATEFROM = '2010-08-10'
SET @DATETO = '2010-09-11'

INSERT INTO
    @HOLDER
        (DATE)
VALUES
    (@DATEFROM)

WHILE @DATEFROM < @DATETO
BEGIN

    SELECT @DATEFROM = DATEADD(D, 1, @DATEFROM)
    INSERT 
    INTO
        @HOLDER
            (DATE)
    VALUES
        (@DATEFROM)
END

SELECT 
    DATE
FROM
    @HOLDER

然后,@HOLDER变量表将所有日期都保留在这两个日期之间的每一天,准备好加入您的内心。


您好,我正在尝试使用代码,并将输出插入到我定义的DateTest表中,该表的Datefill类型为DATETIME类型...但是失败...您能否提供可以使用的代码?注意:在此处使用SQL Server谢谢:)
sys_debug,2011年

0

我已经为此奋斗了很长时间。由于这是我搜索解决方案时首次在Google上获得成功,因此,请允许我发布到目前为止的内容。

SET @d := '2011-09-01';
SELECT @d AS d, cast( @d := DATE_ADD( @d , INTERVAL 1 DAY ) AS DATE ) AS new_d
  FROM [yourTable]
  WHERE @d <= '2012-05-01';

[yourTable]数据库中的表替换。诀窍是,您选择的表中的行数必须> =您要返回的日期数。我尝试使用表占位符DUAL,但它只会返回一行。


1
有趣的方法。但是..这基本上不是Vihang上面的建议吗;)?
Leigh 2012年

0
select * from table_name where col_Date between '2011/02/25' AND DATEADD(s,-1,DATEADD(d,1,'2011/02/27'))

在这里,请先在当前endDate中添加一天,即2011-02-28 00:00:00,然后减去一秒钟,以将结束日期设为2011-02-27 23:59:59。这样,您可以获得给定间隔之间的所有日期。

输出:
2011/02/25
2011/02/26
2011/02/27


0
DELIMITER $$  
CREATE PROCEDURE popula_calendario_controle()
   BEGIN
      DECLARE a INT Default 0;
      DECLARE first_day_of_year DATE;
      set first_day_of_year = CONCAT(DATE_FORMAT(curdate(),'%Y'),'-01-01');
      one_by_one: LOOP
         IF dayofweek(adddate(first_day_of_year,a)) <> 1 THEN
            INSERT INTO calendario.controle VALUES(null,150,adddate(first_day_of_year,a),adddate(first_day_of_year,a),1);
         END IF;
         SET a=a+1;
         IF a=365 THEN
            LEAVE one_by_one;
         END IF;
      END LOOP one_by_one;
END $$

此过程将插入从年初到现在的所有日期,只需替换“开始”和“结束”的日期,即可开始使用!


IF a=365 THENleap年如何影响这条线?
斯图尔特

另外,150第9行的数字的意义是什么?该代码是“拍拍答案”,没有太多实际解释。
斯图尔特

0

我需要一个清单,其中包含两个日期之间的所有月份,以进行统计。这两个日期是订阅的开始日期和结束日期。 因此,该列表显示了所有月份以及每月的订阅量。

MySQL数据库

CREATE PROCEDURE `get_amount_subscription_per_month`()
BEGIN
   -- Select the ultimate start and enddate from subscribers
   select @startdate := min(DATE_FORMAT(a.startdate, "%Y-%m-01")), 
          @enddate := max(DATE_FORMAT(a.enddate, "%Y-%m-01")) + interval 1 MONTH
   from subscription a;

   -- Tmp table with all months (dates), you can always format them with DATE_FORMAT) 
   DROP TABLE IF EXISTS tmp_months;
   create temporary table tmp_months (
      year_month date,
      PRIMARY KEY (year_month)
   );


   set @tempDate=@startdate;  #- interval 1 MONTH;

   -- Insert every month in tmp table
   WHILE @tempDate <= @enddate DO
     insert into tmp_months (year_month) values (@tempDate);
     set @tempDate = (date(@tempDate) + interval 1 MONTH);
   END WHILE;

   -- All months
   select year_month from tmp_months;

   -- If you want the amount of subscription per month else leave it out
   select mnd.year_month, sum(subscription.amount) as subscription_amount
   from tmp_months mnd
   LEFT JOIN subscription ON mnd.year_month >= DATE_FORMAT(subscription.startdate, "%Y-%m-01") and mnd.year_month <= DATE_FORMAT(subscription.enddate, "%Y-%m-01")
   GROUP BY mnd.year_month;

 END

0

你可以用这个

SELECT CAST(cal.date_list AS DATE) day_year
FROM (
  SELECT SUBDATE('2019-01-01', INTERVAL 1 YEAR) + INTERVAL xc DAY AS date_list
  FROM (
        SELECT @xi:=@xi+1 as xc from
        (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc1,
        (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc2,
        (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc3,
        (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc4,
        (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) xc5,
        (SELECT @xi:=-1) xc0
    ) xxc1
) cal
WHERE cal.date_list BETWEEN '2019-01-01' AND '2019-12-31'
ORDER BY cal.date_list DESC;


-2

我在用 Server version: 5.7.11-log MySQL Community Server (GPL)

现在,我们将以简单的方式解决此问题。

我创建了一个名为“ datetable”的表

mysql> describe datetable;
+---------+---------+------+-----+---------+-------+
| Field   | Type    | Null | Key | Default | Extra |
+---------+---------+------+-----+---------+-------+
| colid   | int(11) | NO   | PRI | NULL    |       |
| coldate | date    | YES  |     | NULL    |       |
+---------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)

现在,wee将在其中看到插入的记录。

mysql> select * from datetable;
+-------+------------+
| colid | coldate    |
+-------+------------+
|   101 | 2015-01-01 |
|   102 | 2015-05-01 |
|   103 | 2016-01-01 |
+-------+------------+
3 rows in set (0.00 sec)

这是我们的查询以获取两个日期而不是两个日期内的记录。

mysql> select * from datetable where coldate > '2015-01-01' and coldate < '2016-01-01';
+-------+------------+
| colid | coldate    |
+-------+------------+
|   102 | 2015-05-01 |
+-------+------------+
1 row in set (0.00 sec)

希望这对很多人有帮助。


无需任何理由就可以使用此SQL,它可以正常工作。
ArifMustafa
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.