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

jdo*_*dot 119 sql postgresql subquery lateral-join

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

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

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

Erw*_*ter 136

更像是相关的子查询

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
Run Code Online (Sandbox Code Playgroud)

该子句中的逗号(,)FROM是简短表示法CROSS JOIN.
LATERAL自动为表函数假设.
更多关于特殊情况UNNEST( array_expression [, ... ] ):

SELECT列表中设置返回函数

您也可以直接使用列表unnest()中的set-returns函数SELECT.这曾经表现出令人惊讶的行为,在SELECTPostgres 9.6 的同一列表中有多个这样的功能.但它最终被Postgres 10消毒了,现在是一个有效的替代品(即使不是标准的SQL).看到:

以上面的例子为基础:

SELECT *, unnest(a1) AS elem1, unnest(a2) AS elem2
FROM   tbl;
Run Code Online (Sandbox Code Playgroud)

比较:

dbfiddle for pg 9.6 here
dbfiddle for pg 10 here

澄清错误信息

手册:

对于INNEROUTER连接类型,连接条件必须被指定,即正好一个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;
Run Code Online (Sandbox Code Playgroud)

虽然这个不是:

SELECT *
FROM   tbl t
LEFT   JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;
Run Code Online (Sandbox Code Playgroud)

这就是为什么@ Andomar的代码示例是正确的(在CROSS JOIN不需要连接条件),并@阿提拉 就是无效.

  • 更干净更快?在某些情况下,像幅度更快。在切换到 LATERAL 后,我有一个查询从几天到几秒钟。 (2认同)

And*_*mar 37

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

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

这种"向外看"意味着必须不止一次地评估子查询.毕竟,t1.col1可以假设很多值.

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

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

根据需要lateral,内部查询不以任何方式依赖于外部查询.甲lateral查询是一个的例子correlated,因为它与查询本身以外的行关系查询.

  • `select * from table1 left join t2 using (col1)` 比较如何?我不清楚何时使用 / on 条件进行连接是不够的,而使用横向连接会更有意义。 (6认同)
  • 这是横向连接的最干净的解释。 (2认同)

Vla*_*cea 15

数据库表

有以下blog数据库表存储我们平台托管的博客:

博客表

而且,我们目前托管了两个博客:

ID 创建于 标题 网址
1 2013-09-30 Vlad Mihalcea 的博客 https://vladmihalcea.com
2 2017-01-22 过度坚持 https://hypersistence.io

不使用 SQL LATERAL JOIN 获取我们的报告

我们需要构建一个报告,从blog表中提取以下数据:

  • 博客 ID
  • 博客时代,以年为单位
  • 下一个博客周年纪念日
  • 距下一个周年纪念日的剩余天数。

如果您使用的是 PostgreSQL,则必须执行以下 SQL 查询:

SELECT
  b.id as blog_id,
  extract(
    YEAR FROM age(now(), b.created_on)
  ) AS age_in_years,
  date(
    created_on + (
      extract(YEAR FROM age(now(), b.created_on)) + 1
    ) * interval '1 year'
  ) AS next_anniversary,
  date(
    created_on + (
      extract(YEAR FROM age(now(), b.created_on)) + 1
    ) * interval '1 year'
  ) - date(now()) AS days_to_next_anniversary
FROM blog b
ORDER BY blog_id
Run Code Online (Sandbox Code Playgroud)

如您所见,age_in_years必须定义三次,因为在计算next_anniversarydays_to_next_anniversary值时需要它。

而且,这正是 LATERAL JOIN 可以帮助我们的地方。

使用 SQL LATERAL JOIN 获取报告

以下关系数据库系统支持该LATERAL JOIN语法:

  • 从 12c 开始的 Oracle
  • 从 9.3 开始的 PostgreSQL
  • MySQL 从 8.0.14 开始

SQL Server 可以模拟LATERAL JOIN使用CROSS APPLYOUTER APPLY.

LATERAL JOIN 允许我们重用该age_in_years值,并在计算next_anniversarydays_to_next_anniversary值时进一步传递它。

可以重写前面的查询以使用 LATERAL JOIN,如下所示:

SELECT
  b.id as blog_id,
  age_in_years,
  date(
    created_on + (age_in_years + 1) * interval '1 year'
  ) AS next_anniversary,
  date(
    created_on + (age_in_years + 1) * interval '1 year'
  ) - date(now()) AS days_to_next_anniversary
FROM blog b
CROSS JOIN LATERAL (
  SELECT
    cast(
      extract(YEAR FROM age(now(), b.created_on)) AS int
    ) AS age_in_years
) AS t
ORDER BY blog_id
Run Code Online (Sandbox Code Playgroud)

并且,该age_in_years值可以计算为 1 并重用于next_anniversarydays_to_next_anniversary计算:

博客ID 岁数 下一个周年纪念 days_to_next_anniversary
1 7 2021-09-30 295
2 3 2021-01-22 44

好多了,对吧?

age_in_years计算了每一个记录blog表。因此,它的工作方式类似于相关子查询,但子查询记录与主表连接,因此,我们可以引用子查询生成的列。

  • 谢谢,但我想你错过了“年龄_年_年是为博客表的每条记录计算的。所以,它的工作原理就像一个相关子查询,但子查询记录与主表连接,因此,我们可以参考子查询生成的列”部分强调了 LATERAL JOIN 的使用。 (2认同)

The*_*ith 10

没有人指出的一件事是您可以使用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);
Run Code Online (Sandbox Code Playgroud)

这是我知道如何在 PostgreSQL 中做这种事情的唯一方法。


Ati*_*gur 9

首先,横向和交叉应用是一回事.因此,您也可以阅读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 
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

在此查询中,由于limit子句,您无法使用普通连接.当没有简单的连接条件时,可以使用横向或交叉应用.

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

  • @Andomar AFAIK`lateral`是SQL标准,但`apply`不是. (8认同)
  • “LEFT JOIN”需要连接条件。除非你想以某种方式进行限制,否则请将其设置为“ON TRUE”。 (2认同)
  • @Andomar:受到这种错误信息的刺激,我添加了另一个答案来澄清。 (2认同)