94

標準の 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

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

4

22 に答える 22

69

このストアドプロシージャを使用して、必要な間隔をtime_intervalsという名前の一時テーブルに生成し、次にJOINして、データテーブルを一時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;

この投稿の下部にある同様のデータシナリオの例では、SQLServer用に同様の関数を作成しました。

于 2009-02-04T06:02:57.100 に答える
30

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

編集 2021/01 (Dr. V): このソリューションが気に入り、mySQL V8 で機能するようにしました。プロシージャにラップするコードは次のとおりです。

DELIMITER //

CREATE PROCEDURE dates_between (IN from_date DATETIME,
                               IN to_date DATETIME) BEGIN
    WITH RECURSIVE dates(Date) AS
    (
        SELECT from_date as Date
        UNION ALL
        SELECT DATE_ADD(Date, INTERVAL 1 day) FROM dates WHERE Date < to_date
    )
    SELECT DATE(Date) FROM dates;
END//

DELIMITER ;
于 2011-11-18T14:20:51.810 に答える
14

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

テーブルが5年先に作成されるようにするために、毎月実行されるジョブがあります。テーブルは次のように作成されます。

create table all_dates (
    dt date primary key
);

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

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

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

しかし、正直なところ、深刻なデータストレージの問題なしに1000年間テーブルにデータを入力できます。たとえば、365,000の16バイト(たとえば)の日付に、日付を複製するインデックスに加えて、安全のために20%のオーバーヘッドがあります。約14M[365,000* 16 * 2 * 1.2 = 14,016,000バイト])、物事のスキームの小さなテーブル。

于 2009-02-04T05:19:16.253 に答える
14

次のように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 回インクリメントされるため、使用できません。

于 2009-02-04T05:10:05.397 に答える
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の日付のリストを生成できます。大きくする必要がある場合は、内部クエリに別のクロス結合を追加できます。

于 2009-02-04T05:48:19.237 に答える
3

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

  1. 2 つのフィールドを持つ 1 つのテーブルを作成します。このテーブルを次のように呼びますtempRunDates:
    --FieldsfromDateおよび --toDate
    次に、開始日と終了日を持つ 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;
    

簡単!

于 2011-08-23T17:46:36.437 に答える
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
于 2012-01-24T21:42:28.607 に答える
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

テーブル値関数などのバリエーションを見てきました。

日付の永続的なリストを保持することもできます。これは、データウェアハウスと時刻のリストにあります。

于 2009-02-04T06:12:40.343 に答える
1

このソリューションはMySQL5.0で動作します
テーブルを作成します- mytable
スキーマは重要ではありません。重要なのはその中の行数です。
したがって、INT型の列を1つだけ保持し、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のフォーム部分を変更することで増やすことができ
ます。mytablex、mytable y、mytable z
から、範囲はbe10*10*10 =1000などになります。

于 2011-08-04T06:42:39.667 に答える
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
于 2011-06-08T23:05:39.677 に答える
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行しか返されませんでした。

于 2012-04-06T17:58:21.027 に答える
0

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

于 2009-02-04T04:46:01.390 に答える
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つの日付の間で日単位で増分されたすべての日付を保持し、心ゆくまで参加できるようにします。

于 2010-08-19T10:49:34.827 に答える
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
于 2016-08-31T09:11:47.250 に答える
0

@ bradの回答を改善して、カーソルでCTEを使用しました:

DECLARE cursorPeriod CURSOR FOR SELECT * FROM (
   WITH RECURSIVE period as
        (
             SELECT '2019-01-01' as dt
             UNION
             SELECT DATE_ADD(period.dt, INTERVAL 1 DAY) FROM period WHERE DATE_ADD(period.dt, INTERVAL 1 DAY) <= '2019-04-30'
        )
   SELECT DATE(dt) FROM period ORDER BY dt ) AS derived_table;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET exit_loop = TRUE;
于 2021-09-08T21:52:41.400 に答える
-3

私は使っている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)

これが多くの人に役立つことを願っています。

于 2018-04-19T02:11:27.080 に答える