두 날짜 사이의 날짜 목록 가져 오기


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

답변:


68

이 저장 프로 시저를 사용하여 time_intervals 라는 임시 테이블에 필요한 간격을 생성 한 다음 임시 time_intervals 테이블로 데이터 테이블을 JOIN하고 집계합니다 .

프로시 저는 지정된 모든 유형의 간격을 생성 할 수 있습니다.

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에서 생성 된 시퀀스와 비슷한 것을 원했습니다.
Phillip Whelan 2011 년

데이터를 한 번 생성하는 경우 영구 테이블을 사용하고이 스크립트를 사용하여 누락 된 날짜를 채울 수도 있습니다.
Bimal Poudel 2015 년

phpMyAdmin을 통해 작동하도록 만들기 위해 프로 시저 문 생성 전에 구분 기호를 변경해야했습니다 (선언문에 대한 구문 오류를 방지하기 위해) [code] DECLARE // [/ code]
Defkon1

이 솔루션은 매우 강력하고 유연합니다. 안타깝게도 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)의 용도는 무엇입니까?
Shiva

14

데이터가없는 날에 대해보고하고 싶다는 점에서 BIRT 보고서와 유사한 문제가있었습니다. 해당 날짜에 대한 항목이 없었기 때문에 가장 쉬운 해결책은 모든 날짜를 저장 한 간단한 테이블을 만들고이를 사용하여 범위를 가져 오거나 조인하여 해당 날짜에 대한 값을 0으로 만드는 것이 었습니다.

앞으로 5 년 동안 테이블이 채워지는지 확인하기 위해 매달 실행되는 작업이 있습니다. 따라서 테이블이 생성됩니다.

create table all_dates (
    dt date primary key
);

의심 할 여지없이 다른 DBMS로이 작업을 수행하는 마술적인 까다로운 방법이 있지만 우리는 항상 가장 간단한 솔루션을 선택합니다. 테이블의 저장소 요구 사항은 최소화되어 쿼리를 훨씬 간단하고 이식 할 수 있습니다. 이러한 종류의 솔루션은 데이터에 대한 행당 계산이 필요하지 않기 때문에 성능 관점에서 거의 항상 더 좋습니다.

다른 옵션 (이전에 사용했던)은 모든 날짜에 대해 테이블에 항목이 있는지 확인하는 것입니다. 우리는 주기적으로 테이블을 휩쓸고 존재하지 않는 날짜 및 / 또는 시간에 대한 항목을 추가했습니다. 이것은 귀하의 경우에는 옵션이 아닐 수 있으며 저장된 데이터에 따라 다릅니다.

당신이 경우 실제로 는 유지하는 번거 로움 생각 all_dates채워 테이블, 저장 프로 시저는 그 날짜를 포함하는 데이터 집합을 반환하는 길을 가야하는 것입니다. 테이블에서 미리 계산 된 데이터를 가져 오는 것보다 호출 될 때마다 범위를 계산해야하기 때문에 거의 확실히 느립니다.

하지만 솔직히 말해서 심각한 데이터 저장 문제없이 1000 년 동안 테이블을 채울 수 있습니다. 16 바이트 (예 : 16 바이트) 날짜 365,000 개, 날짜를 복제하는 인덱스, 안전을 위해 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과 같은 결과를 제공합니다. 소수점 뒤의 밀리 초 부분; while-SELECT UNIX_TIMESTAMP (ADDDATE ( '2009-01-01', 0)) AS date; 그 부분이 발생하지 않습니다.
Bimal Poudel

3

Access (또는 모든 SQL 언어)

  1. 이 개 필드가 하나 개의 테이블을 만들고, 우리는이 테이블에 전화 할게 tempRunDates:
    --fields fromDatetoDate
    시작 날짜와 종료 날짜가 --Then 삽입 만 한 기록을.

  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'사이의 날짜 테이블을 반환합니다.


"select '2019-01-01'as dt"이 선택자는 무엇입니까? 시작 및 끝 필드를 선택할 수 있습니까?
Miguel Stevens 19

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
Create a table- mytable.
스키마는 중요하지 않습니다. 중요한 것은 그 안에있는 행의 수입니다.
따라서 10 개의 행, 값 (1 ~ 10)이있는 INT 유형의 열 하나만 유지할 수 있습니다.

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를 선언하고, a_begin을 d에 할당하고, d를 t에 WHILE반복 INSERT하고 ADDDATE함수를 호출 하여 값 d를 증가시킵니다. 마침내 SELECT * FROM t.


실제로 영구 테이블이 실행 속도에 더 좋을 것이라고 생각합니다 (최소한의 저장소 요구 사항이 있음). 날짜를 테이블로 미리 계산하고 필요할 때마다 추출하는 것이 필요할 때마다 범위를 만드는 것보다 더 빠릅니다.
paxdiablo

@Pax, 얻은 속도 향상이 테이블 유지 관리의 가치가 있는지 여부에 따라 다릅니다. 예를 들어 보고서 생성에 3 초가 걸리고 WHILE 루프를 실행하여 날짜를 생성하는 데 0.001 초가 걸린다면 성능 향상은 무시할 수 없다고 주장합니다. Knuth : 조기 최적화는 모든 악의 근원입니다.
Eugene Yokota

@ 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

그런 다음 @HOLDERVariable 테이블에는 두 날짜 사이에 일 단위로 증가하는 모든 날짜가 저장되어 마음에 드는 콘텐츠에 참여할 준비가되어 있습니다.


안녕하세요, 코드를 사용하고 DATETIME 유형의 열 datefill을 사용하여 DateTest를 정의한 테이블에 출력을 삽입하려고하지만 실패했습니다 ... 그것을 사용하는 코드를 제공 할 수 있습니까? 참고 : 여기에서 SQL Server 사용 감사합니다 :)
sys_debug

0

나는 이것으로 꽤 오랫동안 싸웠습니다. 솔루션을 검색했을 때 구글에서 처음으로 히트 한 것이기 때문에 지금까지 얻은 위치를 게시하겠습니다.

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

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이됩니다. 그런 다음 1 초를 빼서 종료 날짜를 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 THEN윤년 은 라인에 어떤 영향을 미칩니 까?
Stewart

또한 1509 행 에있는 숫자의 의미는 무엇 입니까? 이 코드는 실제 설명이 거의없는 "pat answer"입니다.
Stewart

0

통계를 위해 두 날짜 사이의 모든 달 목록이 필요했습니다. 2 개의 날짜는 구독의 시작일과 종료일입니다. 따라서 목록에는 모든 월과 월별 구독 금액이 표시됩니다.

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)

이제 삽입 된 레코드를 볼 수 있습니다.

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 19-07-29
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.