SQL join: wybieranie ostatnich rekordów w relacji jeden do wielu
Przypuśćmy, że mam tabelę klientów i tabelę zakupów. Każdy zakup należy do jednego klienta. Chcę otrzymać listę wszystkich klientów wraz z ich ostatnim zakupem w jednym SELECT
oświadczeniu. Jaka jest najlepsza praktyka? Jakieś rady na temat indeksów budowlanych?
Proszę użyć nazw tabel/kolumn w odpowiedzi:
- klient:
id
,name
- zakup:
id
,customer_id
,item_id
,date
I w bardziej skomplikowanych sytuacjach, czy czy (pod względem wydajności) korzystne jest denormalizowanie bazy danych poprzez umieszczenie ostatniego zakupu w tabeli klientów?
Jeśli (zakup) id
jest gwarantowane, aby być sortowane według daty, można uprościć wyciągi używając czegoś takiego jak LIMIT 1
?
10 answers
Jest to przykład problemu greatest-n-per-group
, który pojawia się regularnie w StackOverflow.
Oto jak zwykle polecam rozwiązanie:
SELECT c.*, p1.*
FROM customer c
JOIN purchase p1 ON (c.id = p1.customer_id)
LEFT OUTER JOIN purchase p2 ON (c.id = p2.customer_id AND
(p1.date < p2.date OR (p1.date = p2.date AND p1.id < p2.id)))
WHERE p2.id IS NULL;
Explanation: given a row p1
, there should be no row p2
with the same customer and a later date (or in the case of ties, a later id
). Kiedy okaże się, że to prawda, to p1
jest ostatnim zakupem dla tego klienta.
Jeśli chodzi o indeksy, utworzyłbym indeks złożony w purchase
nad kolumnami (customer_id
, date
, id
). Pozwala to na wykonanie połączenia zewnętrznego przy użyciu indeksu pokrycia. Pamiętaj, aby przetestować na swojej platformie, ponieważ optymalizacja zależy od wdrożenia. Użyj funkcji RDBMS do analizy planu optymalizacji. Np. {[10] } na MySQL.
Niektórzy używają zapytań podrzędnych zamiast rozwiązania, które pokazałem powyżej, ale uważam, że moje rozwiązanie ułatwia rozwiązywanie powiązań.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-25 02:23:41
Możesz również spróbować zrobić to za pomocą sub select
SELECT c.*, p.*
FROM customer c INNER JOIN
(
SELECT customer_id,
MAX(date) MaxDate
FROM purchase
GROUP BY customer_id
) MaxDates ON c.id = MaxDates.customer_id INNER JOIN
purchase p ON MaxDates.customer_id = p.customer_id
AND MaxDates.MaxDate = p.date
Wybierz powinien dołączyć do wszystkich klientów i ich Ostatnia Data zakupu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 17:40:54
Innym podejściem byłoby użycie warunku NOT EXISTS
w Twoim warunku przyłączenia do testów dla późniejszych zakupów:
SELECT *
FROM customer c
LEFT JOIN purchase p ON (
c.id = p.customer_id
AND NOT EXISTS (
SELECT 1 FROM purchase p1
WHERE p1.customer_id = c.id
AND p1.id > p.id
)
)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-12 08:21:00
Nie podałeś bazy danych. Jeśli jest to takie, które umożliwia funkcje analityczne, może być szybsze użycie tego podejścia niż grupa po jednym(zdecydowanie szybciej w Oracle, najprawdopodobniej szybciej w późnych edycjach SQL Server, Nie wiem o innych).
Składnia w SQL Server będzie:
SELECT c.*, p.*
FROM customer c INNER JOIN
(SELECT RANK() OVER (PARTITION BY customer_id ORDER BY date DESC) r, *
FROM purchase) p
ON (c.id = p.customer_id)
WHERE p.r = 1
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 18:23:30
Znalazłem ten wątek jako rozwiązanie mojego problemu.
Ale kiedy próbowałem ich wydajność była niska. Poniżej jest moja propozycja dla lepszej wydajności.With MaxDates as (
SELECT customer_id,
MAX(date) MaxDate
FROM purchase
GROUP BY customer_id
)
SELECT c.*, M.*
FROM customer c INNER JOIN
MaxDates as M ON c.id = M.customer_id
Mam nadzieję, że to będzie pomocne.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-23 06:29:29
Jeśli używasz PostgreSQL, możesz użyć DISTINCT ON
, aby znaleźć pierwszy wiersz w grupie.
SELECT customer.*, purchase.*
FROM customer
JOIN (
SELECT DISTINCT ON (customer_id) *
FROM purchase
ORDER BY customer_id, date DESC
) purchase ON purchase.customer_id = customer.id
Zauważ, że DISTINCT ON
pole(pola) -- here customer_id
-- musi pasować do lewej większości pól w ORDER BY
klauzuli.
Zastrzeżenie: jest to klauzula niestandardowa.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-21 21:40:21
Spróbuj tego, to pomoże.
Użyłem tego w moim projekcie.
SELECT
*
FROM
customer c
OUTER APPLY(SELECT top 1 * FROM purchase pi
WHERE pi.customer_id = c.Id order by pi.Id desc) AS [LastPurchasePrice]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-23 19:27:02
Testowane na SQLite:
SELECT c.*, p.*, max(p.date)
FROM customer c
LEFT OUTER JOIN purchase p
ON c.id = p.customer_id
GROUP BY c.id
Funkcja agregująca max()
upewni się, że ostatni zakup zostanie wybrany z każdej grupy (ale zakłada, że kolumna daty jest w formacie, w którym max() podaje ostatni - 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 (customer_id, date, [inne kolumny zakupu, które chcesz zwrócić w swoim select]).
The LEFT OUTER JOIN
(w przeciwieństwie do INNER JOIN
) upewni się, że klienci, którzy nigdy nie dokonali zakupu, są również uwzględniani.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-27 04:39:28
Proszę spróbować tego,
SELECT
c.Id,
c.name,
(SELECT pi.price FROM purchase pi WHERE pi.Id = MAX(p.Id)) AS [LastPurchasePrice]
FROM customer c INNER JOIN purchase p
ON c.Id = p.customerId
GROUP BY c.Id,c.name;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-25 09:50:51
Nie wchodząc najpierw w kod, logika / algorytm idzie poniżej:
-
Przejdź do tabeli
transaction
z wieloma rekordami dla tego samegoclient
. -
Wybierz rekordy
clientID
ilatestDate
aktywności klienta za pomocągroup by clientID
imax(transactionDate)
select clientID, max(transactionDate) as latestDate from transaction group by clientID
-
inner join
Tabelatransaction
z wynikiem z Kroku 2, wtedy będziesz miał pełne rekordy tabelitransaction
z tylko najnowszym rekordem każdego klienta.select * from transaction t inner join ( select clientID, max(transactionDate) as latestDate from transaction group by clientID) d on t.clientID = d.clientID and t.transactionDate = d.latestDate)
-
Możesz użyć wynik z kroku 3, aby dołączyć do dowolnej tabeli, którą chcesz uzyskać różne wyniki.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-05 20:27:34