В чем разница между 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
это более авторитетно, чем все, что мы собираемся положить в ответы здесь:
- https://www.postgresql.org/docs/current/static/queries-table-expressions.html#QUERIES-LATERAL
- http://www.postgresql.org/docs/current/static/sql-select.html
вещи, которые подзапрос не может сделать
здесь 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.