【问题标题】:Postgres analogue to CROSS APPLY in SQL ServerPostgres 类似于 SQL Server 中的 CROSS APPLY
【发布时间】:2012-07-13 10:33:57
【问题描述】:

我需要将针对 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 中交叉应用。您可以像使用函数一样使用表函数。只需加入他们。
  • @a_horse_with_no_name - CROSS APPLY 使用相关参数重新执行 TVF,而不是执行一次然后加入结果。
  • 我意识到这很古老...@MartinSmith 如果该函数属于内联表值类型,则在 MSSQL 上不一定是这种情况,请参阅 Paul White 关于 MSSQL 查询计划器如何编写的文章有时可以将apply 优化为joinsqlservercentral.com/articles/APPLY/69954 因为我们在这里看不到原始代码,所以我推测这就是根据Erwin 回答的评论重新表现发生的事情。

标签: sql postgresql postgresql-9.1 cross-apply lateral


【解决方案1】:

此链接似乎显示了如何在 Postgres 9.0+ 中执行此操作:

PostgreSQL: parameterizing a recursive CTE

它在标题为“使用集合返回函数模拟 CROSS APPLY”部分的页面下方。请务必注意示例后的限制列表。

【讨论】:

  • 我很惊讶只有警察没有跳过这个链接。
【解决方案2】:

在 Postgres 9.3 或更高版本中使用 LATERAL 加入:

SELECT v.col_a, v.col_b, f.*  -- no parentheses here, 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


对于较旧的版本,有一种非常简单的方法可以完成我认为您正在尝试使用 set-returning 函数 (RETURNS TABLE or RETURNS SETOF record OR RETURNS record): p>

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) AS
$func$
SELECT s.col_c, s.col_d
FROM   some_tbl s
WHERE  s.col_a = $1
AND    s.col_b = $2
$func$ LANGUAGE sql;

如果要应用 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:在不了解许多因素的情况下,几乎不可能对此发表评论。我推测性能可以提高。
  • 我建议性能差异的原因是原始 MSSQL 查询可能没有为每一行执行函数。该函数可能是一个内联表值函数 (ITVF),并且查询优化器已执行为 join,而不是每一行的相关查询。在那种情况下,使用lateral 是不公平的比较。在任何 rdbms 中,为每一行执行用户定义的(在 sql 中)函数是一个糟糕的主意。这里有一个很好的例子来说明 MSSQL 查询计划器如何优化 ITVF:sqlservercentral.com/articles/APPLY/69954
【解决方案3】:

我喜欢 Erwin Brandstetter 的回答,但是我发现了一个性能问题: 运行时

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

f_citizen_rec_modified 函数将对其返回的每一列运行 1 次(乘以 v_citizenversions 中的每一行)。我没有找到这种效果的文档,但能够通过调试推断出来。现在问题变成了,我们如何才能获得这种效果(在 9.3 之前,横向连接可用)没有这种性能抢夺的副作用?

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

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

主要区别是首先获取原始函数结果(内部子查询),然后将其包装在另一个选择中,将这些结果输出到列中。这是在 PG 9.2 上测试的

【讨论】:

    【解决方案4】:

    死灵术:
    PostgreSQL 9.3 中的新功能:

    LATERAL 关键字

    左 |对 |内连接 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 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-07
      • 1970-01-01
      • 2014-12-04
      • 1970-01-01
      相关资源
      最近更新 更多