Postgres中与SQL Server中的CROSS APPLY类似的操作是什么?

32

我需要将编写在MS SQL Server 2005的SQL查询迁移到Postgres 9.1。
在此查询中,最好用什么替代CROSS APPLY

SELECT *
FROM V_CitizenVersions         
CROSS APPLY     
       dbo.GetCitizenRecModified(Citizen, LastName, FirstName, MiddleName,
BirthYear, BirthMonth, BirthDay, ..... ) -- lots of params

GetCitizenRecModified() 函数是一个表值函数。我无法放置此函数的代码,因为它非常庞大,涉及一些复杂的计算,而且我也不能放弃它。


在Postgres中,您不需要使用CROSS APPLY。您可以像使用函数一样使用表函数。只需将它们连接即可。 - user330315
1
@a_horse_with_no_name - CROSS APPLY 会使用相关参数重新执行 TVF,而不是执行一次然后连接结果。 - Martin Smith
1
我意识到这是古老的... @MartinSmith 如果该函数是内联表值类型,则在MSSQL上不一定如此,可以参见Paul White关于MSSQL查询计划程序如何有时将apply优化为join的说明:http://www.sqlservercentral.com/articles/APPLY/69954/ 由于我们在这里看不到原始代码,所以我根据对Erwin答案性能的评论进行了推测。 - Davos
4个回答

44

在Postgres 9.3或更新版本中,使用LATERAL连接:

SELECT v.col_a, v.col_b, f.*  -- no parentheses, f is a table alias
FROM   v_citizenversions v
LEFT   JOIN LATERAL f_citizen_rec_modified(v.col1, v.col2) f ON true
WHERE  f.col_c = _col_c;

为什么要使用LEFT JOIN LATERAL ... ON true


对于旧版本,有一种非常简单的方法可以通过返回一个集合的函数(RETURNS TABLERETURNS SETOF recordRETURNS record)来实现我认为你试图实现的功能:

SELECT *, (f_citizen_rec_modified(col1, col2)).*
FROM   v_citizenversions v

该函数针对外查询的每一行计算一次值。如果函数返回多行,结果行相应地进行乘法运算。为了分解行类型,所有括号在语法上都是必需的。表函数可能如下所示:

CREATE OR REPLACE FUNCTION f_citizen_rec_modified(_col1 int, _col2 text)
  RETURNS TABLE(col_c integer, col_d text)
  LANGUAGE sql AS
$func$
SELECT s.col_c, s.col_d
FROM   some_tbl s
WHERE  s.col_a = $1
AND    s.col_b = $2
$func$;

如果您想应用WHERE子句,因为这些列在同一级别上不可见,所以您需要将其包装在子查询或CTE中。(而且出于性能考虑,这样做更好,因为您可以防止函数的每个输出列重复评估):

SELECT col_a, col_b, (f_row).*
FROM  (
   SELECT col_a, col_b, f_citizen_rec_modified(col1, col2) AS f_row
   FROM   v_citizenversions v
   ) x
WHERE (f_row).col_c = _col_c;

还有几种其他方法可以实现这个或类似的需求。具体取决于你想要什么。


我使用了你提出的查询。现在我很震惊:这个查询执行超过一分钟。在 MS SQL 中只需要不到一秒钟 O_O。 - user1178399
1
@user1178399:不了解其中的许多因素,很难对此发表评论。我猜测性能可以得到改善。 - Erwin Brandstetter
我建议性能差异的原因可能是原始MSSQL查询没有为每一行执行函数。该函数很可能是一个内联表值函数(ITVF),查询优化程序已将其作为“join”而不是相关查询为每一行执行。在这种情况下,使用“lateral”进行比较是不公平的。在任何rdbms中,为每一行执行用户定义(在sql中)的函数都是一个可怕的想法。这里有一个很好的例子,展示了MSSQL查询规划器如何优化ITVF: http://www.sqlservercentral.com/articles/APPLY/69954/ - Davos

30

死灵术:
PostgreSQL 9.3 的新功能:

LATERAL 关键字

left | right | inner JOIN LATERAL

INNER JOIN LATERALCROSS APPLY 相同
LEFT JOIN LATERALOUTER APPLY 相同

示例用法:

SELECT * FROM T_Contacts 

--LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1 
--WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989


LEFT JOIN LATERAL 
(
    SELECT 
         --MAP_CTCOU_UID    
         MAP_CTCOU_CT_UID   
        ,MAP_CTCOU_COU_UID  
        ,MAP_CTCOU_DateFrom 
        ,MAP_CTCOU_DateTo   
   FROM T_MAP_Contacts_Ref_OrganisationalUnit 
   WHERE MAP_CTCOU_SoftDeleteStatus = 1 
   AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID 

    /*  
    AND 
    ( 
        (__in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo) 
        AND 
        (__in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom) 
    ) 
    */
   ORDER BY MAP_CTCOU_DateFrom 
   LIMIT 1 
) AS FirstOE ON true 

2
我很喜欢Erwin Brandstetter的回答,但是我发现一个性能问题: 在运行时
SELECT *, (f_citizen_rec_modified(col1, col2)).*
FROM   v_citizenversions v

f_citizen_rec_modified函数将根据其返回的每一列(乘上v_citizenversions中的每一行)运行1次。我没有找到该效果的文档,但通过调试后能够推断出来。现在问题变成了,如何在没有这种性能损失的副作用(9.3之前无法使用lateral join的情况下)获得此效果?

更新:我似乎已经找到了答案。将查询重写如下:

select x.col1, x.col2, x.col3, (x.func).* 
FROM (select SELECT v.col1, v.col2, v.col3, f_citizen_rec_modified(col1, col2) func
FROM   v_citizenversions v) x

关键的区别在于首先获取原始函数结果(内部子查询),然后将其包装在另一个select中,将这些结果转换为列。这在PG 9.2上进行了测试。


1

这个链接似乎展示了如何在Postgres 9.0+中实现:

PostgreSQL:参数化递归CTE

它在页面下方的“使用返回集函数模拟CROSS APPLY”一节中。请务必注意示例后面的限制列表。


我很惊讶只有警察没有介入此事。 - Mitch Wheat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接