关于sql:LATERAL和PostgreSQL中的子查询有什么区别?

What is the difference between LATERAL and a subquery in PostgreSQL?

由于Postgres能够进行LATERAL连接,我一直在阅读它,因为我目前为我的团队执行复杂的数据转储,其中包含大量低效的子查询,这使得整个查询需要四分钟或更长时间。

我知道LATERAL加入可能能够帮助我,但即使在阅读了像Heap Analytics这样的文章之后,我仍然没有完全遵循。

LATERAL加入的用例是什么? LATERAL连接和子查询之间有什么区别?


更像是相关的子查询

LATERAL连接(Postgres 9.3或更高版本)更像是相关子查询,而不是普通子查询。就像Andomar指出的那样,LATERAL连接右边的函数或子查询必须为其左边的每一行计算一次 - 就像相关的子查询一样 - 而普通的子查询(表表达式)只被评估一次。 (但是,查询规划器还可以优化其中任何一种的性能。)
这个相关的答案有两个并排的代码示例,解决了同样的问题:

  • 优化GROUP BY查询以检索每个用户的最新行

对于返回多个列,LATERAL连接通常更简单,更清晰,更快速。
另外,请记住相关子查询的等价物是LEFT JOIN LATERAL ... ON true

  • 多次调用带有数组参数的set-returns函数

阅读LATERAL上的手册

它比我们在这里提出的任何答案更具权威性:

  • https://www.postgresql.org/docs/current/static/queries-table-expressions.html#QUERIES-LATERAL
  • http://www.postgresql.org/docs/current/static/sql-select.html

子查询无法做到的事情

有一些LATERAL连接可以做的事情,但(相关的)子查询不能(轻松)。相关子查询只能返回单个值,而不能返回多个列而不是多个行 - 除了裸函数调用(如果它们返回多行,则将结果行相乘)。但即使是某些set?返回函数也只能在FROM子句中使用。与Postgres 9.4或更高版本中具有多个参数的unnest()类似。手册:

This is only allowed in the FROM clause;

所以这可行,但不能轻易用子查询替换:

1
2
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 [, ... ] )的特例:

  • 如何声明set-returning-function只允许在FROM子句中使用?

SELECT列表中设置返回函数

您也可以直接在SELECT列表中使用unnest()等设置返回函数。这曾经在Postgres 9.6的同一SELECT列表中表现出令人惊讶的行为,其中有多个此类函数。但它最终被Postgres 10消毒了,现在是一个有效的替代品(即使不是标准的SQL)。看到:

  • SELECT子句中多个set-returns函数的预期行为是什么?

以上面的例子为基础:

1
2
SELECT *, unnest(a1) AS elem1, unnest(a2) AS elem2
FROM   tbl;

比较:

dbfiddle for pg 9.6 here
dbfiddle for pg 10 here

澄清错误信息

手册:

For the INNER and OUTER join types, a join condition must be
specified, namely exactly one of NATURAL, ON join_condition,
or USING (join_column [, ...]). See below for the meaning.
For CROSS JOIN, none of these clauses can appear.

所以这两个查询是有效的(即使不是特别有用):

1
2
3
4
5
6
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;

虽然这个不是:

1
2
3
<strike>SELECT *
FROM   tbl t
LEFT   JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;</strike>

这就是为什么@Andomar的代码示例是正确的(CROSS JOIN不需要连接条件)并且@Attila的 无效。


laterallateral连接之间的区别在于您是否可以查看左侧表的行。例如:

1
2
3
4
5
6
7
8
SELECT  *
FROM    table1 t1
CROSS JOIN lateral
        (
        SELECT  *
        FROM    t2
        WHERE   t1.col1 = t2.col1 -- Only allowed because of lateral
        ) sub

这种"向外看"意味着必须对子查询进行多次评估。毕竟,t1.col1可以假定许多值。

相比之下,非lateral连接后的子查询可以被评估一次:

1
2
3
4
5
6
7
8
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中实现了很长时间,因此您可以在Lateral中找到有关它的更多信息。

其次,根据我的理解,使用子查询而不是使用横向,没有什么是你无法做到的。但:

考虑以下查询。

1
2
3
4
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

在这种情况下你可以使用横向。

1
2
3
4
5
6
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查询在每个选定的行上应用用户定义的函数。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
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中做这种事情的唯一方法。