¿Por qué varios COUNT son más rápidos que un SUM con CASE?

14

Quería saber cuál de los siguientes dos enfoques es más rápido:

1) tres 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) SUMcon FROM-cláusula:

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;

Me sorprendió que la diferencia sea tan grande. La primera consulta con tres subconsultas devuelve el resultado inmediatamente, mientras que el segundo SUMenfoque necesita 18 segundos.

Claimses una vista que selecciona de una tabla que contiene ~ 18 millones de filas. Hay un índice en la columna FK de la ClaimStatustabla que contiene el nombre de estado.

¿Por qué hace una gran diferencia si uso COUNTo SUM?

Planes de ejecución:

Hay 12 estados en total. Esos tres estados pertenecen al 7% de todas las filas.


Esta es la vista real, no estoy seguro si es relevante:

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
fuente
Parece que ambos enlaces apuntan a la COUNTversión del plan. ¿Puedes editar el me gusta de la SUMversión para señalar el plan correcto?
Geoff Patterson el
¿Cuál es la proporción de filas con esos tres estadios en comparación con las filas con otros estadios?
Max Vernon el
1
@MaxVernon: sí, por supuesto, he visto demasiados ceros, tienes razón. Déjame borrar mis comentarios. Sí, hay 16,7 millones de filas en otro estado. La mayoría lo son Authorized.
Tim Schmelter
2
Yo estimaría que el segundo plan sufre por tener que escanear la tabla completa 12 veces (eso es lo que se muestra). Es probable que esto provenga de no poder empujar los predicados hacia el escaneo. ¿Cómo es el rendimiento si agrega WHERE c.Status = 'Approved' or c.Status = 'Valid' or c.status = 'Reject'a la SUMvariante?
Max Vernon el
@MaxVernon: hay doce estados en total. Realmente no es un problema para mí, pero me sorprendió mucho que el optimizador no pueda manejar esto. Realmente debería trabajar en mis habilidades de análisis del plan de ejecución. Haz que sea una respuesta. ¿Cuál es su suposición, por qué SQL-Server no puede escanear solo tres estados?
Tim Schmelter, el

Respuestas:

19

La COUNT(*)versión puede buscar simplemente el índice que tiene en la columna de estado una vez para cada estado que está seleccionando, mientras que la SUM(...)versión necesita buscar el índice doce veces (el número total de tipos de estado únicos).

Claramente, buscar un índice tres veces será más rápido que buscarlo 12 veces.

El primer plan requiere una concesión de memoria de 238 MB, mientras que el segundo plan requiere una concesión de memoria de 650 MB. Es posible que la concesión de memoria más grande no se pueda completar de inmediato, lo que hace que la consulta sea mucho más lenta.

Modifique la segunda consulta para que sea:

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

Esto permitirá que el optimizador de consultas elimine el 75% de las búsquedas de índice, y debería dar como resultado una concesión de memoria requerida menor, menores requisitos de E / S y un tiempo de resultado más rápido.

La SUM(CASE WHEN ...)construcción esencialmente evita que el optimizador de consultas empuje los Statuspredicados hacia la parte de búsqueda de índice del plan.

Max Vernon
fuente
Buena captura con el recuerdo. Me di cuenta de que todos mis 32 GB están actualmente en uso (solo 300 MB gratis). Editar Sin embargo, he liberado algo de memoria. El resultado es el mismo
Tim Schmelter, el
Es posible que desee ver la max server memoryopción: debe configurarse con el valor correcto para su sistema. Es posible que desee ver esta pregunta y las respuestas para obtener detalles sobre cómo hacerlo.
Max Vernon el
1
Desafortunadamente, este servidor no solo se usa para la base de datos, sino también para un cubo SSAS y algunas herramientas (incluida la aplicación web de intranet). Pero ya he asignado 12 GB como máximo.
Tim Schmelter