Чим відрізняється 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.