Jaka jest różnica między LATERAL a podzapytaniem w PostgreSQL?


146

Odkąd Postgres wyszedł z możliwością wykonywania LATERALpołączeń, czytałem o tym, ponieważ obecnie wykonuję złożone zrzuty danych dla mojego zespołu z wieloma nieefektywnymi podzapytaniami, które sprawiają, że ogólne zapytanie zajmuje cztery minuty lub więcej.

Rozumiem, że LATERALłączenia mogą mi pomóc, ale nawet po przeczytaniu artykułów takich jak ten z Heap Analytics nadal nie do końca go śledzę.

Jaki jest przypadek użycia LATERALsprzężenia? Jaka jest różnica między LATERALzłączeniem a podzapytaniem?


2
blog.heapanalytics.com/… i objaśnienie.com/2009/ 07/16/inner - join - vs - cross - apply (SQL Server applyjest taki sam jak w lateralstandardzie SQL)
— a_horse_with_no_name

Odpowiedzi:


163

Bardziej jak skorelowane podzapytanie

LATERALPrzyłączenia (Postgresa 9,3 lub później) jest większa jak skorelowanej podzapytaniu , a nie gładkie podzapytanie. Jak zauważył Andomar , funkcja lub podzapytanie po prawej stronie LATERALsprzężenia musi być ocenione raz dla każdego wiersza po lewej stronie - podobnie jak podzapytanie skorelowane - podczas gdy zwykłe podzapytanie (wyrażenie tabeli) jest oceniane tylko raz . (Planer zapytań ma jednak sposoby na optymalizację wydajności w obu przypadkach).
Ta pokrewna odpowiedź zawiera przykłady kodu dla obu stron, które rozwiązują ten sam problem:

Dla powracających więcej niż jedną kolumnę , o LATERALprzyłączenia jest zwykle prostsze, czystsze i szybsze.
Pamiętaj też, że odpowiednikiem skorelowanego podzapytania jest LEFT JOIN LATERAL ... ON true:

Przeczytaj instrukcję dalej LATERAL

Jest bardziej autorytatywny niż cokolwiek, co zamieścimy tutaj w odpowiedziach:

Rzeczy, których podzapytanie nie może zrobić

Tam są rzeczy, które LATERALłączą może zrobić, ale (skorelowane) podzapytanie nie może (łatwo). Skorelowane podzapytanie może zwracać tylko jedną wartość, a nie wiele kolumn i wiele wierszy - z wyjątkiem samych wywołań funkcji (które mnożą wiersze wynikowe, jeśli zwracają wiele wierszy). Ale nawet niektóre funkcje zwracające zbiory są dozwolone tylko w FROMklauzuli. Podobnie jak w unnest()przypadku wielu parametrów w Postgres 9.4 lub nowszym. Instrukcja:

Jest to dozwolone tylko w FROMklauzuli;

Więc to działa, ale nie można go łatwo zastąpić podzapytaniem:

CREATE TABLE tbl (a1 int[], a2 int[]);
SELECT * FROM tbl, unnest(a1, a2) u(elem1, elem2);  -- implicit LATERAL

Przecinek ( ,) w FROMklauzuli jest krótką notacją dla CROSS JOIN.
LATERALjest przyjmowany automatycznie dla funkcji tabelarycznych.
Więcej o szczególnym przypadku UNNEST( array_expression [, ... ] ):

Funkcje zwracające zestaw na SELECTliście

Możesz również bezpośrednio użyć funkcji zwracających zestaw, takich jak unnest()na SELECTliście. Kiedyś wykazywało zaskakujące zachowanie z więcej niż jedną taką funkcją na tej samej SELECTliście aż do Postgres 9.6. Ale w końcu został oczyszczony za pomocą Postgres 10 i jest teraz ważną alternatywą (nawet jeśli nie jest to standardowy SQL). Widzieć:

Opierając się na powyższym przykładzie:

SELECT *, unnest(a1) AS elem1, unnest(a2) AS elem2
FROM   tbl;

Porównanie:

dbfiddle dla strony 9.6 tutaj
dbfiddle dla strony 10 tutaj

Wyjaśnij błędne informacje

Instrukcja:

Dla INNERi OUTERłączą typów sprzężenia warunek musi być określony, a mianowicie dokładnie jedną NATURAL, ON join_condition lub USING( join_column [...]). Zobacz poniżej znaczenie.
Ponieważ CROSS JOINżadna z tych klauzul nie może się pojawić.

Więc te dwa zapytania są prawidłowe (nawet jeśli nie są szczególnie przydatne):

SELECT *
FROM   tbl t
LEFT   JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t ON TRUE;

SELECT *
FROM   tbl t, LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;

Chociaż ten nie jest:

SELECT *
FROM   tbl t
LEFT   JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;

Dlatego przykład kodu @ Andomar jest poprawny ( CROSS JOINnie wymaga warunku łączenia), a kod @ Attila jest nieprawidłowy.


Są pewne rzeczy, których podzapytanie nie może zrobić dla SPRZĘŻENIA LATERALNEGO. Podobnie jak funkcje okna. Jak tutaj
— Evan Carroll,

@EvanCarroll: W linku nie mogłem znaleźć żadnych skorelowanych podzapytań. Ale dodałem inną odpowiedź, aby zademonstrować funkcję okna w LATERALpodzapytaniu: gis.stackexchange.com/a/230070/7244
— Erwin Brandstetter

1
Czystsze i szybsze? W niektórych przypadkach wielkości są szybsze. Miałem pytanie, które trwało od dni do sekund po przełączeniu na LATERAL.
— rovyko

51

Różnica między non lateral-a lateraljoin polega na tym, czy możesz spojrzeć na wiersz tabeli po lewej stronie. Na przykład:

select  *
from    table1 t1
cross join lateral
        (
        select  *
        from    t2
        where   t1.col1 = t2.col1 -- Only allowed because of lateral
        ) sub

To „wyglądające na zewnątrz” oznacza, że ​​podzapytanie musi być ocenione więcej niż raz. Przecież t1.col1może przybierać wiele wartości.

W przeciwieństwie do tego podzapytanie po nierozłączeniu lateralmożna ocenić raz:

select  *
from    table1 t1
cross join
        (
        select  *
        from    t2
        where   t2.col1 = 42 -- No reference to outer query
        ) sub

Jak jest to wymagane bez lateral, zapytanie wewnętrzne nie zależy w żaden sposób od zapytania zewnętrznego. lateralZapytanie jest przykładem correlatedzapytania, ze względu na jej związek z wierszy na zewnątrz samej zapytania.


5
To najczystsze wyjaśnienie połączenia bocznego.
— 1valdis

łatwe do zrozumienia wyjaśnienie, dziękuję.
— arilwan

jak select * from table1 left join t2 using (col1)wypada? Nie jest dla mnie jasne, kiedy połączenie przy użyciu / włączonym warunku jest niewystarczające i bardziej sensowne byłoby użycie bocznego.
— No_name

9

Po pierwsze, zastosowanie boczne i krzyżowe to to samo . Dlatego możesz również przeczytać o Cross Apply. Ponieważ był on wdrażany w SQL Server od wieków, więcej informacji na jego temat znajdziesz w sekcji Lateral.

Po drugie, zgodnie z moim rozumieniem , nie ma niczego, czego nie można zrobić za pomocą podzapytania zamiast użycia lateralnego. Ale:

Rozważ następujące zapytanie.

Select A.*
, (Select B.Column1 from B where B.Fk1 = A.PK and Limit 1)
, (Select B.Column2 from B where B.Fk1 = A.PK and Limit 1)
FROM A 

Możesz użyć bocznego w tym stanie.

Select A.*
, x.Column1
, x.Column2
FROM A LEFT JOIN LATERAL (
  Select B.Column1,B.Column2,B.Fk1 from B  Limit 1
) x ON X.Fk1 = A.PK

W tym zapytaniu nie możesz używać zwykłego łączenia ze względu na klauzulę limit. Można zastosować nakładanie poprzeczne lub krzyżowe gdy nie ma prostego warunku łączenia .

Jest więcej zastosowań do nakładania bocznego lub krzyżowego, ale jest to najczęściej spotykane.


1
Dokładnie, zastanawiam się, dlaczego PostgreSQL używa lateralzamiast apply. Być może Microsoft opatentował składnię?
— Andomar

9
@Andomar AFAIK lateraljest w standardzie SQL, ale applytak nie jest.
— mu jest za krótkie

LEFT JOINWymaga przyłączenia warunek. Zrób to, ON TRUEchyba że chcesz jakoś ograniczyć.
— Erwin Brandstetter

Erwin ma rację, pojawi się błąd, chyba że użyjesz warunku cross joinlubon
— Andomar

1
@Andomar: Zachęcony tą dezinformacją dodałem kolejną odpowiedź, aby wyjaśnić.
— Erwin Brandstetter

4

Jedną z rzeczy, na które nikt nie zwrócił uwagi, jest to, że możesz użyć LATERALzapytań do zastosowania funkcji zdefiniowanej przez użytkownika w każdym wybranym wierszu.

Na przykład:

CREATE OR REPLACE FUNCTION delete_company(companyId varchar(255))
RETURNS void AS $$
    BEGIN
        DELETE FROM company_settings WHERE "company_id"=company_id;
        DELETE FROM users WHERE "company_id"=companyId;
        DELETE FROM companies WHERE id=companyId;
    END; 
$$ LANGUAGE plpgsql;

SELECT * FROM (
    SELECT id, name, created_at FROM companies WHERE created_at < '2018-01-01'
) c, LATERAL delete_company(c.id);

Tylko w ten sposób wiem, jak to zrobić w PostgreSQL.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.