Mam wzorzec zapytania, który musi być bardzo powszechny, ale nie wiem, jak napisać dla niego wydajne zapytanie. Chcę wyszukać wiersze tabeli odpowiadające „najnowszej dacie nie później” niż wiersze innej tabeli.
Mam, inventory
powiedzmy, tabelę, która reprezentuje ekwipunek, który przechowuję pewnego dnia.
date | good | quantity
------------------------------
2013-08-09 | egg | 5
2013-08-09 | pear | 7
2013-08-02 | egg | 1
2013-08-02 | pear | 2
i stół, powiedzmy „cena”, który zawiera cenę towaru w danym dniu
date | good | price
--------------------------
2013-08-07 | egg | 120
2013-08-06 | pear | 200
2013-08-01 | egg | 110
2013-07-30 | pear | 220
Jak mogę efektywnie uzyskać „najnowszą” cenę dla każdego wiersza tabeli zapasów, tj
date | pricing date | good | quantity | price
----------------------------------------------------
2013-08-09 | 2013-08-07 | egg | 5 | 120
2013-08-09 | 2013-08-06 | pear | 7 | 200
2013-08-02 | 2013-08-01 | egg | 1 | 110
2013-08-02 | 2013-07-30 | pear | 2 | 220
Znam jeden sposób:
select inventory.date, max(price.date) as pricing_date, good
from inventory, price
where inventory.date >= price.date
and inventory.good = price.good
group by inventory.date, good
a następnie ponownie dołącz to zapytanie do spisu. W przypadku dużych tabel nawet wykonanie pierwszego zapytania (bez ponownego łączenia się z zapasami) jest bardzo wolne. Jednak ten sam problem można szybko rozwiązać, jeśli po prostu używam mojego języka programowania do wydawania jednego max(price.date) ... where price.date <= date_of_interest ... order by price.date desc limit 1
zapytania dla każdego date_of_interest
z tabeli inwentarza, więc wiem, że nie ma przeszkód obliczeniowych. Wolałbym jednak rozwiązać cały problem za pomocą pojedynczego zapytania SQL, ponieważ pozwoliłoby mi to na dalsze przetwarzanie kodu SQL na podstawie wyniku zapytania.
Czy istnieje standardowy sposób, aby to zrobić skutecznie? Wydaje się, że musi to często pojawiać się i powinien istnieć sposób na napisanie szybkiego zapytania.
Korzystam z Postgres, ale doceniłbym ogólną odpowiedź na SQL.
\d tbl
w psql), twoja wersja Postgres i min. / max. liczba cen za towar.Odpowiedzi:
To bardzo zależy od okoliczności i dokładnych wymagań. Rozważ mój komentarz do pytania .
Proste rozwiązanie
Z
DISTINCT ON
w Postgres:Zamówiony wynik.
Lub ze
NOT EXISTS
standardowym SQL (działa z każdym znanym RDBMS):Ten sam wynik, ale z dowolną kolejnością sortowania - chyba że dodasz
ORDER BY
.W zależności od dystrybucji danych, dokładnych wymagań i wskaźników, jeden z nich może być szybszy.
Ogólnie rzecz biorąc,
DISTINCT ON
jest zwycięzcą, a na dodatek otrzymujesz posortowany wynik. Jednak w niektórych przypadkach inne techniki zapytań są (znacznie) szybsze. Patrz poniżej.Rozwiązania z podzapytaniami do obliczania wartości maksymalnych / minimalnych są na ogół wolniejsze. Warianty z CTE są jednak ogólnie wolniejsze.
Zwykłe widoki (jak proponowana przez inną odpowiedź) wcale nie pomagają w wydajności w Postgres.
SQL Fiddle.
Właściwe rozwiązanie
Ciągi i zestawianie
Przede wszystkim cierpisz z powodu nieoptymalnego układu stołu. Może się to wydawać trywialne, ale normalizacja schematu może być bardzo trudna.
Sortowanie według typów znakowych (
text
,varchar
, ...) musi być wykonana zgodnie z lokalizacji - na zestawień w szczególności. Najprawdopodobniej twoja baza danych korzysta z lokalnego zestawu reguł (np. W moim przypadkude_AT.UTF-8
:). Dowiedz się za pomocą:Powoduje to, że sortowanie i indeksowanie wyszukiwania jest wolniejsze . Im dłuższe są twoje łańcuchy (nazwy towarów), tym gorzej. Jeśli w rzeczywistości nie obchodzą Cię reguły sortowania (lub w ogóle porządek sortowania), może to być szybsze, jeśli dodasz
COLLATE "C"
:Zwróć uwagę, jak dodałem zestawienie w dwóch miejscach.
Dwa razy szybciej w moim teście, każdy z 20 tys. Wierszy i bardzo podstawowymi nazwami („good123”).
Indeks
Jeśli zapytanie ma używać indeksu, kolumny z danymi znakowymi muszą używać zgodnego sortowania (
good
w przykładzie):Przeczytaj dwa ostatnie rozdziały tej pokrewnej odpowiedzi na SO:
Możesz nawet mieć wiele indeksów z różnymi zestawieniami w tych samych kolumnach - jeśli potrzebujesz również towarów posortowanych według innego (lub domyślnego) zestawienia w innych zapytaniach.
Normalizować
Nadmiarowe ciągi (nazwa dobra) również nadmuchują tabele i indeksy, co czyni wszystko jeszcze wolniejszym. Przy prawidłowym układzie tabeli można na początku uniknąć większości problemów. Może wyglądać tak:
Klucze podstawowe automatycznie zapewniają (prawie) wszystkie potrzebne indeksy.
W zależności od brakujących szczegółów indeks wielokolumnowy włączony
price
w kolejności malejącej w drugiej kolumnie może poprawić wydajność:Ponownie sortowanie musi pasować do zapytania (patrz wyżej).
W Postgresie 9.2 lub nowszym „indeksowanie indeksów” dla skanów tylko indeksowych może trochę pomóc - zwłaszcza jeśli tabele zawierają dodatkowe kolumny, dzięki czemu tabela jest znacznie większa niż indeks indeksujący.
Te wynikowe zapytania są znacznie szybsze:
NIE ISTNIEJE
WYRÓŻNIJ WŁ
SQL Fiddle.
Szybsze rozwiązania
Jeśli to nadal nie jest wystarczająco szybkie, mogą istnieć szybsze rozwiązania.
Rekurencyjne
JOIN LATERAL
podkwerendy CTE / /Zwłaszcza w przypadku dystrybucji danych z wieloma cenami za towar :
Widok zmaterializowany
Jeśli musisz uruchamiać to często i szybko, sugeruję utworzenie zmaterializowanego widoku. Myślę, że można bezpiecznie założyć, że ceny i zapasy z poprzednich dat rzadko się zmieniają. Oblicz wynik raz i zapisz migawkę jako zmaterializowany widok.
Postgres 9.3+ ma zautomatyzowaną obsługę zmaterializowanych widoków. Możesz łatwo wdrożyć wersję podstawową w starszych wersjach.
źródło
price_good_date_desc_idx
Indeks polecacie znacznie poprawiło wydajność podobnym zapytaniu kopalni. Mój plan zapytań przeszedł od kosztu42374.01..42374.86
do0.00..37.12
!Do Twojej wiadomości użyłem mssql 2008, więc Postgres nie będzie miał indeksu „include”. Jednak korzystanie z podstawowej indeksacji pokazanej poniżej zmieni się ze złączeń mieszających na scalanie złączeń w Postgres: http://explain.depesz.com/s/eF6 (brak indeksu) http://explain.depesz.com/s/j9x ( z indeksem kryteriów łączenia)
Proponuję rozbić twoje zapytanie na dwie części. Po pierwsze, widok (nie mający na celu poprawy wydajności), który można wykorzystać w różnych innych kontekstach, który reprezentuje związek między datami zapasów i datami wyceny.
Wówczas zapytanie może stać się prostsze i łatwiejsze do manipulowania innymi rodzajami, jeśli zapytanie (takie jak użycie lewych złączeń do znalezienia zapasów bez ostatnich dat wyceny):
Daje to następujący plan wykonania: http://sqlfiddle.com/#!3/24f23/1
... Wszystkie skany z pełnym rodzajem. Zauważ, że koszt wydajności dopasowań haszujących zajmuje większość kosztów całkowitych ... i wiemy, że skanowanie i sortowanie tabeli jest powolne (w porównaniu do celu: szukanie indeksu).
Teraz dodaj podstawowe indeksy, aby pomóc w kryteriach zastosowanych w twoim złączeniu (nie twierdzę, że są to indeksy optymalne, ale ilustrują one sens): http://sqlfiddle.com/#!3/5ec75/1
To pokazuje poprawę. Operacje na zagnieżdżonej pętli (łączenie wewnętrzne) nie obciążają już żadnych istotnych kosztów całkowitych dla zapytania. Reszta kosztów jest teraz rozłożona na poszukiwanie indeksów (skanowanie zapasów, ponieważ ściągamy każdy wiersz zapasów). Ale możemy jeszcze lepiej, ponieważ zapytanie pobiera ilość i cenę. Aby uzyskać te dane, po dokonaniu oceny kryteriów łączenia należy wykonać wyszukiwania.
Ostateczna iteracja używa „włącz” w indeksach, aby ułatwić przesuwanie się planu i pobieranie dodatkowych żądanych danych bezpośrednio z samego indeksu. Tak więc wyszukiwania zniknęły: http://sqlfiddle.com/#!3/5f143/1
Teraz mamy plan zapytań, w którym całkowity koszt zapytania rozkłada się równomiernie na bardzo szybkie operacje wyszukiwania indeksu. Będzie to prawie tak dobre, jak to możliwe. Z pewnością inni eksperci mogą to poprawić, ale rozwiązanie rozwiązuje kilka głównych problemów:
źródło
Jeśli zdarzyło Ci się mieć PostgreSQL 9.3 (wydany dzisiaj), możesz użyć POŁĄCZENIA BOCZNEGO.
Nie mam możliwości przetestowania tego i nigdy wcześniej go nie używałem, ale z tego, co mogę stwierdzić na podstawie dokumentacji, składnia wyglądałaby następująco:
Jest to w zasadzie odpowiednik APLIKACJI SQL-Servera , i istnieje praktyczny przykład tego na SQL-Fiddle do celów demonstracyjnych.
źródło
Jak zauważyli Erwin i inni, wydajne zapytanie zależy od wielu zmiennych, a PostgreSQL bardzo stara się zoptymalizować wykonanie zapytania w oparciu o te zmienne. Ogólnie rzecz biorąc, chcesz najpierw napisać dla zachowania przejrzystości, a następnie zmodyfikować wydajność po zidentyfikowaniu wąskich gardeł.
Dodatkowo PostgreSQL ma wiele sztuczek, których możesz użyć, aby uczynić rzeczy nieco bardziej wydajnymi (częściowe indeksy dla jednego), więc w zależności od obciążenia odczytu / zapisu możesz być w stanie zoptymalizować to bardzo daleko, patrząc na uważne indeksowanie.
Pierwszą rzeczą, którą należy wypróbować, jest zrobienie widoku i dołączenie do niego:
Powinno to działać dobrze podczas robienia czegoś takiego:
Możesz do tego dołączyć. Kwerenda ostatecznie połączy widok z bazową tabelą, ale zakładając, że masz unikalny indeks (data, dobry w tej kolejności ), powinieneś zacząć (ponieważ będzie to proste wyszukiwanie w pamięci podręcznej). Będzie to działało bardzo dobrze z kilkoma rzędami w górę, ale będzie bardzo nieefektywne, jeśli spróbujesz strawić miliony cen towarów.
Drugą rzeczą, którą możesz zrobić, to dodać do tabeli ekwipunku kolumnę bool najbardziej_recent i
Następnie należy użyć wyzwalaczy, aby ustawić wartość most_recent na wartość false, gdy wstawiono nowy wiersz towaru. Zwiększa to złożoność i zwiększa szanse na błędy, ale jest pomocne.
Ponownie wiele z tego zależy od istnienia odpowiednich indeksów. W przypadku najnowszych zapytań o datę prawdopodobnie powinieneś mieć indeks na datę i ewentualnie wielokolumnowy, zaczynający się od daty i zawierający kryteria łączenia.
Zaktualizuj Poniższy komentarz Erwina wygląda na to, że źle to zrozumiałem. Ponownie czytając pytanie, wcale nie jestem pewien, co jest zadawane. Chcę wspomnieć w aktualizacji o potencjalnym problemie, który widzę i dlaczego nie jest to jasne.
Oferowany projekt bazy danych nie ma rzeczywistego zastosowania edytora IME w systemach ERP i księgowych. Działałoby to w hipotetycznym modelu wyceny idealnej, w którym wszystko sprzedawane w danym dniu danego produktu ma tę samą cenę. Jednak nie zawsze tak jest. Nie dotyczy to nawet wymiany walut (chociaż niektóre modele udają, że tak jest). Jeśli jest to wymyślony przykład, jest niejasny. Jeśli jest to prawdziwy przykład, występują większe problemy z projektem na poziomie danych. Zakładam tutaj, że to prawdziwy przykład.
Nie można zakładać, że sama data określa cenę danego towaru. Ceny w każdej firmie mogą być negocjowane dla kontrahenta, a czasem nawet dla transakcji. Z tego powodu naprawdę powinieneś przechowywać cenę w tabeli, która faktycznie obsługuje inwentaryzację zapasów (tabela inwentaryzacji). W takim przypadku tabela daty / towarów / cen określa jedynie cenę bazową, która może ulec zmianie w wyniku negocjacji. W takim przypadku problem ten przechodzi od problemu raportowania do problemu transakcyjnego i działającego w jednym wierszu z każdej tabeli na raz. Na przykład możesz sprawdzić domyślną cenę danego produktu w danym dniu jako:
Dzięki indeksowi cen (dobry, data) będzie to działać dobrze.
I to jest wymyślony przykład, być może coś bliższego temu, nad czym pracujesz, pomogłoby.
źródło
most_recent
Podejście powinno działać dobrze dla ostatniej ceny absolutnie . Wydaje się, że OP potrzebuje najnowszej ceny w odniesieniu do każdej daty inwentaryzacji.Innym sposobem byłoby użycie funkcji okna,
lead()
aby uzyskać zakres dat dla każdego wiersza w cenie tabeli, a następnie użyćbetween
przy dołączaniu zapasów. Użyłem tego w prawdziwym życiu, ale głównie dlatego, że był to mój pierwszy pomysł, jak to rozwiązać.SqlFiddle
źródło
Użyj sprzężenia z zapasów do wyceny z warunkami łączenia, które ograniczają zamówienia z tabeli cen tylko do tych, które są w dniu zapasu lub przed nim, a następnie wyodrębnij datę maksymalną i gdzie data jest najwyższą datą z tego podzbioru
Tak więc w przypadku ceny zapasów:
Jeśli cena dowolnego określonego towaru zmieniła się więcej niż raz w tym samym dniu, a tak naprawdę w tych kolumnach są tylko daty, a nie czasy, może być konieczne nałożenie dodatkowych ograniczeń na połączenia, aby wybrać tylko jeden z rekordów zmiany ceny.
źródło