Ogólny pomysł
Istnieją dwa główne podejścia do generowania danych w MySQL. Jedną z nich jest generowanie danych w locie podczas uruchamiania kwerendy, a druga to przechowywanie danych w bazie danych i używanie jej w razie potrzeby. Oczywiście drugi będzie szybszy niż pierwszy, jeśli często będziesz przeprowadzać zapytanie. Jednak drugi będzie wymagał tabeli w bazie danych, której jedynym celem będzie generowanie brakujących danych. Będzie również wymagać posiadania uprawnień wystarczających do utworzenia tego stołu.
Dynamiczne generowanie danych
Takie podejście wymaga podejmowania UNION
s wygenerować fałszywy stolik, który można wykorzystać do przyłączenia się do rzeczywistej tabeli z. Straszliwe i powtarzające się zapytanie to:
select aDate from (
select @maxDate - interval (a.a+(10*b.a)+(100*c.a)+(1000*d.a)) day aDate 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) a, /*10 day range*/
(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) b, /*100 day range*/
(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) c, /*1000 day range*/
(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) d, /*10000 day range*/
(select @minDate := '2001-01-01', @maxDate := '2002-02-02') e
) f
where aDate between @minDate and @maxDate
W każdym razie jest to prostsze, niż się wydaje. Tworzy on kartezjańskie produkty tabel pochodnych z wartościami liczbowymi 10
, więc wynik będzie miał 10^X
wierszy, gdzie X
jest ilością tabel pochodnych w zapytaniu. W tym przykładzie istnieje zakres dnia wynoszący 10000
, więc możesz reprezentować okresy o wartości ponad 27
lat. Jeśli potrzebujesz więcej, dodaj kolejne zapytanie UNION
do kwerendy i zaktualizuj interwał, a jeśli nie potrzebujesz tak wielu, możesz usunąć wartości UNION
s lub poszczególne wartości z wyprowadzonych tabel. Aby wyjaśnić, możesz dostosować okres daty, stosując filtr z klauzulą WHERE
na zmiennych @minDate
i @maxDate
(ale nie używaj dłuższego okresu niż ten, który stworzyłeś z produktami kartezjańskimi).
Static generowania danych
To rozwiązanie będzie wymagać, aby wygenerować tabelę w bazie danych. Podejście jest podobne do poprzedniego. Najpierw musisz wstawić dane do tej tabeli: zakres liczb całkowitych od 1
do X
, gdzie X
to maksymalny wymagany zakres.Ponownie, jeśli nie masz pewności, po prostu wstaw wartości 100000
, a będziesz mógł tworzyć przedziały dzienne dla ponad 273
lat. Tak, raz masz sekwencję liczb całkowitych, można przekształcić go w przedziale czasowym tak:
select '2012-01-01' + interval value - 1 day aDay from seq
having aDay <= '2012-01-05'
Zakładając tabelę o nazwie seq
z kolumną o nazwie value
. Na górze od daty i na dole daty do.
skrętu to w coś użytecznego
Ok, teraz mamy nasze okresy data generowane ale my wciąż brakuje sposób do kwerendy danych i wyświetlania brakujących wartości jako rzeczywisty 0
. Tutaj na ratunek przychodzi left join
. Aby upewnić się, że wszyscy jesteśmy na tej samej stronie, numer left join
jest podobny do inner join
, ale z jedną różnicą: zachowa wszystkie rekordy z lewej tabeli łączenia, niezależnie od tego, czy istnieje pasujący rekord w tabeli prawo. Innymi słowy, inner join
usunie wszystkie nie dopasowane wiersze na łączeniu, podczas gdy left join
zachowa te z lewej tabeli, a dla rekordów po lewej, które nie mają pasujących rekordów na prawym stole, left join
wypełni to. "spacja" z wartością null
.
Tak więc powinniśmy dołączyć do naszej tabeli domen (tej, która ma "brakujące" dane) z nowo wygenerowaną tabelą umieszczając tę ostatnią po lewej stronie połączenia, a pierwszą po prawej, aby wszystkie elementy były brane pod uwagę, niezależnie od ich obecności w tabeli domeny.
Na przykład, jeśli mieliśmy stolik domainTable
z polami ID, birthDate
i chcielibyśmy, aby zobaczyć liczbę wszystkich birthDate
w pierwszych 5
dni 2012
dziennie i jeśli liczba jest 0
pokazać tę wartość, to zapytanie może być prowadzony:
select allDays.aDay, count(dt.id) from (
select '2012-01-01' + interval value - 1 day aDay from seq
having aDay <= '2012-01-05'
) allDays
left join domainTable dt on allDays.aDay = dt.birthDate
group by allDays.aDay
ten generuje tabelę pochodzącą ze wszystkimi wymagane na dniach (zauważ używam statyczną generowanie danych) i wykonuje left join
przed naszym stole domeny, więc zostaną wyświetlone wszystkie dni, niezależnie czy mają pasujące wartości w naszych tabelach domeny. Zauważ także, że count
należy wykonać na polu, które będzie miało wartości null
, ponieważ te nie są zliczane.
Uwagi należy uznać
1) Zapytania mogą być używane do zapytań innych odstępach czasu (miesiące, lata) wykonywanie drobnych zmian w kodzie
2) zamiast sztywno daty można wyszukać dla min
i max
wartości z tabel domen tak:
select (select min(aDate) from domainTable) + interval value - 1 day aDay
from seq
having aDay <= (select max(aDate) from domainTable)
Pozwoliłoby to uniknąć generowania więcej rekordów niż to konieczne.
rzeczywistości odpowiadając na pytanie
myślę, że powinien już zorientowali się, jak to zrobić, co chcesz. W każdym razie, oto kroki, aby inni mogli z nich skorzystać.Najpierw utwórz tabelę całkowitą . Po drugie, należy uruchomić tę kwerendę:
select allDays.aDay, count(mt.id) aCount from (
select (select date(min(created_at)) from my_table) + interval value - 1 day aDay
from seq s
having aDay <= (select date(max(created_at)) from my_table)
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay
Chyba created_at
jest datetime i dlatego jesteś łącząc w ten sposób. Jednak tak się dzieje, ponieważ MySQL natywnie przechowuje daty, więc po prostu grupuję według pola daty, ale przesyłając created_at
do rzeczywistego typu danych date
. Możesz grać z nim za pomocą tego fiddle.
A oto dane rozwiązanie generujące dynamicznie:
select allDays.aDay, count(mt.id) aCount from (
select @maxDate - interval a.a day aDay 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) a, /*10 day range*/
(select @minDate := (select date(min(created_at)) from my_table),
@maxDate := (select date(max(created_at)) from my_table)) e
where @maxDate - interval a.a day between @minDate and @maxDate
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay
Jak widać szkielet zapytania jest taki sam jak poprzedni. Jedyną zmianą jest generowanie wygenerowanej tabeli allDays
. Teraz sposób generowania tabeli pochodnej również nieznacznie różni się od tego, który dodałem wcześniej. Dzieje się tak dlatego, że w przykładowej filddle potrzebowałem tylko 10
-dniowego zasięgu. Jak widać, jest bardziej czytelny niż dodanie zakresu dziennego 1000
. Oto fiddle dla dynamicznego rozwiązania, abyś mógł z nim grać.
Mam nadzieję, że to pomoże!
To jest naprawdę jedyna opcja, chyba że jesteś w stanie utworzyć brakujące wpisy w swoim kodzie po sele podsumowanie posiadanych rekordów. Pamiętaj jednak, że będziesz musiał wypełniać tę datę calendar_date datami i mam nadzieję, że nie zapomnisz dodać więcej, niż aktualnie potrzebujesz. (Ile lat w przyszłości odejdziesz?) Osobiście nie podoba mi się ten pomysł, ponieważ ogranicza on również do grupowania według wybranego przedziału czasowego. Co jeśli jutro chciałbyś pokazać rzeczy pogrupowane według godziny? – Vyrotek
Aby było jasne, w rzeczywistości nie ma dobrego rozwiązania tego problemu za pomocą SQL. –
Pliki kalendarza są przydatne dla wielu rzeczy (szczególnie w sytuacjach sprzedaży detalicznej, gdzie kalendarz fiskalny nie zawsze jest odwzorowany na kalendarz gregoriański), w tym ten konkretny problem. Możesz tworzyć wirtualne w-instrukcja ... z rekurencyjnymi CTE (nieobecne w mySQL). –