LATERAL和PostgreSQL中的子查询有什么区别?


146

由于Postgres具有LATERAL连接的能力,因此我一直在阅读,因为我目前为团队执行复杂的数据转储,其中包含许多效率低下的子查询,使得整个查询需要四分钟或更长时间。

我知道LATERAL联接可能会为我提供帮助,但是即使阅读了Heap Analytics中的此类文章,我仍然不太了解。

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


Answers:


163

更像是相关子查询

LATERAL加入(Postgres的9.3或更高版本)更像是一个相关子查询,而不是一个简单的子查询。就像Andomar指出的那样联接右边的函数或子查询必须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列表中使用返回集合的函数。在过去的SELECTPostgres 9.6列表中,它经常表现出令人惊讶的行为,并具有多个这样的功能。但是它终于用Postgres 10进行了消毒,现在是有效的替代方法(即使不是标准的SQL)。看到:

在以上示例的基础上:

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

比较:

dbfiddle为PG 9.6 这里
dbfiddle为第10页这里

澄清错误信息

手册:

对于INNEROUTER连接类型,必须指定一个连接条件,即NATURAL、、ON join_conditionUSINGjoin_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 JOIN不能做的。类似于窗口功能。就像这里一样
Evan Carroll

@EvanCarroll:我在链接中找不到任何相关的子查询。但是我添加了另一个答案来演示LATERAL子查询中的窗口函数:gis.stackexchange.com/a/230070/7244
Erwin Brandstetter

1
更干净更快?在某些情况下,幅度更快。切换到LATERAL后,我的查询从几天到几秒不等。
罗维科

51

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

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,因为它与查询本身以外的行关系查询。


5
这是横向连接的最清晰的解释。
1valdis

易于理解的解释,谢谢。
arilwan

怎么select * from table1 left join t2 using (col1)比较?对我尚不清楚何时使用/ on条件的联接不足,并且使用侧向更为有意义。
No_name

9

首先,横向和交叉应用是同一回事。因此,您可能还会阅读有关交叉申请的信息。由于它是在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子句,您不能使用普通联接。当没有简单的连接条件时,可以使用横向或交叉应用。

横向或交叉应用的用法更多,但这是我发现的最常见的用法。


1
确实,我想知道PostgreSQL为什么使用lateral而不是apply。也许微软为该语法申请了专利?
Andomar

9
@Andomar AFAIK lateral在SQL标准中,但apply不是。
亩太短了

LEFT JOIN需要连接条件。ON TRUE除非您想以某种方式进行限制,否则就这么做。
Erwin Brandstetter

欧文是正确的,除非使用cross joinon条件,否则您将得到一个错误
Andomar 2015年

1
@Andomar:受此错误信息的刺激,我添加了另一个答案来澄清。
Erwin Brandstetter

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中做这种事情的唯一方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.