由于Postgres能够进行LATERAL
加入,我一直在阅读它,因为我目前为我的团队执行复杂的数据转储,其中包含许多低效的子查询,这些子查询可以进行整体查询需要四分钟或更长时间。
我了解LATERAL
加入可能能够帮助我,但即使在阅读了来自Heap Analytics的文章this one之后,我仍然没有完全遵循。
LATERAL
加入的用例是什么? LATERAL
加入和子查询之间有什么区别?
答案 0 :(得分:117)
LATERAL
联接(Postgres 9.3或更高版本)更像是correlated subquery,而不是普通的子查询。与Andomar pointed out类似,LATERAL
联接右侧的函数或子查询必须为其左侧的每一行计算一次 - 就像相关的子查询一样 - 而平原子查询(表表达式)仅计算一次。 (但是查询规划器有两种优化性能的方法。)
这个相关的答案有两个并排的代码示例,解决了同样的问题:
要返回多个列,LATERAL
联接通常更简单,更清晰,更快。
另外,请记住,相关子查询的等价物是 LEFT JOIN LATERAL ... ON true
:
LATERAL
它比我们在这里提出的答案更具权威性:
是 LATERAL
联接可以做的事情,但是(相关的)子查询不能(轻松)。相关子查询只能返回单个值,而不能返回多个列而不是多个行 - 除了裸函数调用(如果它们返回多行,则将结果行相乘)。但即使是某些设置返回函数也只能在FROM
子句中使用。与Postgres 9.4或更高版本中具有多个参数的unnest()
类似。 The manual:
这只能在
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
等设置返回功能。这曾经在Postgres 9.6的同一SELECT
列表中表现出令人惊讶的行为,其中有多个此类函数。 But it has finally been sanitized with Postgres 10并且现在是一个有效的替代方案(即使不是标准SQL)。参见:
以上面的例子为基础:
SELECT *, unnest(a1) AS elem1, unnest(a2) AS elem2
FROM tbl;
比较:
dbfiddle for pg 9.6 here
dbfiddle for pg 10 here
对于
INNER
和OUTER
联接类型,必须是连接条件 指定的,即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;
虽然这不是:
SELECT *
FROM tbl t
LEFT JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) t;
这就是@Andomar's代码示例正确的原因(CROSS JOIN
不需要连接条件)而@Attila's 无效。
答案 1 :(得分:28)
非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
查询的示例,因为它与查询本身之外的行的关系。
答案 2 :(得分:9)
首先,Lateral and Cross Apply is same thing。因此,您也可以阅读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子句,您无法使用普通连接。 可以使用横向或交叉应用when there is not simple join condition。
横向或交叉应用的用途更多,但这是我发现的最常见的用途。
答案 3 :(得分:8)
有以下 blog
数据库表存储我们平台托管的博客:
而且,我们目前托管了两个博客:
id | created_on | 标题 | 网址 |
---|---|---|---|
1 | 2013-09-30 | Vlad Mihalcea 的博客 | https://vladmihalcea.com |
2 | 2017-01-22 | 过度坚持 | 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 可以帮助我们的地方。
以下关系数据库系统支持 LATERAL JOIN
语法:
SQL Server 可以使用 LATERAL JOIN
和 CROSS APPLY
模拟 OUTER APPLY
。
LATERAL JOIN 允许我们重用 age_in_years
值,并在计算 next_anniversary
和 days_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
而且,age_in_years
值可以计算为 1 并重用于 next_anniversary
和 days_to_next_anniversary
计算:
blog_id | age_in_years | next_anniversary | days_to_next_anniversary |
---|---|---|---|
1 | 7 | 2021-09-30 | 295 |
2 | 3 | 2021-01-22 | 44 |
好多了,对吧?
age_in_years
是为 blog
表的每条记录计算的。因此,它的工作方式类似于相关子查询,但子查询记录与主表连接,因此,我们可以引用子查询生成的列。
答案 4 :(得分:1)
没有人指出的一件事是,您可以使用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中做这种事情的唯一方法。