Załóżmy, że mam tabelę klientów i tabelę zakupów. Każdy zakup należy do jednego klienta. Chcę uzyskać listę wszystkich klientów wraz z ich ostatnim zakupem w jednym wyciągu SELECT. Jaka jest najlepsza praktyka? Wszelkie porady dotyczące budowania indeksów?
Proszę użyć tych nazw tabel / kolumn w swojej odpowiedzi:
- klient: identyfikator, imię i nazwisko
- zakup: identyfikator, identyfikator klienta, identyfikator przedmiotu, data
A czy w bardziej skomplikowanych sytuacjach (pod względem wydajności) korzystne byłoby denormalizowanie bazy danych poprzez umieszczenie ostatniego zakupu w tabeli klientów?
Jeśli gwarantuje się, że identyfikator (zakupu) jest posortowany według daty, czy można uprościć wyciągi, używając czegoś takiego LIMIT 1
?
Odpowiedzi:
To jest przykład
greatest-n-per-group
problemu, który pojawiał się regularnie na StackOverflow.Oto jak zwykle zalecam rozwiązanie tego problemu:
Objaśnienie: w przypadku wiersza
p1
nie powinno być wierszap2
z tym samym klientem i datą późniejszą (lub w przypadku powiązań późniejsząid
). Kiedy uznamy, że to prawda,p1
jest to ostatni zakup dla tego klienta.W odniesieniu do wskaźników, by utworzyć wskaźnik związek w
purchase
ciągu kolumn (customer_id
,date
,id
). To może pozwolić na wykonanie połączenia zewnętrznego za pomocą indeksu pokrywającego. Testuj na swojej platformie, ponieważ optymalizacja zależy od implementacji. Skorzystaj z funkcji RDBMS, aby przeanalizować plan optymalizacji. Np.EXPLAIN
Na MySQL.Niektóre osoby używają podkwerend zamiast rozwiązania pokazanego powyżej, ale uważam, że moje rozwiązanie ułatwia rozwiązywanie powiązań.
źródło
Możesz także spróbować to zrobić za pomocą wyboru podrzędnego
Wybrani powinni dołączyć do wszystkich klientów i ich daty ostatniego zakupu.
źródło
INNER JOIN
DoLEFT OUTER JOIN
.purchase
tabeli, są data i identyfikator_użytkownika, ale zapytanie wymaga podania wszystkich pól z tabeli.Nie określono bazy danych. Jeśli jest taka, która pozwala na funkcje analityczne, może być szybsze zastosowanie tego podejścia niż GROUP BY (zdecydowanie szybsze w Oracle, najprawdopodobniej szybsze w późnych edycjach SQL Server, nie wiem o innych).
Składnia w SQL Server to:
źródło
Innym podejściem byłoby użycie
NOT EXISTS
warunku w warunku łączenia w celu przetestowania pod kątem późniejszych zakupów:źródło
AND NOT EXISTS
część prostymi słowami?Znalazłem ten wątek jako rozwiązanie mojego problemu.
Ale kiedy spróbowałem, wydajność była niska. Poniżej moja sugestia dotycząca lepszej wydajności.
Mam nadzieję, że to będzie pomocne.
źródło
top 1
iordered it by
MaxDatedesc
Jeśli używasz PostgreSQL, możesz użyć
DISTINCT ON
do znalezienia pierwszego wiersza w grupie.Dokumenty PostgreSQL - Wyraźnie włączone
Zauważ, że
DISTINCT ON
pola (pola) - tutajcustomer_id
- muszą pasować do pól znajdujących się najbardziej po lewej stronie wORDER BY
klauzuli.Zastrzeżenie: jest to niestandardowa klauzula.
źródło
Spróbuj tego, to pomoże.
Użyłem tego w moim projekcie.
źródło
Testowane na SQLite:
Funkcja
max()
agregująca upewni się, że z każdej grupy wybrano ostatni zakup (ale zakłada, że kolumna daty ma format, w którym max () podaje ostatnie - co zwykle ma miejsce). Jeśli chcesz obsługiwać zakupy z tą samą datą, możesz użyćmax(p.date, p.id)
.Jeśli chodzi o indeksy, użyłbym indeksu przy zakupie z (identyfikator klienta, data, [dowolne inne kolumny zakupów, które chcesz zwrócić w wybranym]).
LEFT OUTER JOIN
( W przeciwieństwie doINNER JOIN
) zapewni uwzględnienie również klientów, którzy nigdy nie dokonali zakupu.źródło
Spróbuj tego,
źródło