web-dev-qa-db-de.com

Holen Sie sich eine Liste von Daten zwischen zwei Daten

Mit den Standardfunktionen von mysql können Sie eine Abfrage schreiben, die eine Liste von Tagen zwischen zwei Datumsangaben zurückgibt.

bei gegebenem 2009-01-01 und 2009-01-13 wird beispielsweise eine einspaltige Tabelle mit den folgenden Werten zurückgegeben:

 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: Es scheint, ich war nicht klar. Ich möchte diese Liste generieren. Ich habe Werte in der Datenbank gespeichert (nach Datumszeit), möchte jedoch, dass sie auf einem linken äußeren Join zu einer Liste von Datumsangaben wie oben zusammengefasst werden (ich erwarte für einige Tage auf der rechten Seite des Join einige Zeit und werde damit umgehen ).

84
Gilgad

Ich würde diese gespeicherte Prozedur verwenden, um die benötigten Intervalle in der temporären Tabelle mit dem Namen time_intervals zu generieren, dann JOIN und aggregiere Ihre Datentabelle mit der temporären time_intervals -Tabelle.

Die Prozedur kann Intervalle aller darin angegebenen Typen generieren: 

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;

Ein ähnliches Beispielszenario am Ende von this post , wo ich eine ähnliche Funktion für SQL Server entwickelte.

65
Ron Savage

Für MSSQL können Sie dies verwenden. Es ist sehr schnell.

Sie können dies in einer Tabellenwertfunktion zusammenfassen oder gespeicherte proc- und Start- und Enddaten als Variablen analysieren.

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
28
Richard

Wir hatten ein ähnliches Problem mit BIRT-Berichten, da wir an jenen Tagen berichten wollten, an denen keine Daten vorlagen. Da für diese Datumsangaben keine Einträge vorhanden waren, war die einfachste Lösung für uns, eine einfache Tabelle zu erstellen, in der alle Datumsangaben gespeichert wurden, und diese Werte zum Abrufen von Bereichen oder für die Verknüpfung zu verwenden, um für dieses Datum Nullwerte zu erhalten.

Wir haben einen Job, der jeden Monat läuft, um sicherzustellen, dass der Tisch 5 Jahre in die Zukunft gefüllt wird. Die Tabelle wird folgendermaßen erstellt:

create table all_dates (
    dt date primary key
);

Zweifellos gibt es magische knifflige Wege, dies mit verschiedenen DBMS 'zu tun, aber wir entscheiden uns immer für die einfachste Lösung. Der Speicherbedarf für die Tabelle ist minimal und macht die Abfragen so viel einfacher und tragbar. Diese Art von Lösung ist aus Sicht der Leistung fast immer besser, da für die Daten keine Berechnungen pro Zeile erforderlich sind.

Die andere Option (und wir haben diese bereits zuvor verwendet) besteht darin, sicherzustellen, dass für jedes Datum ein Eintrag in der Tabelle vorhanden ist. Wir fuhren die Tabelle regelmäßig ab und fügten null Einträge für Datumsangaben und/oder Zeiten hinzu, die nicht vorhanden waren. Dies ist in Ihrem Fall möglicherweise keine Option, es hängt von den gespeicherten Daten ab.

Wenn Sie wirklich denken, es sei mühsam, die all_dates-Tabelle gefüllt zu halten, ist eine gespeicherte Prozedur der richtige Weg, um ein Dataset mit diesen Datumsangaben zurückzugeben. Dies wird mit ziemlicher Sicherheit langsamer sein, da Sie den Bereich bei jedem Aufruf berechnen müssen, anstatt zuvor berechnete Daten aus einer Tabelle zu ziehen.

Um ehrlich zu sein, könnten Sie die Tabelle jedoch 1000 Jahre lang ohne ernsthafte Probleme mit der Datenspeicherung ausfüllen - 365.000 16-Byte-Datumsangaben (z. B.) plus einen Index, der das Datum dupliziert, plus 20% Overhead aus Sicherheitsgründen, würde ich ungefähr schätzen etwa 14M [365.000 * 16 * 2 * 1,2 = 14.016.000 Bytes]), eine winzige Tabelle im Schema der Dinge.

13
paxdiablo

Sie können MySQL Benutzervariablen wie folgt verwenden:

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 ist -1, weil Sie es bei der ersten Verwendung hinzufügen. Sie können "HAVING date_sequence" auch nicht verwenden, da dadurch die Benutzervariable für jede Zeile zweimal erhöht wird.

12
Andrew Vit

Wenn Sie sich eine Idee von this leihen, können Sie eine Tabelle mit 0 bis 9 einrichten und diese verwenden, um Ihre Datumsliste zu erstellen.

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

Auf diese Weise können Sie eine Liste mit bis zu 1000 Daten erstellen. Wenn Sie größer werden müssen, können Sie der inneren Abfrage einen weiteren Cross-Join hinzufügen.

8
Logan5

Für den Zugriff (oder eine beliebige SQL-Sprache)

  1. Erstellen Sie eine Tabelle mit 2 Feldern. Wir nennen diese Tabelle tempRunDates
    -- Felder fromDate und toDate 
    -- Fügen Sie dann nur 1 Datensatz ein, der das Startdatum und das Enddatum enthält.

  2. Erstellen Sie eine weitere Tabelle: Time_Day_Ref
    -- Importieren Sie eine Datumsliste (in Excel ist eine einfache Liste) in diese Tabelle. 
    -- Der Feldname in meinem Fall lautet Greg_Dt für Gregorianisches Datum
    -- Ich habe meine Liste vom 1. Januar 2009 bis zum 1. Januar 2020 erstellt.

  3. Führen Sie die Abfrage aus:

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

Einfach!

3
Nathan Wood

Normalerweise würde man eine Hilfsnummerntabelle verwenden, die man normalerweise zu diesem Zweck aufbewahrt, mit einigen Variationen:

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

Ich habe Variationen mit Tabellenwertfunktionen usw. gesehen.

Sie können auch eine permanente Datumsliste führen. Wir haben das in unserem Data Warehouse sowie eine Liste der Tageszeiten.

2
Cade Roux
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
Todd Dickerson

Wie Sie Datumsangaben zwischen zwei angegebenen Daten im SQL Server finden, erfahren Sie unter http://ektaraval.blogspot.com/2010/09/writing-recursive-query-to-find-out-all.html

2
Ekta

Diese Lösung arbeitet mit MySQL 5.0
Erstellen Sie eine Tabelle - mytable.
Das Schema ist nicht wesentlich. Was zählt, ist die Anzahl der Reihen.
Sie können also nur eine Spalte vom Typ INT mit 10 Zeilen mit Werten von 1 bis 10 beibehalten.

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

Einschränkung: Die maximale Anzahl von Datumsangaben, die von der obigen Abfrage zurückgegeben werden, ist 
(rows in mytable)*(rows in mytable) = 10*10 = 100.

Sie können diesen Bereich vergrößern, indem Sie den Formularteil in SQL ändern:
aus mytable x, mytable y, mytable z
Der Bereich ist also 10*10*10 =1000 und so weiter.

1
Vihang Patil

Wir haben dies in unserem HRMS-System verwendet, Sie werden es nützlich finden

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
Anas
select * from table_name where col_Date between '2011/02/25' AND DATEADD(s,-1,DATEADD(d,1,'2011/02/27'))

Fügen Sie hier zunächst einen Tag zum aktuellen Enddatum hinzu, es wird 2011-02-28 00:00:00 sein, dann subtrahieren Sie eine Sekunde, um das Enddatum 2011-02-27 23:59:59 festzulegen. Auf diese Weise können Sie alle Daten zwischen den angegebenen Intervallen erhalten.

ausgabe:
2011/02/25
2011/02/26
2011/02/27

0
Chandra Prakash

Ich habe schon eine ganze Weile damit zu kämpfen. Da dies der erste Treffer bei Google ist, als ich nach der Lösung gesucht habe, lassen Sie mich wissen, wo ich bisher bin.

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

Ersetzen Sie [yourTable] durch eine Tabelle aus Ihrer Datenbank. Der Trick ist, dass die Anzahl der Zeilen in der Tabelle, die Sie auswählen,> die Anzahl der Datumsangaben sein muss, die Sie zurückgeben möchten. Ich habe versucht, den Tabellenplatzhalter DUAL zu verwenden, aber es würde nur eine einzige Zeile zurückgegeben.

0
champ

Ich brauchte eine Liste mit allen Monaten zwischen zwei Datumsangaben für Statistiken. Die beiden Daten sind das Start- und Enddatum eines Abonnements. Die Liste zeigt also alle Monate und die Anzahl der Abonnements pro Monat.

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 @[email protected];  #- 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
Wow

Ich benutzeServer version: 5.7.11-log MySQL Community Server (GPL)

Jetzt lösen wir das auf einfache Weise.

Ich habe eine Tabelle mit dem Namen "datetable" erstellt.

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)

jetzt sehen wir die eingefügten Datensätze darin.

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)

und hier unsere Abfrage zum Abrufen von Datensätzen innerhalb von zwei Terminen statt diesen Terminen.

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)

ich hoffe, das würde vielen helfen.

0
ArifMustafa

Elegante Lösung mit neuer rekursiver Funktionalität (Common Table Expressions) in MariaDB> = 10.3 und 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;

Das Obige gibt eine Datumstabelle zwischen '2019-01-01' und '2019-04-30' zurück.

0
Brad

Erstellen Sie eine gespeicherte Prozedur, die zwei Parameter a_begin und a_end ..__ enthält: Erstellen Sie eine temporäre Tabelle mit dem Namen t, deklarieren Sie eine Variable d, weisen Sie d_begin einen Namen zu, und führen Sie eine WHILE-Schleife INSERTing d in t aus, und rufen Sie die Funktion ADDDATE auf geschätzt. Zum Schluss SELECT * FROM t.

0
Eugene Yokota
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 $$

bei diesem Verfahren werden alle Daten vom Jahresanfang bis jetzt eingefügt. Ersetzen Sie einfach die Tage für "Anfang" und "Ende", und Sie können loslegen!

0
Julio Marins

Ich würde etwas ähnliches verwenden:

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

Die @HOLDER Variablentabelle enthält dann alle Datumsangaben, die zwischen diesen beiden Datumsangaben um den Tag erhöht wurden.

0