web-dev-qa-db-ja.com

CASEを使用した1つのSUMよりも複数のCOUNTの方が速いのはなぜですか?

次の2つの方法のどちらが速いか知りたいと思いました。

1)3つの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')

2)SUM- clauseを使用した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;

その差があまりにも大きいのには驚きました。 3つのサブクエリを持つ最初のクエリはすぐに結果を返しますが、2番目のSUMアプローチは18秒必要です。

Claimsは、約1,800万行を含むテーブルから選択するビューです。 FK列には、ステータス名を含むClaimStatusテーブルへのインデックスがあります。

COUNTを使用してもSUMを使用しても、なぜそんなに大きな違いがあるのですか?

実行計画:

合計12のステータスがあります。これら3つのステータスは、すべての行の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 
14
Tim Schmelter

COUNT(*)バージョンは、選択するステータスごとにステータス列にあるインデックスを単純にシークできますが、SUM(...)バージョンは12回インデックスをシークする必要があります(一意のステータスタイプの総数)。

明らかに、インデックスを3回検索する方が、12回検索するよりも高速になります。

最初のプランには238MBのメモリ許可が必要ですが、2番目のプランには650MBのメモリ許可が必要です。それはmayより大きなメモリ許可をすぐに満たすことができず、クエリが非常に遅くなるということです。

2番目のクエリを次のように変更します。

_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';
_

これにより、クエリオプティマイザーはインデックスシークの75%を排除できるため、必要なメモリ許可が少なくなり、I/O要件が低くなり、結果が得られるまでの時間が短縮されます。

SUM(CASE WHEN ...)構造は、クエリオプティマイザがStatus述語をプランのインデックスシーク部分に押し込むのを本質的に防ぎます。

19
Max Vernon