LATERAL和PostgreSQL中的子查询之间有什么区别?
由于Postgres具有执行LATERAL
连接的function,因此我一直在阅读,因为我目前正在为我的团队进行复杂的数据转储,导致大量低效的子查询,使整个查询花费四分钟或更长时间。
我明白, LATERAL
join可能会帮助我,但即使从Heap Analytics阅读这样的文章之后,我仍然不太理解。
什么是LATERAL
连接的用例? LATERAL
连接和子查询有什么区别?
更像是一个相关的子查询
一个LATERAL
连接(Postgres 9.3+)更像是一个相关的子查询 ,而不是一个普通的子查询。 就像@Andomar指出的那样 ,一个LATERAL
连接权限的函数或子查询通常必须被多次计算 – 对于LATERAL
连接剩下的每一行,就像一个相关的子查询一样 – 而一个纯粹的子查询(expression式)是只评估一次 。 (查询计划人员虽然有办法优化性能。
这个相关的答案有两个并排的代码示例,解决了同样的问题:
- 优化GROUP BY查询以检索每个用户的最新logging
对于返回多个列 , LATERAL
连接通常更简单,更干净,更快速。 另外,请记住相关的子查询的等价物是LEFT JOIN LATERAL ... ON true
:
- 使用数组参数多次调用set-returning函数
阅读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
连接可以做的事情,但(相关的)子查询不能(容易)。 一个相关的子查询只能返回一个单一的值,而不是多个列而不是多个行 – 除了裸函数调用(如果它们返回多行,则返回结果行)。 但是,即使某些设置返回函数只能在FROM
子句中使用。 就像Postgres 9.4中带有多个参数的新unnest()
一样。 手册:
这只在
FROM
子句中是允许的。
所以这个工作,但不能轻易地被一个子查询replace:
CREATE TABLE tbl (a1 int[], a2 int[]); SELECT * FROM tbl t, unnest(t.a1, t.a2) u(elem1, elem2); -- implicit LATERAL
( FROM
子句中的逗号( ,
)是CROSS JOIN
简称。
LATERAL
被自动假定为表函数。)
关于UNNEST( array_expression [, ... ] )
这个问题的更多关于UNNEST( array_expression [, ... ] )
特例:
- 你如何声明一个只能在FROM子句中使用的set-returning-function?
在SELECT
列表中设置返回函数
你也可以直接在SELECT
列表中使用像unnest()
这样的设置返回函数。 这用于展示令人惊讶的行为,在同一个SELECT
列表中有多个实例,直到Postgres 9.6。 但它终于被Postgres 10消毒了,现在是一个有效的select(即使不是标准的SQL)。
基于上面的例子:
SELECT *, unnest(t.a1) AS elem1, unnest(t.a2) AS elem2 FROM tbl t;
比较:
这里是pg 9.6的dbfiddle
这里是第10页的dbfiddle
澄清错误信息
该手册在此澄清误导性信息:
对于
INNER
和OUTER
连接types,必须指定连接条件,即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的代码示例是正确的( CROSS JOIN
不需要连接条件), @ Attila的 是无效的。
非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
查询的一个例子,因为它与查询本身之外的行之间的关系。
首先, 横向和交叉应用是一回事 。 因此,您也可以阅读有关交叉申请。 由于它已经在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
在这个查询中,由于限制子句,你不能使用正常的连接。 当没有简单的连接条件时,可以使用横向或交叉应用。
有更多的横向或交叉适用的用法,但这是我发现最常见的。