В чем разница между LATERAL и подзапросом в PostgreSQL?

146

С тех пор, как Postgres получил возможность выполнять LATERALобъединения, я уже читал об этом, поскольку в настоящее время я делаю сложные дампы данных для своей команды с большим количеством неэффективных подзапросов, которые делают общий запрос продолжительностью четыре минуты или более.

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

Какой вариант использования для LATERALобъединения? В чем разница между LATERALобъединением и подзапросом?

jdotjdot
источник
2
blog.heapanalytics.com/… и объясниться xtended.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 здесь

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

Руководство:

Для типов INNERand и OUTERjoin должно быть указано условие соединения, а именно одно из 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 был недействителен.

Эрвин Брандштеттер
источник
Есть некоторые вещи, которые может сделать подзапрос, а LATERAL JOIN - нет. Как и оконные функции. Как здесь
Эван Кэрролл
@EvanCarroll: я не смог найти никаких связанных подзапросов в ссылке. Но я добавил еще один ответ, чтобы продемонстрировать оконную функцию в LATERALподзапросе: gis.stackexchange.com/a/230070/7244
Эрвин Брандштеттер,
1
Чище и быстрее? В некоторых случаях, как величины быстрее. У меня был запрос, который переходил от дней к секундам после перехода на LATERAL.
Ровико
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запроса, из - за его связи с рядами за пределами самого запроса.

Andomar
источник
5
Это самое чистое объяснение бокового соединения.
января
легко понять объяснение, спасибо.
Арилван
как select * from table1 left join t2 using (col1)сравнивать? Мне непонятно, когда соединение с использованием / on является недостаточным, и было бы более целесообразно использовать боковое.
Без имени
9

Во-первых, боковое и поперечное применение - это одно и то же . Поэтому вы также можете прочитать о Cross Apply. Поскольку он был внедрен в SQL Server целую вечность, вы найдете больше информации о нем, чем Lateral.

Во-вторых, согласно моему пониманию , вы ничего не можете сделать, используя подзапрос вместо использования бокового. Но:

Рассмотрим следующий запрос.

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

В этом запросе вы не можете использовать обычное объединение из-за предложения limit. Боковое или поперечное применение может использоваться, когда нет простого условия соединения .

Существует более широкое применение для бокового или поперечного применения, но это наиболее распространенный вариант, который я нашел.

Атилла Озгур
источник
1
Точно, мне интересно, почему PostgreSQL использует lateralвместо apply. Возможно, Microsoft запатентовала синтаксис?
Andomar
9
@Andomar AFAIK lateralв стандарте SQL, но applyэто не так.
мю слишком коротка
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.

Теодор Р. Смит
источник