Dapatkan daftar tanggal antara dua tanggal


91

Menggunakan fungsi mysql standar apakah ada cara untuk menulis kueri yang akan mengembalikan daftar hari antara dua tanggal.

Misalnya, diberikan 2009-01-01 dan 2009-01-13 itu akan mengembalikan tabel satu kolom dengan nilai:

 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

Edit: Sepertinya saya belum jelas. Saya ingin MEMBUAT daftar ini. Saya memiliki nilai yang disimpan dalam database (berdasarkan datetime) tetapi ingin mereka dikumpulkan di sebelah kiri luar bergabung ke daftar tanggal seperti di atas (Saya mengharapkan null dari sisi kanan beberapa gabungan ini untuk beberapa hari dan akan menangani ini ).


2
Saya pikir solusi terbaik dijelaskan dalam jawaban stackoverflow.com/a/2157776/466677
Marek Gregor

Jawaban:


68

Saya akan menggunakan prosedur tersimpan ini untuk menghasilkan interval yang Anda butuhkan ke dalam tabel temp bernama time_intervals , kemudian BERGABUNG dan menggabungkan tabel data Anda dengan tabel interval_waktu temp .

Prosedur dapat menghasilkan interval dari semua tipe berbeda yang Anda lihat ditentukan di dalamnya:

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;

Contoh skenario data serupa di bagian bawah posting ini , di mana saya membangun fungsi serupa untuk SQL Server.


4
Saya pribadi berharap untuk sesuatu yang mirip dengan urutan yang dihasilkan di PostgreSQL.
Phillip Whelan

Jika Anda menghasilkan data sekali, Anda bahkan dapat menggunakan tabel permanen dan menggunakan skrip ini untuk mengisi tanggal yang hilang.
Bimal Poudel

Saya harus mengubah pembatas sebelum membuat pernyataan prosedur untuk membuatnya bekerja melalui phpMyAdmin (untuk menghindari kesalahan sintaks pada pernyataan deklarasi) [kode] MENYATAKAN // [/ kode]
Defkon1

Solusi ini sangat kuat dan fleksibel, sayangnya di mariaDB saya mendapatkan kesalahan # 1067 - Nilai default tidak valid untuk 'interval_end' saat menjalankan panggilan make_intervals ('2009-01-01 00:00:00', '2009-01-10 00: 00: 00 ', 1,' DAY '); Saya menggunakan versi 5.7.28
shelbypereira

28

Untuk MSSQL Anda dapat menggunakan ini. Ini SANGAT cepat.

Anda dapat membungkus ini dalam fungsi nilai tabel atau proc dan parse yang disimpan di tanggal mulai dan akhir sebagai variabel.

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
Apa gunanya OPTION (MAXRECURSION 0) disini?
Shiva

14

Kami memiliki masalah yang sama dengan laporan BIRT yang ingin kami laporkan pada hari-hari yang tidak memiliki data. Karena tidak ada entri untuk tanggal tersebut, solusi termudah bagi kami adalah membuat tabel sederhana yang menyimpan semua tanggal dan menggunakannya untuk mendapatkan rentang atau bergabung untuk mendapatkan nilai nol untuk tanggal tersebut.

Kami memiliki tugas yang dijalankan setiap bulan untuk memastikan bahwa tabel diisi 5 tahun ke depan. Tabel dibuat sebagai berikut:

create table all_dates (
    dt date primary key
);

Tidak diragukan lagi ada cara ajaib yang rumit untuk melakukan ini dengan DBMS berbeda, tetapi kami selalu memilih solusi yang paling sederhana. Persyaratan penyimpanan untuk tabel tersebut minimal dan membuat kueri jadi lebih sederhana dan portabel. Solusi semacam ini hampir selalu lebih baik dari sudut pandang kinerja karena tidak memerlukan penghitungan per baris pada data.

Opsi lain (dan kami telah menggunakan ini sebelumnya) adalah memastikan ada entri di tabel untuk setiap tanggal. Kami menyapu tabel secara berkala dan menambahkan nol entri untuk tanggal dan / atau waktu yang tidak ada. Ini mungkin bukan pilihan dalam kasus Anda, itu tergantung pada data yang disimpan.

Jika Anda benar-benar berpikir itu merepotkan untuk menjaga all_datestabel terisi, prosedur tersimpan adalah cara yang akan mengembalikan dataset yang berisi tanggal-tanggal tersebut. Ini hampir pasti akan lebih lambat karena Anda harus menghitung rentang setiap kali dipanggil daripada hanya menarik data yang telah dihitung sebelumnya dari tabel.

Tapi, sejujurnya, Anda dapat mengisi tabel selama 1000 tahun tanpa masalah penyimpanan data yang serius - 365.000 16-byte (misalnya) tanggal ditambah indeks yang menggandakan tanggal ditambah 20% overhead untuk keamanan, saya kira-kira akan memperkirakan di sekitar 14M [365.000 * 16 * 2 * 1.2 = 14.016.000 byte]), tabel yang sangat kecil dalam skema hal.


12

Anda dapat menggunakan variabel pengguna MySQL seperti ini:

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 adalah -1 karena Anda menambahkannya saat pertama kali Anda menggunakannya. Selain itu, Anda tidak dapat menggunakan "HAVING date_sequence" karena itu membuat variabel pengguna bertambah dua kali untuk setiap baris.


8

Meminjam ide dari jawaban ini , Anda dapat membuat tabel dengan 0 hingga 9 dan menggunakannya untuk menghasilkan daftar tanggal.

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';

Ini akan memungkinkan Anda membuat daftar hingga 1000 tanggal. Jika Anda perlu memperbesar, Anda bisa menambahkan gabungan silang lain ke kueri dalam.


Solusi ini bekerja jauh lebih baik. Tetapi ada masalah dengan UNIX_TIMESTAMP () - ini memberikan hasil seperti 1231185600.000000; bagian milidetik setelah koma desimal; sementara - PILIH UNIX_TIMESTAMP (ADDDATE ('2009-01-01', 0)) AS date; TIDAK menghasilkan bagian itu.
Bimal Poudel

3

Untuk Access (atau bahasa SQL apa pun)

  1. Buat satu tabel yang memiliki 2 field, kita akan memanggil tabel ini tempRunDates:
    --Fields fromDatedan toDate
    --Lalu masukkan hanya 1 record, yang memiliki tanggal mulai dan tanggal akhir.

  2. Buat tabel lain: Time_Day_Ref
    --Impor daftar tanggal (buat daftar di excel itu mudah) ke dalam tabel ini.
    --Nama bidang dalam kasus saya adalah Greg_Dt, untuk Tanggal
    Masehi --Saya membuat daftar saya dari 1 Januari 2009 hingga 1 Januari 2020.

  3. Jalankan kueri:

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

Mudah!


2

Biasanya seseorang akan menggunakan tabel bilangan tambahan yang biasanya Anda simpan hanya untuk tujuan ini dengan beberapa variasi pada ini:

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

Saya telah melihat variasi dengan fungsi nilai tabel, dll.

Anda juga dapat menyimpan daftar tanggal permanen. Kami memilikinya di gudang data kami serta daftar waktu dalam sehari.



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

Solusi elegan menggunakan fungsionalitas rekursif (Common Table Expressions) baru di MariaDB> = 10.3 dan 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;

Di atas mengembalikan tabel tanggal antara '2019-01-01' dan '2019-04-30'.


"pilih '2019-01-01' as dt" apa pemilih ini? Apakah mungkin untuk hanya memilih bidang dari dan ke?
Miguel Stevens

1

Kami menggunakan ini di Sistem HRMS kami, Anda akan merasa berguna

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

Solusi ini bekerja dengan MySQL 5.0
Buat tabel - mytable.
Skema tidak material. Yang penting adalah jumlah baris di dalamnya.
Jadi, Anda hanya dapat menyimpan satu kolom tipe INT dengan 10 baris, nilai - 1 hingga 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';

Batasan: Jumlah tanggal maksimum yang dikembalikan oleh kueri di atas adalah
(rows in mytable)*(rows in mytable) = 10*10 = 100.

Anda dapat meningkatkan rentang ini dengan mengubah bagian formulir di sql:
dari mytable x, mytable y, mytable z
Jadi, range be 10*10*10 =1000dan seterusnya.


0

Buat prosedur tersimpan yang membutuhkan dua parameter a_begin dan a_end. Buat tabel sementara di dalamnya bernama t, deklarasikan variabel d, tetapkan a_begin ke d, dan jalankan WHILEperulangan INSERTke d ke t dan panggil ADDDATEfungsi untuk menaikkan nilai d. Akhirnya SELECT * FROM t.


Sebenarnya saya yakin meja permanen akan lebih baik untuk kecepatan eksekusi (memiliki persyaratan penyimpanan minimal). Lebih cepat menghitung sebelumnya tanggal ke dalam tabel dan mengekstraknya saat diperlukan, daripada membuat rentang setiap kali Anda membutuhkannya.
paxdiablo

@Pax, itu tergantung apakah peningkatan kecepatan yang diperoleh sepadan dengan pemeliharaan tabel. Misalnya, jika pembuatan laporan membutuhkan waktu 3 detik dan menjalankan putaran WHILE untuk menghasilkan tanggal membutuhkan 0,001 detik, saya berpendapat bahwa perolehan kinerja dapat diabaikan. Knuth: Optimasi prematur adalah akar dari segala kejahatan.
Eugene Yokota

@ eed3si9n, optimasi prematur, ya, tidak semua optimasi :-) Jika contoh Anda benar maka ya, saya setuju dengan Anda, tetapi itu harus diukur untuk kepastian. Pemeliharaan tabel hilang jika Anda membuatnya 1000 tahun (biaya satu kali) seperti yang saya sarankan tetapi detik-detik itu akan bertambah.
paxdiablo

0

Saya akan menggunakan sesuatu yang mirip dengan ini:

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

Kemudian @HOLDERtabel Variabel menyimpan semua tanggal yang bertambah hari antara dua tanggal tersebut, siap untuk digabungkan sesuka hati Anda.


Halo, Saya mencoba menggunakan kode dan memasukkan output ke dalam tabel saya mendefinisikan DateTest dengan kolom datefill tipe DATETIME ... tapi gagal ... dapatkah Anda memberikan kode yang akan bekerja dengan itu? Catatan: Menggunakan SQL Server di sini Terima kasih :)
sys_debug

0

Saya telah bertengkar dengan ini cukup lama. Karena ini adalah hit pertama di Google ketika saya mencari solusinya, izinkan saya memposting di mana saya mendapatkan sejauh ini.

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';

Ganti [yourTable]dengan tabel dari database Anda. Caranya adalah jumlah baris dalam tabel yang Anda pilih harus> = jumlah tanggal yang ingin Anda kembalikan. Saya mencoba menggunakan placeholder tabel DUAL, tetapi hanya akan mengembalikan satu baris.


1
Pendekatan yang menarik. Tapi .. bukankah ini pada dasarnya apa yang disarankan Vihang di atas;)?
Leigh

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

Di sini, pertama-tama tambahkan hari ke tanggal akhir saat ini, yaitu 2011-02-28 00:00:00, lalu Anda kurangi satu detik untuk membuat tanggal akhir 2011-02-27 23:59:59. Dengan melakukan ini, Anda bisa mendapatkan semua tanggal di antara interval yang diberikan.

keluaran:
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 $$

prosedur ini akan memasukkan semua tanggal dari awal tahun sampai sekarang, cukup ganti hari "awal" dan "akhir", dan Anda siap untuk pergi!


Bagaimana garis IF a=365 THENdipengaruhi oleh tahun kabisat?
Stewart

Juga, apa signifikansi angka 150pada baris 9? Kode ini adalah "jawaban tepuk" tanpa banyak penjelasan yang sebenarnya.
Stewart

0

Saya membutuhkan daftar dengan semua bulan antara 2 tanggal untuk statistik. 2 tanggal tersebut adalah tanggal mulai dan tanggal akhir dari langganan. Jadi daftar tersebut menunjukkan semua bulan dan jumlah langganan per bulan.

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

Anda bisa menggunakan ini

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

saya menggunakan Server version: 5.7.11-log MySQL Community Server (GPL)

Sekarang kita akan menyelesaikannya dengan cara yang sederhana.

Saya telah membuat tabel bernama "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)

sekarang, kita akan melihat catatan yang disisipkan di dalamnya.

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)

dan di sini kueri kami untuk mengambil rekaman dalam dua tanggal, bukan tanggal tersebut.

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)

berharap ini akan membantu banyak orang.


Diturunkan, tanpa alasan apa pun, SQL ini baik-baik saja dan dalam skenario kerja.
ArifMustafa
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.