generuje dni z zakresu dat


135

Chciałbym uruchomić zapytanie w stylu

select ... as days where `date` is between '2010-01-20' and '2010-01-24'

I zwróć dane takie jak:

dni
----------
2010-01-20
2010-01-21
2010-01-22
2010-01-23
2010-01-24

10
Z tym pytaniem nie wiąże się żaden inny problem. Powyższe pytanie jest problemem opanowania kursów SQL.
Pentium10

Czy potrzebujesz tylko tablicy dat opartej na wybranym zakresie dat?
Derek Adair

1
Myślę o użyciu, aby znaleźć problem ... Jeśli dostaniesz zadanie uzupełnienia brakujących rekordów w tabeli. I musisz uruchamiać zapytanie każdego dnia, myślę o czymś w rodzajuinsert into table select ... as days date between '' and ''
Pentium10,

13
Przykładem jego użycia może być generowanie statystyk i dołączanie wiersza z datami, dla których nie masz żadnych danych. Jeśli robisz coś w rodzaju grupowania według, może być znacznie szybciej wygenerowanie wszystkich informacji w SQL i dodanie ich do dowolnego formatu, jakiego potrzebujesz, zamiast zrzucać dane bez zmian do swojego języka i rozpocząć zapętlanie i dodawanie opróżnia.
Nanne,

1
@Nanne właśnie dlatego zapisałem to pytanie. Potrzebuję powyższego, aby LEWAĆ DOŁĄCZ do danych, które mogą nie istnieć w określonych datach.
Josh Diehl

Odpowiedzi:


318

To rozwiązanie nie wykorzystuje pętli, procedur ani tabel tymczasowych . Podzapytanie generuje daty z ostatnich 10 000 dni i można je rozszerzyć, aby przejść do tyłu lub do przodu, jak chcesz.

select a.Date 
from (
    select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a) ) DAY as Date
    from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as d
) a
where a.Date between '2010-01-20' and '2010-01-24' 

Wynik:

Date
----------
2010-01-24
2010-01-23
2010-01-22
2010-01-21
2010-01-20

Uwagi dotyczące wydajności

Testując to tutaj , wydajność jest zaskakująco dobra: powyższe zapytanie zajmuje 0,0009 sek.

Jeśli rozszerzymy podzapytanie, aby wygenerować ok. 100 000 liczb (a więc dat datowanych na około 274 lata) trwa 0,0458 sek.

Nawiasem mówiąc, jest to bardzo przenośna technika, która działa z większością baz danych z niewielkimi poprawkami.

Przykład SQL Fiddle zwracający 1000 dni


6
Zobaczysz lepszą wydajność, jeśli zmienisz UNIONna UNION ALL- marnowanie czasu na szukanie duplikatów do usunięcia, które nie istnieją. Jest to jednak zbyt skomplikowana IMO - jeśli zamierzasz skonstruować zestaw wyników przy użyciu UNION, dlaczego nie określić po prostu daty i skończyć z tym?
OMG Kucyki

7
dlaczego po prostu nie określić daty i skończyć z nią - ponieważ powyższa metoda pozwala na tworzenie dowolnie dużych zbiorów liczb (i dat) niewymagających tworzenia tabeli, które byłyby bolesne do zakodowania w sposób, który sugerujesz. Oczywiście w przypadku 5 randek to przesada; ale nawet wtedy, jeśli dołączasz do stołu, w którym nie znasz z góry dat, ale tylko potencjalne wartości minimalne i maksymalne, ma to sens.
RedFilter

2
To „bolesne” po prostu użyć funkcji DATETIME zamiast instrukcji UNION, którą już utworzyłeś? To łagodzi jakąkolwiek potrzebę logiki trzeba było dodać . Dlatego - nadmiernie skomplikowałeś zapytanie. Oświadczenie UNION, tak czy inaczej, nie jest skalowalne - określając datę lub liczbę, kto chce je zaktualizować, aby pomieścić powiedzmy 20 lub 30 dat?
OMG Kucyki

23
Naprawdę miło jest zobaczyć odpowiedź na pytanie, a nie niekończące się komentarze, jak nie można lub nie należy tego zrobić. Większość rzeczy można zrobić, a „należy” ma znaczenie tylko w kontekście, który jest inny dla każdego. Ta odpowiedź pomogła mi, chociaż doskonale zdaję sobie sprawę, że w większości sytuacji są lepsze sposoby.
joe

7
Ci z Was, którzy nie mogą uruchomić tego zapytania: Proszę uderzyć się w twarz, a następnie ponownie przeczytać komentarz OP na temat tego zapytania generującego 1000 dat. Ponieważ rok 2010 był ponad 1000 dni temu, musisz odpowiednio dostosować zapytanie.
Noel Baron

32

Oto kolejna odmiana wykorzystująca widoki:

CREATE VIEW digits AS
  SELECT 0 AS digit UNION ALL
  SELECT 1 UNION ALL
  SELECT 2 UNION ALL
  SELECT 3 UNION ALL
  SELECT 4 UNION ALL
  SELECT 5 UNION ALL
  SELECT 6 UNION ALL
  SELECT 7 UNION ALL
  SELECT 8 UNION ALL
  SELECT 9;

CREATE VIEW numbers AS
  SELECT
    ones.digit + tens.digit * 10 + hundreds.digit * 100 + thousands.digit * 1000 AS number
  FROM
    digits as ones,
    digits as tens,
    digits as hundreds,
    digits as thousands;

CREATE VIEW dates AS
  SELECT
    SUBDATE(CURRENT_DATE(), number) AS date
  FROM
    numbers;

A potem możesz po prostu zrobić (zobacz, jakie to eleganckie?):

SELECT
  date
FROM
  dates
WHERE
  date BETWEEN '2010-01-20' AND '2010-01-24'
ORDER BY
  date

Aktualizacja

Warto zauważyć, że będzie można wygenerować tylko przeszłe daty, począwszy od daty bieżącej . Jeśli chcesz wygenerować dowolny zakres dat (przeszły, przyszły i pomiędzy), będziesz musiał zamiast tego użyć tego widoku:

CREATE VIEW dates AS
  SELECT
    SUBDATE(CURRENT_DATE(), number) AS date
  FROM
    numbers
  UNION ALL
  SELECT
    ADDDATE(CURRENT_DATE(), number + 1) AS date
  FROM
    numbers;

1
To nie działa we wszystkich przypadkach. WYBIERZ datę Z dat GDZIE data MIĘDZY „2014-12-01” a „2014-12-28” Sortuj według daty
vasanth

3
Dobry telefon @ user927258. Dzieje się tak, ponieważ pierwszy dateswspomniany powyżej widok oblicza daty zaczynające się od daty bieżącej, dlatego nie będzie można pobrać dat ustawionych w przyszłości. Odpowiedź od @RedFilter ma tę samą wadę projektową. Dodałem jednak obejście w mojej odpowiedzi.
Stéphane

Korzystanie z niektórych widoków zdecydowanie upraszcza zapytania i umożliwia ich wielokrotne użycie. Chociaż zasadniczo robią to samo, wszystkie te UNIONklauzule wyglądają dziwnie w pojedynczej instrukcji SQL.
Stewart,

24

Zaakceptowana odpowiedź nie działa dla PostgreSQL (błąd składni na lub w pobliżu „a”).

Sposób, w jaki robisz to w PostgreSQL, polega na użyciu generate_seriesfunkcji, tj .:

SELECT day::date
FROM generate_series('2010-01-20', '2010-01-24', INTERVAL '1 day') day;

    day
------------
 2010-01-20
 2010-01-21
 2010-01-22
 2010-01-23
 2010-01-24
(5 rows)

14

Używając rekurencyjnego wyrażenia Common Table Expression (CTE), możesz wygenerować listę dat, a następnie wybrać z niej. Oczywiście normalnie nie chciałbyś utworzyć trzech milionów dat, więc to tylko ilustruje możliwości. Możesz po prostu ograniczyć zakres dat w CTE i pominąć klauzulę where w instrukcji select za pomocą CTE.

with [dates] as (
    select convert(datetime, '1753-01-01') as [date] --start
    union all
    select dateadd(day, 1, [date])
    from [dates]
    where [date] < '9999-12-31' --end
)
select [date]
from [dates]
where [date] between '2013-01-01' and '2013-12-31'
option (maxrecursion 0)

W Microsoft SQL Server 2005 wygenerowanie listy CTE wszystkich możliwych dat zajęło 1:08. Wygenerowanie stu lat zajęło mniej niż sekundę.


7

Zapytanie MSSQL

select datetable.Date 
from (
    select DATEADD(day,-(a.a + (10 * b.a) + (100 * c.a)),getdate()) AS Date
    from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
     union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a

    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
     union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b

    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
     union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) datetable
where datetable.Date between '2014-01-20' and '2014-01-24' 
order by datetable.Date DESC

Wynik

Date
-----
2014-01-23 12:35:25.250
2014-01-22 12:35:25.250
2014-01-21 12:35:25.250
2014-01-20 12:35:25.250

2
Gdybym tylko przewinął trochę więcej ... westchnij. W każdym razie dziękuję. Dodałem CAST (<wyrażenie> AS DATE), aby usunąć czas z mojej wersji. Używane również tam, gdzie a.Date między GETDATE () - 365 AND GETDATE () ... jeśli uruchomisz zapytanie dzisiaj, nie wyświetli żadnych wierszy, jeśli nie zauważysz dat w WHERE = P
Ricardo C

4

Starym rozwiązaniem bez pętli / kursora jest utworzenie NUMBERStabeli, która ma jedną kolumnę typu Integer z wartościami zaczynającymi się od 1.

CREATE TABLE  `example`.`numbers` (
  `id` int(10) unsigned NOT NULL auto_increment,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Musisz wypełnić tabelę wystarczającą liczbą rekordów, aby zaspokoić Twoje potrzeby:

INSERT INTO NUMBERS (id) VALUES (NULL);

Gdy masz już NUMBERSstół, możesz użyć:

SELECT x.start_date + INTERVAL n.id-1 DAY
  FROM NUMBERS n
  JOIN (SELECT STR_TO_DATE('2010-01-20', '%Y-%m-%d') AS start_date 
          FROM DUAL) x
 WHERE x.start_date + INTERVAL n.id-1 DAY <= '2010-01-24'

Absolutnie mało zaawansowane technologicznie rozwiązanie byłoby:

SELECT STR_TO_DATE('2010-01-20', '%Y-%m-%d')
 FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-21', '%Y-%m-%d')
 FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-22', '%Y-%m-%d')
 FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-23', '%Y-%m-%d')
 FROM DUAL
UNION ALL
SELECT STR_TO_DATE('2010-01-24', '%Y-%m-%d')
 FROM DUAL

Do czego byś go użył?


Aby wygenerować listy dat lub numerów, aby LEWY DOŁĄCZ DO. Zrobiłbyś to, aby zobaczyć, gdzie są luki w danych, ponieważ LEWY JESTEŚ JOIN do listy danych sekwencyjnych - wartości zerowe pokażą, gdzie istnieją luki.


1
DUALStół jest wspierany przez Oracle i MySQL do wykorzystania jako stand-in tabeli w FROMklauzuli. Nie istnieje, wybranie z niego wartości zwróci niezależnie od wartości. Pomysł polegał na tym, aby mieć stand-in, ponieważ zapytanie SELECT wymaga FROMklauzuli określającej przynajmniej jedną tabelę.
OMG Kucyki

1
+1 do faktycznego tworzenia trwałej tabeli liczb zamiast zmuszania RDBMS do budowania jej za każdym razem, gdy potrzebujesz zapytania. Stoły pomocnicze nie są złe, ludzie!
Bacon Bits

4

W przypadku Access 2010 - wymagane jest kilka kroków; Postępowałem według tego samego wzorca, co powyżej, ale pomyślałem, że mogę komuś pomóc w programie Access. U mnie działało świetnie, nie musiałem utrzymywać rozstawionej tabeli dat.

Utwórz tabelę o nazwie DUAL (podobnie jak działa tabela Oracle DUAL)

  • ID (numer automatyczny)
  • DummyColumn (tekst)
  • Dodaj wartości jednego wiersza (1, „DummyRow”)

Utwórz zapytanie o nazwie „ZeroThru9Q”; ręcznie wprowadź następującą składnię:

SELECT 0 AS a
FROM dual
UNION ALL
SELECT 1
FROM dual
UNION ALL
SELECT 2
FROM dual
UNION ALL
SELECT 3
FROM dual
UNION ALL
SELECT 4
FROM dual
UNION ALL
SELECT 5
FROM dual
UNION ALL
SELECT 6
FROM dual
UNION ALL
SELECT 7
FROM dual
UNION ALL
SELECT 8
FROM dual
UNION ALL
SELECT 9
FROM dual;

Utwórz zapytanie o nazwie „TodayMinus1KQ” (dla dat poprzedzających dzisiejszy dzień); ręcznie wprowadź następującą składnię:

SELECT date() - (a.a + (10 * b.a) + (100 * c.a)) AS MyDate
FROM
  (SELECT *
   FROM ZeroThru9Q) AS a,

  (SELECT *
   FROM ZeroThru9Q) AS b,

  (SELECT *
   FROM ZeroThru9Q) AS c

Utwórz zapytanie o nazwie „TodayPlus1KQ” (dla dat następujących po dniu dzisiejszym); ręcznie wprowadź następującą składnię:

SELECT date() + (a.a + (10 * b.a) + (100 * c.a)) AS MyDate
FROM
  (SELECT *
   FROM ZeroThru9Q) AS a,

  (SELECT *
   FROM ZeroThru9Q) AS b,

  (SELECT *
   FROM ZeroThru9Q) AS c;

Utwórz zapytanie składające o nazwie „TodayPlusMinus1KQ” (dla dat +/- 1000 dni):

SELECT MyDate
FROM TodayMinus1KQ
UNION
SELECT MyDate
FROM TodayPlus1KQ;

Teraz możesz użyć zapytania:

SELECT MyDate
FROM TodayPlusMinus1KQ
WHERE MyDate BETWEEN #05/01/2014# and #05/30/2014#

3

Procedura + stół tymczasowy:

DELIMITER $$

CREATE DEFINER=`root`@`localhost` PROCEDURE `days`(IN dateStart DATE, IN dateEnd DATE)
BEGIN

    CREATE TEMPORARY TABLE IF NOT EXISTS date_range (day DATE);

    WHILE dateStart <= dateEnd DO
      INSERT INTO date_range VALUES (dateStart);
      SET dateStart = DATE_ADD(dateStart, INTERVAL 1 DAY);
    END WHILE;

    SELECT * FROM date_range;
    DROP TEMPORARY TABLE IF EXISTS date_range;

END

3

thx Pentium10 - zmusiłeś mnie do dołączenia do stackoverflow :) - to jest mój portowanie na msaccess - myślę, że zadziała na każdej wersji:

SELECT date_value
FROM (SELECT a.espr1+(10*b.espr1)+(100*c.espr1) AS integer_value,
dateadd("d",integer_value,dateserial([start_year], [start_month], [start_day])) as date_value
FROM (select * from 
    (
    select top 1 "0" as espr1 from MSysObjects
    union all
    select top 1 "1" as espr2 from MSysObjects
    union all
    select top 1 "2" as espr3 from MSysObjects
    union all
    select top 1 "3" as espr4 from MSysObjects
    union all
    select top 1 "4" as espr5 from MSysObjects
    union all
    select top 1 "5" as espr6 from MSysObjects
    union all
    select top 1 "6" as espr7 from MSysObjects
    union all
    select top 1 "7" as espr8 from MSysObjects
    union all
    select top 1 "8" as espr9 from MSysObjects
    union all
    select top 1 "9" as espr9 from MSysObjects
    ) as a,
    (
    select top 1 "0" as espr1 from MSysObjects
    union all
    select top 1 "1" as espr2 from MSysObjects
    union all
    select top 1 "2" as espr3 from MSysObjects
    union all
    select top 1 "3" as espr4 from MSysObjects
    union all
    select top 1 "4" as espr5 from MSysObjects
    union all
    select top 1 "5" as espr6 from MSysObjects
    union all
    select top 1 "6" as espr7 from MSysObjects
    union all
    select top 1 "7" as espr8 from MSysObjects
    union all
    select top 1 "8" as espr9 from MSysObjects
    union all
    select top 1 "9" as espr9 from MSysObjects
    ) as b,
    (
    select top 1 "0" as espr1 from MSysObjects
    union all
    select top 1 "1" as espr2 from MSysObjects
    union all
    select top 1 "2" as espr3 from MSysObjects
    union all
    select top 1 "3" as espr4 from MSysObjects
    union all
    select top 1 "4" as espr5 from MSysObjects
    union all
    select top 1 "5" as espr6 from MSysObjects
    union all
    select top 1 "6" as espr7 from MSysObjects
    union all
    select top 1 "7" as espr8 from MSysObjects
    union all
    select top 1 "8" as espr9 from MSysObjects
    union all
    select top 1 "9" as espr9 from MSysObjects
    ) as c   
)  as d) 
WHERE date_value 
between dateserial([start_year], [start_month], [start_day]) 
and dateserial([end_year], [end_month], [end_day]);

przywoływane MSysObjects po prostu „ponieważ dostęp wymaga tabeli liczącej” co najmniej 1 rekord, w klauzuli from - wystarczyłaby każda tabela z co najmniej 1 rekordem.


2

Jak stwierdzono (lub przynajmniej wspominano) w wielu cudownych odpowiedziach już udzielonych, problem ten można łatwo rozwiązać, gdy masz zestaw liczb do pracy.

Uwaga: Poniżej znajduje się T-SQL, ale jest to po prostu moja konkretna implementacja ogólnych pojęć, o których już tu wspomniano i ogólnie w Internecie. Przekształcenie kodu na wybrany dialekt powinno być stosunkowo proste.

W jaki sposób? Rozważ to zapytanie:

SELECT DATEADD(d, N, '0001-01-22')
FROM Numbers -- A table containing the numbers 0 through N
WHERE N <= 5;

Powyższe daje zakres dat od 22.01.10001 do 27.01.10001 i jest niezwykle trywialne. Istnieją 2 kluczowe fragmenty informacji w powyższej zapytania: the data rozpoczęcia of 0001-01-22a przesunięcie z 5. Jeśli połączymy te dwie informacje, to oczywiście mamy datę zakończenia. Zatem biorąc pod uwagę dwie daty, generowanie zakresu można podzielić w następujący sposób:

  • Znajdź różnicę między dwiema podanymi datami (przesunięcie), łatwo:

    -- Returns 125 SELECT ABS(DATEDIFF(d, '2014-08-22', '2014-12-25'))

    Użycie ABS()tutaj gwarantuje, że kolejność dat jest nieistotna.

  • Wygeneruj ograniczony zestaw liczb, również łatwy:

    -- Returns the numbers 0-2 SELECT N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1 FROM(SELECT 'A' AS S UNION ALL SELECT 'A' UNION ALL SELECT 'A')

    Zauważ, że tak naprawdę nie obchodzi nas, co tutaj wybieramy FROM. Potrzebujemy tylko zestawu do pracy, aby policzyć liczbę wierszy w nim. Osobiście używam TVF, niektórzy używają CTE, inni zamiast tego używają tabeli liczb, masz pomysł. Opowiadam się za użyciem najbardziej wydajnego rozwiązania, które również rozumiesz.

Połączenie tych dwóch metod rozwiąże nasz problem:

DECLARE @date1 DATE = '9001-11-21';
DECLARE @date2 DATE = '9001-11-23';

SELECT D = DATEADD(d, N, @date1)
FROM (
    SELECT N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1
    FROM (SELECT 'A' AS S UNION ALL SELECT 'A' UNION ALL SELECT 'A') S
) Numbers
WHERE N <= ABS(DATEDIFF(d, @date1, @date2));

Powyższy przykład to okropny kod, ale pokazuje, jak wszystko się składa.

Więcej zabawy

Muszę dużo robić tego typu rzeczy, więc zawarłem logikę w dwóch TVF. Pierwsza generuje zakres liczb, a druga używa tej funkcji do generowania zakresu dat. Matematyka polega na tym, że kolejność wprowadzania nie ma znaczenia i ponieważ chciałem użyć pełnego zakresu liczb dostępnych w GenerateRangeSmallInt.

Poniższa funkcja potrzebuje ~ 16 ms czasu procesora, aby zwrócić maksymalny zakres 65536 dat.

CREATE FUNCTION dbo.GenerateRangeDate (   
    @date1 DATE,   
    @date2 DATE   
)   
RETURNS TABLE
WITH SCHEMABINDING   
AS   
RETURN (
    SELECT D = DATEADD(d, N + 32768, CASE WHEN @date1 <= @date2 THEN @date1 ELSE @date2 END)
    FROM dbo.GenerateRangeSmallInt(-32768, ABS(DATEDIFF(d, @date1, @date2)) - 32768)
);

GO

CREATE FUNCTION dbo.GenerateRangeSmallInt (
    @num1 SMALLINT = -32768
  , @num2 SMALLINT = 32767
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
    WITH Numbers(N) AS (
        SELECT N FROM(VALUES
            (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 16
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 32
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 48
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 64
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 80
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 96
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 112
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 128
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 144
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 160
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 176
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 192
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 208
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 224
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 240
          , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 256
        ) V (N)
    )
    SELECT TOP(ABS(CAST(@num1 AS INT) - CAST(@num2 AS INT)) + 1)
           N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + CASE WHEN @num1 <= @num2 THEN @num1 ELSE @num2 END - 1
    FROM Numbers A
       , Numbers B
);

2

Spróbuj tego.

SELECT TO_DATE('20160210','yyyymmdd') - 1 + LEVEL AS start_day 
from DUAL
connect by level <= (TO_DATE('20160228','yyyymmdd') + 1) - TO_DATE('20160210','yyyymmdd') ;

2

Chciałbyś uzyskać zakres dat.

W Twoim przykładzie chcesz uzyskać daty między „2010-01-20” a „2010-01-24”

możliwe rozwiązanie:

 select date_add('2010-01-20', interval row day) from
 ( 
    SELECT @row := @row + 1 as row FROM 
    (select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t,
    (select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2, 
    (select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3, 
    (select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4, 
    (SELECT @row:=-1) r
 ) sequence
 where date_add('2010-01-20', interval row day) <= '2010-01-24'

Wyjaśnienie

MySQL ma funkcję date_add, więc

select date_add('2010-01-20', interval 1 day)

da tobie

2010-01-21

Funkcja datediff często informowałaby Cię, że będziesz musiał to powtarzać

select datediff('2010-01-24', '2010-01-20')

który powraca

 4

Uzyskanie listy dat w zakresie dat sprowadza się do utworzenia sekwencji liczb całkowitych zobacz Generowanie sekwencji liczb całkowitych w MySQL

Najbardziej pozytywna odpowiedź tutaj przyjęła podobne podejście, jak https://stackoverflow.com/a/2652051/1497139 jako podstawa:

SELECT @row := @row + 1 as row FROM 
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2, 
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3, 
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4, 
(SELECT @row:=0) r
limit 4

co spowoduje

row
1.0
2.0
3.0
4.0

Wiersze mogą teraz służyć do tworzenia listy dat od podanej daty rozpoczęcia. Aby uwzględnić datę rozpoczęcia, zaczynamy od wiersza -1;

select date_add('2010-01-20', interval row day) from
 ( 
    SELECT @row := @row + 1 as row FROM 
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t,
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2, 
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3, 
    (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4, 
    (SELECT @row:=-1) r
 ) sequence
 where date_add('2010-01-20', interval row day) <= '2010-01-24'

1

jeśli kiedykolwiek będziesz potrzebować więcej niż kilka dni, potrzebujesz stołu.

Utwórz zakres dat w mysql

następnie,

select from days.day, count(mytable.field) as fields from days left join mytable on day=date where date between x and y;

3
dlaczego to opublikowałeś, skoro powyższa odpowiedź nie wymaga tabeli i dostarcza rozwiązania?
Pentium

1

Generuj daty między dwoma polami daty

Jeśli znasz zapytanie SQL CTE, to rozwiązanie pomoże ci rozwiązać twoje pytanie

Oto przykład

Mamy daty w jednej tabeli

Nazwa tabeli: „testdate”

STARTDATE   ENDDATE
10/24/2012  10/24/2012
10/27/2012  10/29/2012
10/30/2012  10/30/2012

Wymagaj wyniku:

STARTDATE
10/24/2012
10/27/2012
10/28/2012
10/29/2012
10/30/2012

Rozwiązanie:

WITH CTE AS
  (SELECT DISTINCT convert(varchar(10),StartTime, 101) AS StartTime,
                   datediff(dd,StartTime, endTime) AS diff
   FROM dbo.testdate
   UNION ALL SELECT StartTime,
                    diff - 1 AS diff
   FROM CTE
   WHERE diff<> 0)
SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime
FROM CTE

Objaśnienie: Wyjaśnienie rekursywnego zapytania CTE

  • Pierwsza część zapytania:

    SELECT DISTINCT convert(varchar(10), StartTime, 101) AS StartTime, datediff(dd, StartTime, endTime) AS diff FROM dbo.testdate

    Wyjaśnienie: pierwsza kolumna to „data początkowa”, druga kolumna to różnica dat rozpoczęcia i zakończenia w dniach i będzie traktowana jako kolumna „diff”

  • Druga część zapytania:

    UNION ALL SELECT StartTime, diff-1 AS diff FROM CTE WHERE diff<>0

    Objaśnienie: Union all będzie dziedziczyć wynik powyższego zapytania, dopóki wynik nie osiągnie wartości null, więc wynik „StartTime” jest dziedziczony z wygenerowanego zapytania CTE, a z diff, zmniejsz - 1, więc wygląda na 3, 2 i 1 do 0

Na przykład

STARTDATE   DIFF
10/24/2012  0
10/27/2012  0
10/27/2012  1
10/27/2012  2
10/30/2012  0

Specyfikacja wyników

STARTDATE       Specification
10/24/2012  --> From Record 1
10/27/2012  --> From Record 2
10/27/2012  --> From Record 2
10/27/2012  --> From Record 2
10/30/2012  --> From Record 3
  • Trzecia część zapytania

    SELECT DISTINCT DateAdd(dd,diff, StartTime) AS StartTime FROM CTE

    Doda dzień „diff” do „startdate”, więc wynik powinien być taki jak poniżej

Wynik

STARTDATE
10/24/2012
10/27/2012
10/28/2012
10/29/2012
10/30/2012

1

Krótsza niż zaakceptowana odpowiedź, ten sam pomysł:

(SELECT TRIM('2016-01-05' + INTERVAL a + b DAY) date
FROM
(SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7
UNION SELECT 8 UNION SELECT 9 ) d,
(SELECT 0 b UNION SELECT 10 UNION SELECT 20
UNION SELECT 30 UNION SELECT 40) m
WHERE '2016-01-05' + INTERVAL a + b DAY  <=  '2016-01-21')

1

Dla każdego, kto chce, aby był to zapisany widok (MySQL nie obsługuje zagnieżdżonych instrukcji Select w widokach):

create view zero_to_nine as
    select 0 as n union all 
    select 1 union all 
    select 2 union all 
    select 3 union all 
    select 4 union all 
    select 5 union all 
    select 6 union all 
    select 7 union all 
    select 8 union all 
    select 9;

create view date_range as
    select curdate() - INTERVAL (a.n + (10 * b.n) + (100 * c.n)) DAY as date
    from zero_to_nine as a
    cross join zero_to_nine as b
    cross join zero_to_nine as c;

Możesz to zrobić

select * from date_range

dostać

date
---
2017-06-06
2017-06-05
2017-06-04
2017-06-03
2017-06-02
...

1

Eleganckie rozwiązanie wykorzystujące nową funkcję rekurencyjną (Common Table Expressions) w MariaDB> = 10.3 i 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;

Powyższe zwraca tabelę dat od „2019-01-01” do „2019-04-30”. Jest też przyzwoicie szybki. Zwracanie dat z 1000 lat (~ 365 000 dni) na moim komputerze zajmuje około 400 ms.


1

Dobrym pomysłem jest generowanie tych dat w locie. Jednak nie czuję się komfortowo, robiąc to przy dość dużym zasięgu, więc otrzymałem następujące rozwiązanie:

  1. Utworzono tabelę „DatesNumbers”, w której będą przechowywane liczby używane do obliczania dat:
CREATE TABLE DatesNumbers (
    i MEDIUMINT NOT NULL,
    PRIMARY KEY (i)
)
COMMENT='Used by Dates view'
;
  1. Wypełniono tabelę przy użyciu powyższych technik liczbami od -59999 do 40000. Ten zakres da mi daty od 59999 dni (~ 164 lat) do 40000 dni (109 lat) do przodu:
INSERT INTO DatesNumbers
SELECT 
    a.i + (10 * b.i) + (100 * c.i) + (1000 * d.i) + (10000 * e.i) - 59999 AS i
FROM 
  (SELECT 0 AS i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS a,
  (SELECT 0 AS i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS b,
  (SELECT 0 AS i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS c,
  (SELECT 0 AS i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS d,
  (SELECT 0 AS i UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS e
;
  1. Utworzono widok „Daty”:
SELECT
      i,
      CURRENT_DATE() + INTERVAL i DAY AS Date
FROM
    DatesNumbers

Otóż ​​to.

  • (+) Łatwe do odczytania zapytania
  • (+) Nie ma w locie liczb pokoleń
  • (+) Podaje daty w przeszłości i przyszłości i nie ma na to UNII, jak w tym poście .
  • (+) Daty „tylko w przeszłości” lub „tylko w przyszłości” można filtrować za pomocą WHERE i < 0lub WHERE i > 0(PK)
  • (-) „tymczasowa” tabela i widok jest używany

0

W porządku .. Spróbuj tego: http://www.devshed.com/c/a/MySQL/Delving-Deeper-into-MySQL-50/
http://dev.mysql.com/doc/refman/5.0/en/ loop-statement.html
http://www.roseindia.net/sql/mysql-example/mysql-loop.shtml

Użyj tego do, powiedzmy, wygenerowania tabeli tymczasowej, a następnie wybierz * na tabeli tymczasowej. Lub wyświetlaj wyniki pojedynczo.
To, co mówisz, że chcesz zrobić, nie może zostać wykonane za pomocą instrukcji SELECT , ale może to być wykonalne w przypadku rzeczy specyficznych dla MySQL.
Z drugiej strony, może potrzebujesz kursorów: http://dev.mysql.com/doc/refman/5.0/en/cursors.html


0

W przypadku Oracle moje rozwiązanie to:

select trunc(sysdate-dayincrement, 'DD') 
  from dual, (select level as dayincrement 
                from dual connect by level <= 30)

Sysdate można zmienić na określoną datę, a numer poziomu można zmienić, aby podać więcej dat.


0

jeśli chcesz listę dat między dwoma datami:

create table #dates ([date] smalldatetime)
while @since < @to
begin
     insert into #dates(dateadd(day,1,@since))
     set @since = dateadd(day,1,@since)
end
select [date] from #dates

* fiddle tutaj: http://sqlfiddle.com/#!6/9eecb/3469


0
set language  'SPANISH'
DECLARE @table table(fechaDesde datetime , fechaHasta datetime ) 
INSERT @table VALUES('20151231' , '20161231');
WITH x AS 
    (
        SELECT   DATEADD( m , 1 ,fechaDesde ) as fecha  FROM @table
        UNION ALL
        SELECT  DATEADD( m , 1 ,fecha )
        FROM @table t INNER JOIN x ON  DATEADD( m , 1 ,x.fecha ) <= t.fechaHasta
    )
SELECT LEFT( CONVERT( VARCHAR, fecha , 112 ) , 6 ) as Periodo_Id 
,DATEPART ( dd, DATEADD(dd,-(DAY(fecha)-1),fecha)) Num_Dia_Inicio
,DATEADD(dd,-(DAY(fecha)-1),fecha) Fecha_Inicio
,DATEPART ( mm , fecha ) Mes_Id
,DATEPART ( yy , fecha ) Anio
,DATEPART ( dd, DATEADD(dd,-(DAY(DATEADD(mm,1,fecha))),DATEADD(mm,1,fecha))) Num_Dia_Fin
,DATEADD(dd,-(DAY(DATEADD(mm,1,fecha))),DATEADD(mm,1,fecha)) ultimoDia
,datename(MONTH, fecha) mes
,'Q' + convert(varchar(10),  DATEPART(QUARTER, fecha)) Trimestre_Name
FROM x 
OPTION(MAXRECURSION 0)

0
DELIMITER $$
CREATE PROCEDURE GenerateRangeDates(IN dateStart DATE, IN dateEnd DATE)
BEGIN

    CREATE TEMPORARY TABLE IF NOT EXISTS dates (day DATE);

    loopDate: LOOP
        INSERT INTO dates(day) VALUES (dateStart); 
        SET dateStart = DATE_ADD(dateStart, INTERVAL 1 DAY);

        IF dateStart <= dateEnd 
            THEN ITERATE loopDate;
            ELSE LEAVE loopDate;
        END IF;
    END LOOP loopDate;

    SELECT day FROM dates;
    DROP TEMPORARY TABLE IF EXISTS dates;

END 
$$

-- Call procedure
call GenerateRangeDates( 
        now() - INTERVAL 40 DAY,
        now()
    );

0

Wersja SQLite topowego rozwiązania RedFilters

select d.Date
from (
    select 
    date(julianday('2010-01-20') + (a.a + (10 * b.a) + (100 * c.a))) as Date
    from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) d
where 
d.Date between '2010-01-20' and '2010-01-24' 
order by d.Date

0

ulepszone z dniem tygodnia i dołączeniem do niestandardowego stołu świątecznego Microsoft MSSQL 2012 dla tabeli dat powerpivot https://gist.github.com/josy1024/cb1487d66d9e0ccbd420bc4a23b6e90e

with [dates] as (
    select convert(datetime, '2016-01-01') as [date] --start
    union all
    select dateadd(day, 1, [date])
    from [dates]
    where [date] < '2018-01-01' --end
)
select [date]
, DATEPART (dw,[date]) as Wochentag
, (select holidayname from holidaytable 
where holidaytable.hdate = [date]) 
as Feiertag
from [dates]
where [date] between '2016-01-01' and '2016-31-12'
option (maxrecursion 0)

0
WITH
  Digits AS (SELECT 0 D UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9),
  Dates AS (SELECT adddate('1970-01-01',t4.d*10000 + t3.d*1000 + t2.d*100 + t1.d*10 +t0.d) AS date FROM Digits AS t0, Digits AS t1, Digits AS t2, Digits AS t3, Digits AS t4)
SELECT * FROM Dates WHERE date BETWEEN '2017-01-01' AND '2017-12-31'

0

Potrafi stworzyć procedurę tworzenia tabeli kalendarza z mapą czasu inną niż dzień. Jeśli chcesz mieć stół na każdy kwartał

na przykład

2019-01-22 08:45:00
2019-01-22 09:00:00
2019-01-22 09:15:00
2019-01-22 09:30:00
2019-01-22 09:45:00
2019-01-22 10:00:00

możesz użyć

CREATE DEFINER=`root`@`localhost` PROCEDURE `generate_calendar_table`()
BEGIN

select unix_timestamp('2014-01-01 00:00:00') into @startts;
select unix_timestamp('2025-01-01 00:00:00') into @endts;

if ( @startts < @endts ) then

    DROP TEMPORARY TABLE IF EXISTS calendar_table_tmp;

    CREATE TEMPORARY TABLE calendar_table_tmp (ts int, dt datetime); 

    WHILE ( @startts < @endts)
        DO 
        SET @startts = @startts + 900;
        INSERT calendar_table_tmp VALUES (@startts, from_unixtime(@startts));
    END WHILE;

END if;

END

a następnie manipulować

select ts, dt from calendar_table_tmp;

które również dają ts

'1548143100', '2019-01-22 08:45:00'
'1548144000', '2019-01-22 09:00:00'
'1548144900', '2019-01-22 09:15:00'
'1548145800', '2019-01-22 09:30:00'
'1548146700', '2019-01-22 09:45:00'
'1548147600', '2019-01-22 10:00:00'

stąd możesz zacząć dodawać inne informacje, takie jak

select ts, dt, weekday(dt) as wd from calendar_table_tmp;

lub utwórz prawdziwą tabelę za pomocą instrukcji create table


0

Bardziej ogólna odpowiedź, która działa w AWS MySQL.

select datetable.Date
from (
    select date_format(adddate(now(),-(a.a + (10 * b.a) + (100 * c.a))),'%Y-%m-%d') AS Date
    from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
     union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a

    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
     union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b

    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
     union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) datetable
where datetable.Date between now() - INTERVAL 14 Day and Now()
order by datetable.Date DESC

-1

Jeszcze jedno rozwiązanie dla mysql 8.0.1 i mariadb 10.2.2 wykorzystujące rekurencyjne wspólne wyrażenia tabelowe:

with recursive dates as (
    select '2010-01-20' as date
    union all
    select date + interval 1 day from dates where date < '2010-01-24'
)
select * from dates;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.