Mengapa beberapa COUNT lebih cepat dari satu SUM dengan CASE?

14

Saya ingin tahu yang mana dari dua pendekatan berikut ini yang lebih cepat:

1) Tiga 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) SUMdengan FROM-clause:

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;

Saya terkejut bahwa perbedaannya sangat besar. Kueri pertama dengan tiga subkueri mengembalikan hasilnya segera sedangkan SUMpendekatan kedua membutuhkan 18 detik.

Claimsadalah tampilan yang dipilih dari tabel yang berisi ~ 18 juta baris. Ada indeks pada Kolom FK ke ClaimStatustabel yang berisi status-nama.

Mengapa itu membuat perbedaan besar apakah saya menggunakan COUNTatau SUM?

Rencana eksekusi:

Ada 12 status secara total. Ketiga status tersebut termasuk dalam 7% dari semua baris.


Ini adalah tampilan yang sebenarnya, saya tidak yakin apakah itu relevan:

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 
Tim Schmelter
sumber
Sepertinya kedua tautan mengarah ke COUNTversi paket. Bisakah Anda mengedit suka ke SUMversi untuk menunjukkan rencana yang benar?
Geoff Patterson
Apa rasio baris dengan ketiga statii dibandingkan dengan baris dengan statii lainnya?
Max Vernon
1
@ MaxVernon: ya, tentu saja, saya sudah melihat terlalu banyak nol, Anda benar. Biarkan saya menghapus komentar saya. Ya, ada 16,7 juta baris di status lain. Sebagian besar Authorized.
Tim Schmelter
2
Saya memperkirakan rencana kedua menderita karena harus memindai seluruh tabel 12 kali (itulah yang ditunjukkan). Ini kemungkinan berasal dari tidak mampu menekan predikat ke dalam pemindaian. Apa kinerja seperti jika Anda menambahkan WHERE c.Status = 'Approved' or c.Status = 'Valid' or c.status = 'Reject'ke SUMvarian.
Max Vernon
@MaxVernon: total ada dua belas status. Ini bukan masalah bagi saya, tetapi saya sangat yakin bahwa pengoptimal tidak dapat menangani ini. Saya harus benar-benar bekerja pada keterampilan menganalisis rencana-eksekusi saya. Buat jawaban. Apa asumsi Anda, mengapa SQL-Server tidak dapat memindai hanya tiga status?
Tim Schmelter 3-15

Jawaban:

19

Itu COUNT(*) Versi mampu hanya mencari ke dalam indeks yang Anda miliki di kolom status sekali untuk setiap status yang Anda memilih, sedangkan SUM(...)kebutuhan versi untuk mencari indeks dua belas kali (total jumlah jenis status yang unik).

Jelas mencari indeks tiga kali akan lebih cepat daripada mencari itu 12 kali.

Paket pertama membutuhkan hibah memori sebesar 238MB, sedangkan paket kedua membutuhkan hibah memori sebesar 650MB. Ini mungkin bahwa semakin besar hibah memori tidak dapat segera diisi, membuat query yang jauh lebih lambat.

Ubah kueri kedua menjadi:

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

Ini akan memungkinkan pengoptimal kueri untuk menghilangkan 75% dari indeks yang dicari, dan harus menghasilkan hibah memori yang lebih rendah, persyaratan I / O yang lebih rendah, dan waktu-untuk-hasil yang lebih cepat.

The SUM(CASE WHEN ...)konstruk dasarnya mencegah query optimizer dari mendorong Statuspredikat ke dalam indeks mencari bagian dari rencana.

Max Vernon
sumber
Tangkapan bagus dengan memori. Saya perhatikan bahwa semua 32GB saya sedang digunakan (hanya 300 MB gratis). Sunting Namun, saya sudah membebaskan sebagian memori. Hasilnya sama
Tim Schmelter 3-15
Anda mungkin ingin melihat max server memoryopsi - opsi ini harus dikonfigurasikan ke nilai yang benar untuk sistem Anda. Anda mungkin ingin melihat pertanyaan ini dan jawaban untuk perincian tentang bagaimana melakukannya.
Max Vernon
1
Sayangnya server ini tidak hanya digunakan untuk database tetapi juga untuk kubus SSAS dan beberapa alat (termasuk aplikasi web intranet). Tapi saya sudah menetapkan maksimum 12GB.
Tim Schmelter 3-15