W SQL opisujesz „co” chcesz osiągnąć, a nie „jak” powinno to zostać wykonane. Dlatego honorowe miejsce zajmuje problem tworzenia zapytań SQL w stylu „jak się słyszy, tak się pisze” wraz z cechy obliczania warunków w SQL.
Dziś na niezwykle prostych przykładach zobaczmy do czego może to prowadzić w kontekście użytkowania GROUP/DISTINCT и LIMIT z nimi.
Teraz, jeśli napisałeś w żądaniu „najpierw połącz te znaki, a potem wyrzuć wszystkie duplikaty, powinien zostać tylko jeden skopiuj dla każdego klucza” - dokładnie tak to będzie działać, nawet jeśli połączenie w ogóle nie było potrzebne.
Czasem ma się szczęście i „po prostu działa”, czasem ma to nieprzyjemny wpływ na wydajność, a czasem daje efekty zupełnie nieoczekiwane z punktu widzenia dewelopera.
No może nie aż tak spektakularnie, ale...
„Słodka para”: DOŁĄCZ + WYRÓŻNIJ
SELECT DISTINCT
X.*
FROM
X
JOIN
Y
ON Y.fk = X.pk
WHERE
Y.bool_condition;
Byłoby jasne, czego chcieli wybierz rekordy X, dla których w Y znajdują się rekordy powiązane ze spełnionym warunkiem. Napisałem prośbę za pośrednictwem JOIN — otrzymał kilka wartości pk kilka razy (dokładnie ile odpowiednich wpisów pojawiło się w Y). Jak usunąć? Z pewnością DISTINCT!
Jest to szczególnie „satysfakcjonujące”, gdy na każdy rekord X przypada kilkaset powiązanych rekordów Y, a następnie bohatersko usuwane są duplikaty…
Jak naprawić? Na początek zdaj sobie sprawę, że problem można zmodyfikować „wybierz rekordy X, dla których w Y jest CO NAJMNIEJ JEDEN powiązany ze spełnionym warunkiem” - w końcu nie potrzebujemy niczego z samej płyty Y.
Zagnieżdżone ISTNIEJE
SELECT
*
FROM
X
WHERE
EXISTS(
SELECT
NULL
FROM
Y
WHERE
fk = X.pk AND
bool_condition
LIMIT 1
);
Niektóre wersje PostgreSQL rozumieją, że w EXISTS wystarczy znaleźć pierwszy wpis, który się pojawi, starsze nie. Dlatego wolę zawsze wskazywać LIMIT 1 wewnątrz EXISTS.
POŁĄCZENIE BOCZNE
SELECT
X.*
FROM
X
, LATERAL (
SELECT
Y.*
FROM
Y
WHERE
fk = X.pk AND
bool_condition
LIMIT 1
) Y
WHERE
Y IS DISTINCT FROM NULL;
Dodatkową zaletą takich przekształceń zapytań jest możliwość łatwego ograniczenia wyszukiwania rekordów w przypadku, gdy potrzebny jest tylko jeden lub kilka z nich, jak w poniższym przypadku:
SELECT DISTINCT ON(X.pk)
*
FROM
X
JOIN
Y
ON Y.fk = X.pk
LIMIT 1;
Teraz czytamy żądanie i staramy się zrozumieć, co proponuje DBMS:
łączenie znaków
unikalny przez X.pk
z pozostałych wpisów wybierz jeden
Więc co dostałeś? „Tylko jeden wpis” od unikalnych - a jeśli weźmiemy ten z nieunikalnych, czy wynik jakoś się zmieni?.. „A jeśli nie ma różnicy, po co płacić więcej?”
SELECT
*
FROM
(
SELECT
*
FROM
X
-- сюда можно подсунуть подходящих условий
LIMIT 1 -- +1 Limit
) X
JOIN
Y
ON Y.fk = X.pk
LIMIT 1;
I dokładnie ten sam temat z GROUP BY + LIMIT 1.
„Muszę tylko zapytać”: ukryta GRUPA + LIMIT
Podobne rzeczy zdarzają się w różnych kontrole braku pustki znaki lub CTE w miarę postępu żądania:
...
CASE
WHEN (
SELECT
count(*)
FROM
X
LIMIT 1
) = 0 THEN ...
Funkcje agregujące (count/min/max/sum/...) są pomyślnie wykonywane na całym zestawie, nawet bez wyraźnych instrukcji GROUP BY. Tylko z LIMIT nie są zbyt przyjaźni.
Deweloper może pomyśleć „jeśli są tam zapisy, to potrzebuję nie więcej niż LIMIT”. Ale nie rób tego! Ponieważ dla bazy jest to:
policz, czego chcą według wszelkich zapisów
podaj tyle linijek, ile proszą
W zależności od warunków docelowych właściwe jest dokonanie jednego z następujących podstawień:
(count + LIMIT 1) = 0naNOT EXISTS(LIMIT 1)
(count + LIMIT 1) > 0naEXISTS(LIMIT 1)
count >= Nna(SELECT count(*) FROM (... LIMIT N))
„Ile zawiesić w gramach”: DISTINCT + LIMIT
SELECT DISTINCT
pk
FROM
X
LIMIT $1
Naiwny programista może szczerze wierzyć, że żądanie przestanie być wykonywane. gdy tylko znajdziemy 1 $ z pierwszych różnych wartości, które się pojawią.
Kiedyś w przyszłości może to zadziałać i będzie działać dzięki nowemu węzłowi Skanowanie pomijania indeksu, którego wdrożenie jest obecnie opracowywane, ale jeszcze nie teraz.
Na razie najpierw wszystkie rekordy zostaną pobrane, są unikalne i tylko z nich zostanie zwrócona żądana kwota. Jest to szczególnie smutne, jeśli chcieliśmy czegoś takiego $ 1 = 4, a w tabeli znajdują się setki tysięcy rekordów...