【问题标题】:SQL Server : CROSS APPLY equivalent for ROW_NUMBER() querySQL Server:CROSS APPLY 等效于 ROW_NUMBER() 查询
【发布时间】:2018-10-11 15:31:19
【问题描述】:

我正在尝试寻找一种不同的方法来使用CROSS APPLY 编写ROW_NUMBER() 查询,以便比较性能。

在下面的简单示例中,创建了一个员工表,插入了一些测试数据,并使用带有ROW_NUMBER() 窗口函数的SELECT 来查找每个部门中任期最长的员工。

还有其他方法可以使用CROSS APPLY 编写SELECT 吗?

CREATE TABLE [dbo].[tblEmployee]
(
    [ID] [INT] NOT NULL,
    [DeptID] [TINYINT] NOT NULL,
    [EmpName] [VARCHAR](100) NOT NULL,
    [Tenure] [TINYINT] NOT NULL,

    CONSTRAINT [PK_tblEmployee] 
        PRIMARY KEY CLUSTERED ([ID] ASC)
) ON [PRIMARY]
GO

INSERT INTO dbo.tblEmployee (ID, DeptID, EmpName, Tenure) 
VALUES ('1', '1', 'John', 2),
       ('2', '1', 'Mary', 5),
       ('3', '2', 'Joe', 3),
       ('4', '3', 'Bill', 10),
       ('5', '2', 'Marilynn', 9),
       ('6', '3', 'Sue', 7);

SELECT 
    EmpName, DeptID, Tenure 
FROM  
    (SELECT 
         EmpName, DeptID, Tenure, 
         ROW_NUMBER() OVER(PARTITION BY DeptID ORDER BY Tenure DESC) TenureRank
     FROM 
         tblEmployee) e 
WHERE 
    e.TenureRank = 1
ORDER BY 
    DeptID

编辑:我不希望将 CTE 用作 SELECT

的一部分

【问题讨论】:

  • 有,但您不想这样做。效率会低得多。
  • 除了 CROSS APPLY 以外的其他选项呢……还有什么可能更有效的吗?
  • 这取决于。如果两个或多个员工的任期相同,您想怎么做?
  • @JoelCoehoorn 请假设不会有任何关系。我只是使用这个简单的示例来找出其他查询选项。在我的现实生活场景中,不会有任何联系。

标签: sql sql-server sql-server-2014


【解决方案1】:

cross apply 等效项是:

select e.*, a.seqnum
from tblEmployee e cross apply
     (select count(*) as seqnum
      from tblEmployee e2
      where e2.deptid = e.deptid and
            (e2.tenure > e.tenure or
             e2.tenure = e.tenure and e2.id >= e.id
            )
     ) a;

您不会想这样做,因为它的效率比row_number() 低得多。请注意使用id 进行比较以确保唯一编号。

【讨论】:

  • 我认为你可以使用 TOP 1 样式应用做得更好......仍然不会击败 row_number(),但它是某个时候。
  • @Zack。 . .无需澄清。 row_number() 的行为方式非常明确。
  • 是的,因为它明确提供“不保证使用 ROW_NUMBER() 的查询返回的行将在每次执行时的顺序完全相同[具有非唯一列]”。困惑是 OP 想要如何处理潜在的关系。由于 OP 声明关系不是问题,因此无论如何这都是有争议的。 :)
  • @Zack 你可能会错过其中的“非独特”部分。如果OVER 子句的ORDER BY 部分足够清晰,可以定义严格的顺序,则函数是确定性的。
【解决方案2】:

您可能有比扫描整个 tblEmployee 表更好的部门 ID 来源:

SELECT a.* 
FROM (SELECT DISTINCT DeptID FROM tblEmployee) d
CROSS APPLY (
    SELECT TOP 1 EmpName, DeptID, Tenure
    FROM tblEmployee e
    WHERE e.DeptID = d.DeptID
    ORDER BY Tenure DESC
) a

这个特定示例不太可能支持APPLY 方法,因为您还需要设计源值。

当您处理已经拥有源数据的查询时(例如,您还需要部门名称,因此您自然而然地从想象中的tblDepartment 表开始),应用可能更有意义。当您需要从多个地方获取此类信息时(在查询中拥有多个 APPLY 比 ROW_NUMBER() 更容易),或者当 APPLY 操作本身是更复杂的子查询时,它也可以做得很好,查看几张表。

【讨论】:

    【解决方案3】:

    您实际上可以在不使用CROSS APPLYROWNUM 的情况下执行此操作。您可以创建一个派生表,列出每个部门的最长任期,然后将其连接到基本员工表。使用 CTE,您的查询将如下所示:

    WITH 
        DeptWithMaxTenure AS
        (
            SELECT DeptID, Max(Tenure) AS MaxTenure
            FROM tblEmployee
            GROUP BY DeptID
        )
    SELECT
        tblEmployee.EmpName,
        tblEmployee.DeptID,
        tblEmployee.Tenure
    FROM tblEmployee
        INNER JOIN DeptWithMaxTenure ON
            DeptWithMaxTenure.DeptID = tblEmployee.DeptID
            AND DeptWithMaxTenure.MaxTenure = tblEmployee.Tenure
    

    【讨论】:

    • 如果两个员工的任期相同,这可能会为一个部门生成两条记录。
    • 谢谢,但我忘了提到我正在努力避免使用 CTE。我在复制环境中使用 CTE 的经历非常糟糕。
    • @user2966445 很遗憾听到这个消息。 CTE 非常棒,您绝对应该再给他们一次机会。只是也许没有这个机会。
    • @JoelCoehoorn:由于 OP 想要“找到每个部门中任期最长的员工”,我认为您希望在该用例中返回多个员工(虽然,当然,OP必须澄清)。如果您不想使用 CTE,可以将表表达式直接放在查询中。然而,它最终真的是一样的。
    • row_number 解决方案没有给出平局断路器,因此不确定在哪里有平局 - 所以这是一个潜在的问题。前 1 名也是如此,它可能会给出不同的结果
    【解决方案4】:

    我试过了,它的速度是原来的 4 倍

    SELECT A.EmpName, A.DeptID, A.Tenure FROM tblEmployee A 
                   CROSS APPLY 
                        (SELECT TOP 1 B.ID FROM tblEmployee B 
                              WHERE B.DeptID = A.DeptID Order By Tenure Desc) CA
    WHERE CA.ID = A.ID;
    

    【讨论】:

    • 哪个性能更好取决于数据分布和索引。 itprotoday.com/software-development/…
    • 我想知道 TOP 1 是否比可能必须计算所有 row_numbers 更有效,然后选择那些为 1 的(不管性能优化器有多聪明)——即我们不关心工作出来是第 50,51,52.... 行等 - 我们只需要最大值
    猜你喜欢
    • 2019-06-09
    • 1970-01-01
    • 2014-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多