SQL 交叉应用

Posted

技术标签:

【中文标题】SQL 交叉应用【英文标题】:SQL cross apply 【发布时间】:2013-02-28 13:43:29 【问题描述】:

我有一个包含审计信息的 SQL 表:

GroupId AuditDate   ID  FirstName   LastName
1      01/06/2011   123 Michael    Jackson
1      01/09/2010   123 M          J
1      01/06/2009   123 Mike       J

并尝试显示审计记录之间的差异:

GroupId AuditDate   ID  Attribute   From    To
1      01/06/2011   123 FirstName   M       Michael
1      01/06/2011   123 LastName    J       Jackson
1      01/09/2010   123 FirstName   Mike    M
1      01/06/2009   123 FirstName   NULL    Mike
1      01/06/2009   123 LastName    NULL    J

我正在使用以下 SQL 查询:

WITH result AS (
SELECT          [Current].Id, 
                [Current].GroupId, 
                [Current].AuditDate, 
                [Current].FirstName, 
                [Current].LastName
                Previous.FirstName AS PFirstName,
                Previous.LastName AS PLastName,
FROM 
    (SELECT 
        *, ROW_NUMBER() OVER(PARTITION BY GroupId ORDER BY AuditDate ASC) AS RowNumber 
    FROM 
        AuditTable  
    WHERE 
        Id = @ID
    ) AS [Current]
LEFT JOIN
    (SELECT 
        *, ROW_NUMBER() OVER(PARTITION BY GroupId ORDER BY AuditDate ASC) AS RowNumber 
    FROM 
        AuditTable
    WHERE 
        Id = @ID
    ) AS [Previous]
ON
    [Current].RowNumber = [Previous].RowNumber + 1
)

SELECT r.Id,r.GroupId, r.AuditDate 
  x.Attribute,
  x.[From],
  x.[To]
FROM result r
CROSS APPLY 
(
    VALUES
        ('FirstName', t.FirstName, t.PFirstName),
        ('LastName', t.LastName, t.PLastName),
) x (Attribute, [To], [From])
where 
    ISNULL(x.[From],'') <> ISNULL(x.[To],'') 
ORDER BY r.AuditDate asc;

是否可以合并两个选择查询以提高性能?

【问题讨论】:

您是有性能问题,还是原则上只想尽快处理? 您还在使用 SQL Server 2012 吗? 你想要的结果是什么? 【参考方案1】:

试试这个查询

WITH result AS (
SELECT Id, 
       GroupId, 
       AuditDate, 
       FirstName, 
       LastName,          
       ROW_NUMBER() OVER(PARTITION BY GroupId ORDER BY AuditDate ASC) AS RowNumber 
FROM AuditTable  
WHERE Id = @ID
 )
SELECT r.Id,r.GroupId, r.AuditDate,
       x.Attribute,
       x.[From],
       x.[To]
FROM result r LEFT JOIN result r2 ON r.RowNumber = r2.RowNumber + 1
CROSS APPLY (
             VALUES ('FirstName', r.FirstName, r2.FirstName),
                    ('LastName', r.LastName, r2.LastName)
             ) x (Attribute, [To], [From])
WHERE ISNULL(x.[From],'') <> ISNULL(x.[To],'') 
ORDER BY r.AuditDate ASC;

SQLFiddle上的演示

【讨论】:

谢谢亚历山大。我还可以将“result r LEFT JOIN result r2”替换为实际结果查询,以将它们组合成一个查询。【参考方案2】:

您可以使用lag() 完全消除这两个子查询:

WITH result AS (
SELECT Id, 
       GroupId, 
       AuditDate, 
       FirstName, 
       LastName,
       lag(FirstName) over (PARTITION BY GroupId ORDER BY AuditDate ASC) 
          AS PFirstName,
       lag(LastName) over (PARTITION BY GroupId ORDER BY AuditDate ASC)
          AS PLastName
    FROM AuditTable  
    WHERE Id = @ID
)
...

Here is the relevant documentation.

更新:但是,不幸的是,这仅在 SQL Server 2012 中可用。如果您有较早的版本,您将需要某种自我加入。

如果你不能使用lag(),你至少应该能够将你的代码从3个查询减少到2个:在你的第一个select语句中包含行号,并留下一个子查询而不是加入它有两个子查询。我不确定这种方式或 Chris Moutray 的方式是否会更快。

WITH result AS (
SELECT ROW_NUMBER() OVER(PARTITION BY GroupId ORDER BY AuditDate ASC) AS RowNumber         
       [Current].Id, 
       [Current].GroupId, 
       [Current].AuditDate, 
       [Current].FirstName, 
       [Current].LastName
       [Previous].FirstName AS PFirstName,
       [Previous].LastName AS PLastName,
FROM AuditTable as [Current]
LEFT JOIN
    (SELECT 
        *, ROW_NUMBER() OVER(PARTITION BY GroupId ORDER BY AuditDate ASC) AS RowNumber 
    FROM 
        AuditTable
    WHERE 
        Id = @ID
    ) AS [Previous]
ON
    [Current].RowNumber = [Previous].RowNumber + 1
)

【讨论】:

感谢您的回复,不幸的是我使用的是 SQL Server 2008,所以我不能使用 lag() 所以我必须使用自连接(如您所述)。【参考方案3】:

您可以在 SQL Server 2012 中使用 LAG。我在这里使用了 UNION ALL 将列取消透视为行。

根据您的过滤方式和组级别,添加/修改 PARTITION BY

DECLARE @foo TABLE (GroupId tinyint, AuditDate date, ID tinyint, FirstName varchar(100),  LastName varchar(100));
INSERT @foo VALUES (1, '20110601', 123, 'Michael', 'Jackson'), (1, '20100901', 123, 'M', 'J'), (1, '20090601', 123, 'Mike', 'J');

SELECT
    X.GroupId, X.AuditDate, X.ID, X.[From], X.[To]
FROM
    (
    SELECT
        F.GroupId, F.AuditDate, F.ID, 'FirstName' AS Attribute, LAG(F.FirstName) OVER (/*PARTITION BY GroupId, ID*/ ORDER BY AuditDate) AS [From], F.FirstName AS [To]
    FROM
        @foo F
    UNION ALL
    SELECT
        F.GroupId, F.AuditDate, F.ID, 'LastName' AS Attribute, LAG(F.LastName) OVER (/*PARTITION BY GroupId, ID*/ ORDER BY AuditDate) AS [From], F.LastName AS [To]
    FROM
        @foo F
    ) X
WHERE
    ISNULL(X.[From], '') <> ISNULL(X.[To],  '')
ORDER BY
    X.AuditDate DESC, X.Attribute

【讨论】:

感谢您的回复。我正在使用 SQL Server 2008,所以我不能使用 LAG() 但了解此类功能很有用。

以上是关于SQL 交叉应用的主要内容,如果未能解决你的问题,请参考以下文章

sql在一个交叉应用/案例表达式中设置多个值

SQL 交叉应用没有结果

交叉应用 LINQ-TO-SQL

SQL Server 交叉应用

sql “交叉应用”版本的mysql

SQL 交叉应用性能问题