自从PostgreSQL出现了执行LATERAL
连接的能力,为了给我的团队生成大量数据转储,我一直在阅读这方面的内容。由于使用了许多低效子查询,导致整个查询需要四分钟或更长时间。
我知道LATERAL
连接也许可以帮助我,但即使阅读像Heap Analytics的这篇文章这样的文章之后,我还是不太理解。
LATERAL
连接的用例是什么?LATERAL
连接和子查询有什么区别?
自从PostgreSQL出现了执行LATERAL
连接的能力,为了给我的团队生成大量数据转储,我一直在阅读这方面的内容。由于使用了许多低效子查询,导致整个查询需要四分钟或更长时间。
我知道LATERAL
连接也许可以帮助我,但即使阅读像Heap Analytics的这篇文章这样的文章之后,我还是不太理解。
LATERAL
连接的用例是什么?LATERAL
连接和子查询有什么区别?
这个功能是在PostgreSQL 9.3版本中引入的。手册:
在
FROM
子句中出现的子查询可以在前面加上关键字LATERAL
。这使得它们可以引用前面FROM
项提供的列。(没有LATERAL
,每个子查询都是独立评估的,因此不能相互引用任何其他FROM
项。)在
FROM
子句中出现的表函数也可以在前面加上关键字LATERAL
,但对于函数来说,关键字是可选的;函数的参数可以包含对前面FROM
项提供的列的引用。
手册中提供了基本的代码示例。
有些事情一个(相关)子查询无法轻松做到,但是一个LATERAL
连接可以。一个相关子查询只能返回单个值,而不能返回多列或多行 - 除非是裸露的函数调用(如果函数返回多行,则会产生多个结果行)。但是,即使是某些返回集合的函数也只允许在FROM
子句中使用,比如在Postgres 9.4或更高版本中带有多个参数的unnest()
函数。参考手册:
这只允许在
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
列表中的返回集函数您也可以直接在SELECT
列表中使用unnest()
等返回集函数。在Postgres 9.6之前,当在同一个SELECT
列表中使用多个这样的函数时,可能会出现令人惊讶的行为。但在Postgres 10中已经得到了改进,现在是一个有效的替代方法(即使不是标准SQL)。请参阅:
在上面的示例基础上构建:
SELECT *, unnest(a1) AS elem1, unnest(a2) AS elem2
FROM tbl;
比较:
需要注意:在SELECT
列表中使用返回集合的函数(组合)产生没有行的情况会消除该行。在内部,它被翻译为CROSS JOIN LATERAL ROWS FROM ...
,而不是LEFT JOIN LATERAL ... ON true
!
演示了pg 16的示例中的差异。
SELECT *
FROM tbl t
LEFT JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t <b>ON TRUE</b>;
SELECT *
FROM tbl t, LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;
这就是为什么Andomar的代码示例是正确的(SELECT * FROM tbl t LEFT JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;
CROSS JOIN
不需要连接条件),而Attila的LATERAL
子查询中的窗口函数:http://gis.stackexchange.com/a/230070/7244 - Erwin Brandstetter非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
,内部查询不会以任何方式依赖于外部查询。一种称为 correlated
查询的示例是 lateral
查询,因为它与查询本身之外的行存在关系。
select * from table1 left join t2 using (col1)
与使用 on
条件的连接有何不同?我不确定在何种情况下使用 lateral
更为合适。 - No_name以下是存储我们平台上托管的博客的 blog
数据库表:
目前,我们有两个托管的博客:
id | created_on | title | url |
---|---|---|---|
1 | 2013-09-30 | Vlad Mihalcea's Blog | https://vladmihalcea.com |
2 | 2017-01-22 | Hypersistence | https://hypersistence.io |
我们需要从 blog
表中提取以下数据以构建报告:
如果您使用 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
age_in_years
需要定义三次,因为在计算next_anniversary
和days_to_next_anniversary
值时需要用到它。LATERAL JOIN
语法:
CROSS APPLY
和OUTER APPLY
来模拟LATERAL JOIN
。age_in_years
的值,并在计算next_anniversary
和days_to_next_anniversary
值时将其传递给下一步。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
age_in_years
值并重复使用它来计算next_anniversary
和days_to_next_anniversary
:
博客ID | 年龄(岁) | 下一周年纪念日日期 | 距离下一周年纪念日天数 |
---|---|---|---|
1 | 7 | 2021-09-30 | 295 |
2 | 3 | 2021-01-22 | 44 |
是不是好多了?
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中知道的唯一方法。
首先,Lateral和Cross Apply是一回事。 因此,您也可以阅读关于Cross Apply的内容。由于它在SQL Server中已经实现了很长时间,因此您将找到比Lateral更多的信息。
其次,根据我的理解,使用子查询而不是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子句的限制,在此查询中无法使用普通连接。可以使用Lateral或Cross Apply(当没有简单的连接条件时)。
Lateral或Cross Apply还有更多用途,但这是我发现的最常见的用法。
lateral
而不是apply
。也许微软已经对这种语法进行了专利申请? - AndomarLEFT JOIN
需要一个连接条件。除非你想进行限制,否则将其设置为ON TRUE
。 - Erwin BrandstetterX.Fk1
上进行连接,因为它不在子查询的结果中。如果您在子查询中包含Fk1
,则它将在形式上是正确的,但仍会表现出奇异的行为。它从B
中选择任意一行,并仅在Fk1
恰好匹配A.PK
时将其连接到A
。 - Erwin Brandstetter
LATERAL
关键字属于其后面的“派生表”(子查询),即它不是一种 JOIN 类型。 - jarlh