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 Datefrom(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as acrossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as bcrossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as ccrossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as d) awhere a.Date between'2010-01-20'and'2010-01-24'
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:
CREATEVIEW digits ASSELECT0AS digit UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9;CREATEVIEW numbers ASSELECT
ones.digit + tens.digit *10+ hundreds.digit *100+ thousands.digit *1000AS numberFROM
digits as ones,
digits as tens,
digits as hundreds,
digits as thousands;CREATEVIEW dates ASSELECT
SUBDATE(CURRENT_DATE(), number)AS dateFROM
numbers;
A potem możesz po prostu zrobić (zobacz, jakie to eleganckie?):
SELECT
dateFROM
datesWHERE
date BETWEEN'2010-01-20'AND'2010-01-24'ORDERBY
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:
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 .:
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.
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.
Musisz wypełnić tabelę wystarczającą liczbą rekordów, aby zaspokoić Twoje potrzeby:
INSERTINTO 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:
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.
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ę:
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(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as a,(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"9"as espr9 from MSysObjects
)as b,(selecttop1"0"as espr1 from MSysObjects
unionallselecttop1"1"as espr2 from MSysObjects
unionallselecttop1"2"as espr3 from MSysObjects
unionallselecttop1"3"as espr4 from MSysObjects
unionallselecttop1"4"as espr5 from MSysObjects
unionallselecttop1"5"as espr6 from MSysObjects
unionallselecttop1"6"as espr7 from MSysObjects
unionallselecttop1"7"as espr8 from MSysObjects
unionallselecttop1"8"as espr9 from MSysObjects
unionallselecttop1"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.
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 NWHERE 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:
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(ORDERBY(SELECTNULL))-1FROM(SELECT'A'AS S UNIONALLSELECT'A'UNIONALLSELECT'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.
CREATEFUNCTION dbo.GenerateRangeDate (@date1 DATE,@date2 DATE
)
RETURNS TABLEWITH SCHEMABINDING
ASRETURN(SELECT D = DATEADD(d, N +32768,CASEWHEN@date1 <=@date2 THEN@date1 ELSE@date2 END)FROM dbo.GenerateRangeSmallInt(-32768, ABS(DATEDIFF(d,@date1,@date2))-32768));
GO
CREATEFUNCTION dbo.GenerateRangeSmallInt (@num1 SMALLINT =-32768,@num2 SMALLINT =32767)
RETURNS TABLEWITH SCHEMABINDING
ASRETURN(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))SELECTTOP(ABS(CAST(@num1 AS INT)- CAST(@num2 AS INT))+1)
N = ROW_NUMBER()OVER(ORDERBY(SELECTNULL))+CASEWHEN@num1 <=@num2 THEN@num1 ELSE@num2 END-1FROM Numbers A
, Numbers B
);
WITH CTE AS(SELECTDISTINCTconvert(varchar(10),StartTime,101)AS StartTime,
datediff(dd,StartTime, endTime)AS diff
FROM dbo.testdate
UNIONALLSELECT StartTime,
diff -1AS diff
FROM CTE
WHERE diff<>0)SELECTDISTINCT DateAdd(dd,diff, StartTime)AS StartTime
FROM CTE
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
STARTDATE Specification
10/24/2012--> From Record 110/27/2012--> From Record 210/27/2012--> From Record 210/27/2012--> From Record 210/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
Krótsza niż zaakceptowana odpowiedź, ten sam pomysł:
(SELECT TRIM('2016-01-05'+ INTERVAL a + b DAY) date
FROM(SELECT0 a UNIONSELECT1 a UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5UNIONSELECT6UNIONSELECT7UNIONSELECT8UNIONSELECT9) d,(SELECT0 b UNIONSELECT10UNIONSELECT20UNIONSELECT30UNIONSELECT40) m
WHERE'2016-01-05'+ INTERVAL a + b DAY <='2016-01-21')
Dla każdego, kto chce, aby był to zapisany widok (MySQL nie obsługuje zagnieżdżonych instrukcji Select w widokach):
createview zero_to_nine asselect0as n unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9;createview date_range asselect curdate()- INTERVAL (a.n +(10* b.n)+(100* c.n)) DAY as date
from zero_to_nine as a
crossjoin zero_to_nine as b
crossjoin zero_to_nine as c;
Możesz to zrobić
select*from date_range
dostać
date
---2017-06-062017-06-052017-06-042017-06-032017-06-02...
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
UNIONSELECT 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.
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:
Utworzono tabelę „DatesNumbers”, w której będą przechowywane liczby używane do obliczania dat:
CREATETABLE DatesNumbers (
i MEDIUMINT NOTNULL,PRIMARYKEY(i))
COMMENT='Used by Dates view';
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:
INSERTINTO DatesNumbers
SELECT
a.i +(10* b.i)+(100* c.i)+(1000* d.i)+(10000* e.i)-59999AS i
FROM(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS a,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS b,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS c,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS d,(SELECT0AS i UNIONALLSELECT1UNIONALLSELECT2UNIONALLSELECT3UNIONALLSELECT4UNIONALLSELECT5UNIONALLSELECT6UNIONALLSELECT7UNIONALLSELECT8UNIONALLSELECT9)AS e
;
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)
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
set language 'SPANISH'DECLARE@tabletable(fechaDesde datetime , fechaHasta datetime )INSERT@tableVALUES('20151231','20161231');WITH x AS(SELECT DATEADD( m ,1,fechaDesde )as fecha FROM@tableUNIONALLSELECT DATEADD( m ,1,fecha )FROM@table t INNERJOIN x ON DATEADD( m ,1,x.fecha )<= t.fechaHasta
)SELECTLEFT(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)
select d.Date
from(select
date(julianday('2010-01-20')+(a.a +(10* b.a)+(100* c.a)))as Date
from(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) d
where
d.Date between'2010-01-20'and'2010-01-24'orderby d.Date
WITH
Digits AS(SELECT0 D UNIONSELECT1UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5UNIONSELECT6UNIONSELECT7UNIONSELECT8UNIONSELECT9),
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'
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(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as a
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as b
crossjoin(select0as a unionallselect1unionallselect2unionallselect3unionallselect4unionallselect5unionallselect6unionallselect7unionallselect8unionallselect9)as c
) datetable
where datetable.Date between now()- INTERVAL 14 Day and Now()orderby datetable.Date DESC
insert into table select ... as days date between '' and ''
Odpowiedzi:
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.
Wynik:
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
źródło
UNION
naUNION 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?Oto kolejna odmiana wykorzystująca widoki:
A potem możesz po prostu zrobić (zobacz, jakie to eleganckie?):
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:
źródło
dates
wspomniany 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.UNION
klauzule wyglądają dziwnie w pojedynczej instrukcji SQL.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_series
funkcji, tj .:źródło
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.
W Microsoft SQL Server 2005 wygenerowanie listy CTE wszystkich możliwych dat zajęło 1:08. Wygenerowanie stu lat zajęło mniej niż sekundę.
źródło
Zapytanie MSSQL
Wynik
źródło
Starym rozwiązaniem bez pętli / kursora jest utworzenie
NUMBERS
tabeli, która ma jedną kolumnę typu Integer z wartościami zaczynającymi się od 1.Musisz wypełnić tabelę wystarczającą liczbą rekordów, aby zaspokoić Twoje potrzeby:
Gdy masz już
NUMBERS
stół, możesz użyć:Absolutnie mało zaawansowane technologicznie rozwiązanie byłoby:
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.
źródło
DUAL
Stół jest wspierany przez Oracle i MySQL do wykorzystania jako stand-in tabeli wFROM
klauzuli. 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 wymagaFROM
klauzuli określającej przynajmniej jedną tabelę.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)
Utwórz zapytanie o nazwie „ZeroThru9Q”; ręcznie wprowadź następującą składnię:
Utwórz zapytanie o nazwie „TodayMinus1KQ” (dla dat poprzedzających dzisiejszy dzień); ręcznie wprowadź następującą składnię:
Utwórz zapytanie o nazwie „TodayPlus1KQ” (dla dat następujących po dniu dzisiejszym); ręcznie wprowadź następującą składnię:
Utwórz zapytanie składające o nazwie „TodayPlusMinus1KQ” (dla dat +/- 1000 dni):
Teraz możesz użyć zapytania:
źródło
Procedura + stół tymczasowy:
źródło
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:
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.
źródło
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:
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-22
a przesunięcie z5
. 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:
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.
źródło
Spróbuj tego.
źródło
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:
Wyjaśnienie
MySQL ma funkcję date_add, więc
da tobie
Funkcja datediff często informowałaby Cię, że będziesz musiał to powtarzać
który powraca
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:
co spowoduje
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;
źródło
jeśli kiedykolwiek będziesz potrzebować więcej niż kilka dni, potrzebujesz stołu.
Utwórz zakres dat w mysql
następnie,
źródło
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”
Wymagaj wyniku:
Rozwiązanie:
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
Specyfikacja wyników
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
źródło
Krótsza niż zaakceptowana odpowiedź, ten sam pomysł:
źródło
Dla każdego, kto chce, aby był to zapisany widok (MySQL nie obsługuje zagnieżdżonych instrukcji Select w widokach):
Możesz to zrobić
dostać
źródło
Eleganckie rozwiązanie wykorzystujące nową funkcję rekurencyjną (Common Table Expressions) w MariaDB> = 10.3 i MySQL> = 8.0.
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.
źródło
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:
Otóż to.
WHERE i < 0
lubWHERE i > 0
(PK)źródło
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
źródło
W przypadku Oracle moje rozwiązanie to:
Sysdate można zmienić na określoną datę, a numer poziomu można zmienić, aby podać więcej dat.
źródło
jeśli chcesz listę dat między dwoma datami:
* fiddle tutaj: http://sqlfiddle.com/#!6/9eecb/3469
źródło
źródło
źródło
Wersja SQLite topowego rozwiązania RedFilters
źródło
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
źródło
źródło
Potrafi stworzyć procedurę tworzenia tabeli kalendarza z mapą czasu inną niż dzień. Jeśli chcesz mieć stół na każdy kwartał
na przykład
możesz użyć
a następnie manipulować
które również dają ts
stąd możesz zacząć dodawać inne informacje, takie jak
lub utwórz prawdziwą tabelę za pomocą instrukcji create table
źródło
Bardziej ogólna odpowiedź, która działa w AWS MySQL.
źródło
Jeszcze jedno rozwiązanie dla mysql 8.0.1 i mariadb 10.2.2 wykorzystujące rekurencyjne wspólne wyrażenia tabelowe:
źródło