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

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

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

каков вариант использования для LATERAL присоединиться? Что разница между LATERAL join и подзапрос?

4 ответов


еще как коррелируется подзапрос

A LATERAL join (Postgres 9.3+) больше похоже на коррелированный подзапрос, а не простой подзапрос. Как @Andomar указал, функция или подзапрос справа от LATERAL join обычно должен оцениваться много раз-один раз для каждой строки слева от LATERAL присоединиться-так же, как коррелируется subquery-в то время как простой подзапрос (табличное выражение) оценивается после только. (Планировщик запросов имеет способы оптимизации производительности для любого из них.)
Этот связанный ответ имеет примеры кода для обеих сторон, решая одну и ту же проблему:

возвращение более одной колонки, a LATERAL join обычно проще, чище и быстрее. Кроме того, помните, что эквивалент коррелированного подзапрос LEFT JOIN LATERAL ... ON true:

прочитайте руководство для on LATERAL

это более авторитетно, чем все, что мы собираемся положить в ответы здесь:

вещи, которые подзапрос не может сделать

здесь are вещей LATERAL join может сделать, но (коррелированный) подзапрос не может (легко). Коррелированный подзапрос может возвращать только одно значение, а не несколько столбцы, а не несколько строк - за исключением голых вызовов функций (которые умножают результирующие строки, если они возвращают несколько строк). Но даже некоторые функции возврата набора разрешены только в FROM предложения. Как новый unnest() С несколькими параметрами в Postgres 9.4. инструкции:

это разрешено только в FROM п.;

так это работает, но не может быть легко заменено на подзапрос:

CREATE TABLE tbl (a1 int[], a2 int[]);

SELECT *
FROM   tbl t, unnest(t.a1, t.a2) u(elem1, elem2);  -- implicit LATERAL

(запятая (,) в FROM предложение является короткой нотацией для CROSS JOIN.
LATERAL предполагается автоматически для табличных функций.)

подробнее о специальном случае UNNEST( array_expression [, ... ] ) при этом позже вопрос на dba.SE:

Set-функции возврата в SELECT список

вы также можете использовать функции set-returning, такие как unnest() на SELECT список напрямую. Раньше это демонстрировало удивительное поведение с более чем одним экземпляром в одном и том же SELECT список до Postgres 9.6. но он, наконец, был дезинфицирован Postgres 10 и теперь является допустимой альтернативой (даже если это не стандартный SQL).
Основываясь на приведенном выше примере:

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

для сравнения:

dbfiddle для страницы 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 не требует условия соединения) и @Аттилы и был недействительный.


разницу междуlateral и lateral join лежит в том, Можете ли вы посмотреть на строку таблицы левой руки. Например:

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

этот "внешний вид" означает, что подзапрос должен оцениваться более одного раза. Ведь t1.col1 может принимать разные значения.

напротив, подзапрос послеlateral join можно оценить один раз:

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

как требуется без lateral, внутренний запрос никоим образом не зависит от внешний запрос. А lateral запрос-пример correlated запрос, из-за его связи со строками вне самого запроса.


во-первых,боковые и поперечные применяются то же самое. Поэтому вы также можете прочитать о 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

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

есть больше применений для бокового или перекрестного применения, но это наиболее распространенное, которое я нашел.


одна вещь, которую никто не указал, что вы можете использовать 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.