2つの日付の間の日付のリストを取得する


91

標準のmysql関数を使用すると、2つの日付の間の日のリストを返すクエリを作成する方法があります。

たとえば、2009-01-01と2009-01-13を指定すると、次の値を持つ1列のテーブルが返されます。

 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

編集:私ははっきりしていないようです。このリストを生成したいと思います。データベースに(datetimeによって)値が格納されていますが、上記の日付のリストへの左外部結合でそれらを集計したい(この結合のいくつかの右側から数日間nullが期待され、これを処理します) )。


2
最善の解決策は回答に記載されていると思いますstackoverflow.com/a/2157776/466677
Marek Gregor

回答:


68

このストアドプロシージャを使用して、time_intervalsという名前の一時テーブルに必要な間隔を生成し、次にJOINして、データテーブルを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で生成されたシーケンスに似たものを個人的に望んでいました。
Phillip Whelan、2011年

データを1回生成する場合は、永続テーブルを使用し、このスクリプトを使用して欠落している日付を埋めることもできます。
Bimal Poudel、2015年

phpMyAdminを介して機能するように、create procedureステートメントの前に区切り文字を変更する必要がありました(declareステートメントの構文エラーを回避するため)[code] DECLARE // [/ code]
Defkon1

このソリューションは非常に強力で柔軟性があり、残念ながらmariaDBではエラー#1067-call make_intervals( '2009-01-01 00:00:00'、 '2009-01-10を実行すると' interval_end 'のデフォルト値が無効になります00:00:00 '、1、' DAY '); 私はバージョン5.7.28を使用しています
シェルペレイラ

28

MSSQLの場合、これを使用できます。とても速いです。

これをテーブル値関数またはストアドプロシージャにラップし、開始日と終了日を変数として解析できます。

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
ここでオプション(MAXRECURSION 0)の使用は何ですか?
シバ

14

BIRTレポートにも同様の問題があり、データがなかった当時のレポートを作成したかったのです。これらの日付のエントリがなかったので、最も簡単な解決策は、すべての日付を格納する単純なテーブルを作成し、それを使用して範囲を取得するか、結合してその日付のゼロ値を取得することでした。

テーブルが5年先に作成されるように、毎月実行するジョブがあります。テーブルはこうして作成されます:

create table all_dates (
    dt date primary key
);

間違いなく、別のDBMSでこれを行うには魔法のようなトリッキーな方法がありますが、私たちは常に最も単純なソリューションを選択します。テーブルのストレージ要件は最小限であり、クエリが非常に単純で移植性の高いものになります。この種のソリューションは、データの行ごとの計算を必要としないため、ほとんどの場合、パフォーマンスの観点からは優れています。

もう1つのオプション(以前に使用したことがある)は、すべての日付のテーブルにエントリがあることを確認することです。テーブルを定期的にスイープし、存在しない日付や時間のエントリをゼロに追加しました。これはあなたの場合オプションではないかもしれません、それは格納されたデータに依存します。

テーブルにデータを入力しておくのが面倒だと本当に思う場合all_datesは、ストアドプロシージャが、これらの日付を含むデータセットを返す方法です。テーブルから事前に計算されたデータを単にプルするのではなく、呼び出されるたびに範囲を計算する必要があるため、これはほぼ確実に遅くなります。

しかし、正直に言うと、深刻なデータストレージの問題なしに、テーブルを1000年間使用できます。たとえば、365,000の16バイト(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"を使用することはできません。これは、ユーザー変数が各行で2回インクリメントされるためです。


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 2015年

3

Access(または任意のSQL言語)

  1. 2つのフィールドを持っている一つのテーブルを作成し、我々はこのテーブルと呼ぶことにしますtempRunDates
    --fields fromDatetoDate
    --Thenは、開始日と終了日を持っているだけで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」までの日付のテーブルを返します。


「DTとして '2019-01-01'を選択」このセレクターとは何ですか?開始フィールドと終了フィールドを選択することはできますか?
ミゲルスティーブンス

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
スキーマは重要ではありません。重要なのは、その中の行数です。
したがって、値が1から10の10行のINTタイプの列を1つだけ保持できます。

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のフォーム部分を変更することにより、この範囲を増やすことができ
ます。mytablex、mytable y、mytable z
から、範囲は次のように10*10*10 =1000なります。


0

2つのパラメーターa_beginおよびa_endを受け取るストアード・プロシージャーを作成します。その中にtという名前の一時テーブルを作成し、変数dを宣言し、a_beginをdに割り当て、dをtにWHILEループしINSERTADDDATE関数を呼び出して値dをインクリメントします。ついにSELECT * FROM t


実際、パーマネントテーブルは実行速度の点で優れていると思います(ストレージ要件は最小限です)。日付を事前に計算してテーブルに入れ、必要なときに抽出する方が、必要になるたびに範囲を作成するよりも高速です。
paxdiablo 2009

@Pax、得られた速度の改善がテーブルのメンテナンスに値するかどうかによって異なります。たとえば、レポートの生成に3秒かかり、日付を生成するためのWHILEループの実行に0.001秒かかる場合、パフォーマンスの向上は無視できると主張します。Knuth:時期尚早の最適化はすべての悪の根源です。
Eugene Yokota

@ eed3si9n、時期尚早の最適化、はい、すべての最適化ではありません:-)あなたの例が正しければ、はい、私はあなたに同意しますが、確実に測定する必要があります。テーブルのメイントは、私が提案したように1000年前に(1回限りのコスト)生成すると消えますが、それらの秒数は加算されます。
paxdiablo 2009

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テーブルには、これら2つの日付の間で日ごとに増分されたすべての日付が保持され、心ゆくまで参加できます。


こんにちは、コードを使用して、DATETIME型の列datefillでDateTestを定義したテーブルに出力を挿入しようとしていますが、失敗します...それで動作するコードを提供できますか?注:ここでSQL Serverを使用していただきありがとうございます:)
sys_debug '29年

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行しか返されません。


1
興味深いアプローチ。しかし、これは本質的にVihangが上記で提案したものではありません;)?
リー

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うるう年はラインにどのような影響を与えますか?
スチュワート

また、1509行目の数字の意味は何ですか?このコードは、実際の説明があまりない「パット回答」です。
スチュワート

0

統計のために、2つの日付の間のすべての月のリストが必要でした。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)

ここでは、これらの日付ではなく2つの日付内のレコードをフェッチするクエリを使用しています。

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.