Чим відрізняється LATERAL від підзапиту в PostgreSQL?


147

Оскільки Postgres вийшов із можливістю робити LATERALприєднання, я читав на ньому, оскільки в даний час роблю складні дампи для моєї команди з безліччю неефективних підзапитів, завдяки яким загальний запит займає чотири хвилини або більше.

Я розумію , що LATERALприєднується може бути в змозі допомогти мені, але навіть після прочитання статті як це одне з кучного Analytics, я до сих пір не зовсім розумію.

Який випадок використання LATERALприєднання? Яка різниця між LATERALоб'єднанням і підзапитом?


2
blog.heapanalytics.com / ... і explainextended.com/2009/07/16/inner-join-vs-cross-apply (SQL Server, applyє такий же , як lateralвід стандарту SQL)
— a_horse_with_no_name

Відповіді:


163

Більше схожий на співвіднесений підзапитів

LATERALПриєднатися (Postgres 9.3 або більш пізньої версії) більше схожий на корелюється підзапитів , а не простий підзапиту. Як зазначав Андомар , функцію або підзапит праворуч від LATERALоб'єднання потрібно оцінювати один раз для кожного рядка, що залишився від нього - як і співвіднесений підзапит - в той час як простий підзапит (вираження таблиці) оцінюється лише один раз . (Планувальник запитів, однак, має способи оптимізації продуктивності для будь-якого.)
Ця відповідна відповідь має приклади коду для обох поряд, вирішуючи ту саму проблему:

Для повернення декількох стовпців , LATERALз'єднання, як правило, простіше, чистіше та швидше.
Також пам’ятайте, що еквівалент корельованого підпиту LEFT JOIN LATERAL ... ON true:

Прочитайте посібник на LATERAL

Це більш авторитетно, ніж усе, що ми збираємось тут відповісти:

Те, що підзапит не може зробити

Є деякі речі, які LATERALможе зробити об'єднання, але (корельований) підзапит не може (легко). Співвіднесений підзапит може повернути лише одне значення, а не кілька стовпців і не кілька рядків - за винятком голосних викликів функцій (які множать результати рядків, якщо вони повертають кілька рядків). Але навіть певні функції, що повертаються, дозволяються лише у FROMпункті. Як і unnest()з кількома параметрами в Postgres 9.4 або новіших версій. Посібник:

Це дозволено лише в FROMпункті;

Отже, це працює, але його не можна легко замінити підзапитом:

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

Кома ( ,) у FROMпункті - це коротке позначення CROSS JOIN.
LATERALпередбачається автоматично для функцій таблиці.
Більше про особливий випадок UNNEST( array_expression [, ... ] ):

Налаштування повернення функцій у SELECT списку

Ви також можете використовувати функції повернення набору, як unnest()у SELECTсписку, безпосередньо. Це використовувало для виявлення дивовижної поведінки з більш ніж однією такою функцією в одному SELECTсписку аж до Postgres 9.6. Але, нарешті, це було дезінфіковано Postgres 10 і є тепер вагомою альтернативою (навіть якщо це не стандартний SQL). Побачити:

Спираючись на приклад вище:

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

Порівняння:

dbfiddle для pg 9.6 тут
dbfiddle для pg 10 тут

Уточнити дезінформацію

Посібник:

Для INNERі OUTERприєднатися типів, об'єднання умова має бути зазначено, а саме тільки один з NATURAL, ON join_condition , або USING( join_column [...]). Див. Нижче значення.
Бо CROSS JOINжодна з цих пропозицій не може з’явитися.

Отже, ці два запити є дійсними (навіть якщо вони не особливо корисні):

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;

Хоча цього немає:

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

Ось чому @ Andomar в прикладі коду є правильним ( CROSS JOINне вимагає умова з'єднання) і @ Аттіли IS був недійсний.


Є деякі речі, які підзапит може зробити, ЛІТЕРАЛЬНЕ ПРИЄДНАННЯ не може. Як і функції вікон. Як тут
— Еван Керролл

@EvanCarroll: Я не зміг знайти жодних кореляційних підзапитів у посиланні. Але я додав ще одну відповідь, щоб продемонструвати функцію вікна в LATERALпідзапиті: gis.stackexchange.com/a/230070/7244
— Erwin Brandstetter

1
Чистіше і швидше? Як і величини швидше, в деяких випадках. У мене з’явився запит, який тривав від днів до секунд після переходу на LATERAL.
— rovyko

52

Різниця між не- lateralта lateralз'єднанням полягає в тому, чи можете ви дивитись на рядок таблиці зліва. Наприклад:

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

Цей "зовнішній вигляд" означає, що підзапит потрібно оцінювати не один раз. Після всього,t1.col1 можна припустити багато значень.

Навпаки, підзапит після неприєднання lateralможе бути оцінений один раз:

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

Як і потрібно lateral, внутрішній запит жодним чином не залежить від зовнішнього запиту. lateralЗапит є прикладом correlatedзапиту, з - за його зв'язки з рядами за межами самого запиту.


5
Це найчистіше пояснення бічного з'єднання.
— 1валдіс

легко зрозуміти пояснення, дякую.
— arilwan

як select * from table1 left join t2 using (col1)порівнювати? Мені незрозуміло, коли з'єднання, що використовує / за умови, є недостатнім, і було б більше сенсу використовувати бічне.
— No_name

9

По-перше, бокове та поперечне застосування - це одне і те ж . Тому ви також можете прочитати про Cross Apply. Оскільки він впроваджувався в SQL Server протягом століть, ви знайдете більше інформації про нього, а потім Латеральний.

По-друге, за моїм розумінням , немає нічого, чого не можна зробити, використовуючи підзапит, замість того, щоб використовувати бічний. Але:

Розглянемо наступний запит.

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 

Ви можете використовувати бічні в цьому стані.

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

У цьому запиті ви не можете використовувати звичайне з'єднання через обмеження. Можна використовувати бічне або перехресне нанесення коли немає простої умови з'єднання .

Існує більше застосувань для бічного або поперечного застосування, але це найпоширеніший варіант, який я знайшов.


1
Точно мені цікаво, чому PostgreSQL використовує lateralзамість apply. Можливо, Microsoft запатентував синтаксис?
— Андомар

9
@Andomar AFAIK lateralє стандартом SQL, але applyце не так.
— mu занадто короткий

LEFT JOINВимагає умова з'єднання. Зробіть це, ON TRUEякщо ви хочете якось обмежити.
— Ервін Брандстеттер

Ервін правий, ви отримаєте помилку, якщо не скористаєтесь умовою cross joinчи onумовою
— Andomar

1
@Andomar: Створений цією дезінформацією, я додав ще одну відповідь для уточнення.
— Ервін Брандстеттер

4

Одне, на що ніхто не вказував, - це те, що ви можете використовувати LATERALзапити для застосування визначеної користувачем функції до кожного вибраного рядка.

Наприклад:

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);

Це єдиний спосіб, коли я знаю, як це зробити в PostgreSQL.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.