为什么多个 COUNT 比使用 CASE 的一个 SUM 快?

Tim*_*ter 14 performance sql-server-2005 sql-server t-sql

我想知道以下两种方法中哪一种更快:

1) 三COUNT

 SELECT Approved = (SELECT COUNT(*) FROM dbo.Claims d
                  WHERE d.Status = 'Approved'),
        Valid    = (SELECT COUNT(*) FROM dbo.Claims d
                    WHERE d.Status = 'Valid'),
        Reject   = (SELECT COUNT(*) FROM dbo.Claims d
                    WHERE d.Status = 'Reject')
Run Code Online (Sandbox Code Playgroud)

2)SUM带 -FROM子句:

SELECT  Approved = SUM(CASE WHEN Status = 'Approved' THEN 1 ELSE 0 END),
        Valid    = SUM(CASE WHEN Status = 'Valid'    THEN 1 ELSE 0 END),
        Reject   = SUM(CASE WHEN Status = 'Reject'   THEN 1 ELSE 0 END)
FROM dbo.Claims c;
Run Code Online (Sandbox Code Playgroud)

我很惊讶差异如此之大。带有三个子查询的第一个查询立即返回结果,而第二种SUM方法需要 18 秒。

Claims是从包含约 1800 万行的表中选择的视图。在ClaimStatus包含状态名称的表的 FK 列上有一个索引。

为什么我使用COUNT或使用它会产生如此大的差异SUM

执行计划:

总共有12种状态。这三个状态属于所有行的 7%。


这是实际视图,我不确定它是否相关:

CREATE VIEW [dbo].[Claims]
AS
SELECT 
   mu.Marketunitname AS MarketUnit, 
   c.Countryname     AS Country, 
   gsp.Gspname       AS GSP, 
   gsp.Wcmskeynumber AS GspNumber, 
   sl.Slname         AS SL, 
   sl.Wcmskeynumber  AS SlNumber, 
   m.Modelname       AS Model, 
   m.Salesname       AS [Model-Salesname], 
   s.Claimstatusname AS [Status], 
   d.Work_order      AS [Work Order], 
   d.Ssn_number      AS IMEI, 
   d.Ssn_out, 
   Remarks, 
   d.Claimnumber     AS [Claim-Number], 
   d.Rma_number      AS [RMA-Number], 
   dbo.ToShortDateString(d.Received_Date, 1) AS [Received Date], 
   Iddata, 
   Fisl, 
   Fimodel, 
   Ficlaimstatus 
FROM Tabdata AS d 
   INNER JOIN Locsl AS sl 
           ON d.Fisl = sl.Idsl 
   INNER JOIN Locgsp AS gsp 
           ON sl.Figsp = gsp.Idgsp 
   INNER JOIN Loccountry AS c 
           ON gsp.Ficountry = c.Idcountry 
   INNER JOIN Locmarketunit AS mu 
           ON c.Fimarketunit = mu.Idmarketunit 
   INNER JOIN Modmodel AS m 
           ON d.Fimodel = m.Idmodel 
   INNER JOIN Dimclaimstatus AS s 
           ON d.Ficlaimstatus = s.Idclaimstatus 
   INNER JOIN Tdefproducttype 
           ON d.Fiproducttype = Tdefproducttype.Idproducttype 
   LEFT OUTER JOIN Tdefservicelevel 
                ON d.Fimaxservicelevel = Tdefservicelevel.Idservicelevel 
   LEFT OUTER JOIN Tdefactioncode AS ac 
                ON d.Fimaxactioncode = ac.Idactioncode 
Run Code Online (Sandbox Code Playgroud)

Han*_*non 19

COUNT(*)版本能够简单地为您选择的每个状态查找一次您在状态列上的索引,而SUM(...)版本需要查找索引十二次(唯一状态类型的总数)。

显然,搜索索引 3 次比搜索 12 次要快。

第一个计划需要 238MB 的内存授权,而第二个计划需要 650MB 的内存授权。这可能是更大的内存授权不能立即填补,使得查询慢得多。

将第二个查询更改为:

SELECT  Approved = SUM(CASE WHEN Status = 'Approved' THEN 1 ELSE 0 END),
        Valid    = SUM(CASE WHEN Status = 'Valid'    THEN 1 ELSE 0 END),
        Reject   = SUM(CASE WHEN Status = 'Reject'   THEN 1 ELSE 0 END)
FROM dbo.Claims c
WHERE c.Status = 'Approved'
    OR c.Status = 'Valid'
    OR c.Status = 'Reject';
Run Code Online (Sandbox Code Playgroud)

这将允许查询优化器消除 75% 的索引查找,并且会导致所需的内存授予、I/O 要求更低,以及更快的结果时间。

SUM(CASE WHEN ...)构造本质上可以防止查询优化器将Status谓词下推到计划的索引查找部分。