В чем разница между LATERAL и подзапросом в PostgreSQL?
Так как Postgres вышел с возможностью сделать LATERAL
присоединения, я читал об этом, так как в настоящее время я делаю сложные дампы данных для своей команды с большим количеством неэффективных подзапросов, которые заставляют весь запрос занимать четыре минуты или больше.
Я это понимаю LATERAL
Объединения могут мне помочь, но даже после прочтения таких статей в Heap Analytics я все еще не совсем понимаю.
Какой вариант использования для LATERAL
присоединиться? В чем разница между LATERAL
присоединиться и подзапрос?
5 ответов
Больше похоже на коррелированный подзапрос
LATERAL
join (Postgres 9.3+) больше похож на коррелированный подзапрос, а не на простой подзапрос. Как указал @Andomar, функция или подзапрос справа от LATERAL
тип соединения обычно должен оцениваться много раз - по одному разу для каждой строки слева от LATERAL
join - как коррелированный подзапрос - тогда как простой подзапрос (табличное выражение) вычисляется только один раз. (Однако у планировщика запросов есть способы оптимизировать производительность для любого из них.)
У этого связанного ответа есть примеры кода для обеих сторон, решая ту же самую проблему:
Для возврата более одного столбца LATERAL
Присоединиться, как правило, проще, чище и быстрее. Кроме того, помните, что эквивалент коррелированного подзапроса LEFT JOIN LATERAL ... ON true
:
Прочтите руководство по LATERAL
Это более авторитетно, чем все, что мы собираемся вставить в ответы здесь:
- https://www.postgresql.org/docs/current/static/queries-table-expressions.html
- http://www.postgresql.org/docs/current/static/sql-select.html
То, что подзапрос не может сделать
Есть вещи, которые LATERAL
объединение может сделать, но (коррелированный) подзапрос не может (легко). Коррелированный подзапрос может возвращать только одно значение, а не несколько столбцов и не несколько строк, за исключением простых вызовов функций (которые умножают результирующие строки, если они возвращают несколько строк). Но даже некоторые возвращающие множество функции разрешены только в 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:
Набор возвращающих функций в SELECT
список
Вы также можете использовать функции, возвращающие множество unnest()
в SELECT
список напрямую. Раньше это демонстрировало удивительное поведение с более чем одним экземпляром в одном и том же SELECT
список до Postgres 9.6. Но он, наконец, был продезинфицирован с помощью Postgres 10 и теперь является допустимой альтернативой (даже если не стандартным SQL).
Опираясь на приведенный выше пример:
SELECT *, unnest(t.a1) AS elem1, unnest(t.a2) AS elem2
FROM tbl t;
Сравнение:
dbfiddle для pg 9.6 здесь
dbfiddle для pg 10 здесь
Уточнить дезинформацию
Руководство разъясняет вводящую в заблуждение информацию здесь:
Для
INNER
а такжеOUTER
типы соединения, должно быть указано условие соединения, а именно одно изNATURAL
,ON
условие соединения, или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
не требует условия соединения) и @Attila's недействительно.
Разница между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
запрос, из-за его связи со строками вне самого запроса.
Таблица базы данных
Имея следующую таблицу базы данных, в которой хранятся блоги, размещенные на нашей платформе:
И в настоящее время у нас есть два блога:
Намного лучше, правда?
В
age_in_years
рассчитывается для каждой записи
blog
стол. Таким образом, он работает как коррелированный подзапрос, но записи подзапроса объединяются с основной таблицей, и по этой причине мы можем ссылаться на столбцы, созданные подзапросом.
Одна вещь, которую никто не указал, это то, что вы можете использовать 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.
Во-первых, боковое и поперечное применение - это одно и то же. Поэтому вы также можете прочитать о 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. Боковое или поперечное применение может использоваться, когда нет простого условия соединения.
Существует более широкое применение для бокового или поперечного применения, но это наиболее распространенный, который я нашел.