Campo calculado de SQL en la cláusula SELECT y GROUP BY

11

A menudo, al consultar mis bases de datos de MS SQL Server, necesito crear un campo calculado, como este

(CASE WHEN A.type = 'Workover' THEN 'Workover' 
      ELSE (CASE WHEN substring(C.category, 2, 1) = 'D' THEN 'Drilling' 
                 WHEN substring(C.category, 2, 1) = 'C' THEN 'Completion' 
                 WHEN substring(C.category, 2, 1) = 'W' THEN 'Workover' 
                 ELSE 'Other' 
            END)
END)

y luego necesito agrupar mis resultados por este campo calculado (entre otros). Por lo tanto, tengo el mismo cálculo en las cláusulas SELECT y GROUP BY. ¿Realmente el servidor SQL realiza estos cálculos dos veces o es lo suficientemente inteligente como para hacerlo solo una vez?

Dr. Drew
fuente

Respuestas:

13

Tengo el mismo cálculo en las cláusulas SELECT y GROUP BY. ¿Realmente el servidor SQL realiza estos cálculos dos veces o es lo suficientemente inteligente como para hacerlo solo una vez?

La respuesta simple es que SQL Server no ofrece garantías generales sobre cuándo y cuántas veces se evaluará una expresión escalar en el momento de la ejecución.

Hay todo tipo de comportamientos complicados (e indocumentados) dentro del motor de optimización y ejecución con respecto a la colocación, ejecución y almacenamiento en caché de expresiones escalares. Books Online no tiene mucho que decir sobre esto, pero lo que sí dice es esto:

Descripción de Compute Scalar

Esto describe uno de los comportamientos a los que aludí antes, la ejecución diferida de expresiones. Escribí sobre algunos de los otros comportamientos actuales (que podrían cambiar en cualquier momento) en esta publicación de blog .

Otra consideración es que el modelo de costo utilizado por el optimizador de consultas actualmente no hace mucho en cuanto a la estimación de costos para expresiones escalares. Sin un marco sólido de costos, los resultados actuales se basan en heurísticas amplias o pura casualidad.

Para expresiones muy simples, probablemente no haga mucha diferencia si la expresión se evalúa una o varias veces en la mayoría de los casos. Dicho esto, he encontrado grandes consultas en las que el rendimiento se ha visto afectado negativamente cuando la expresión se evalúa de forma redundante una gran cantidad de veces, o la evaluación se produce en un solo hilo donde hubiera sido ventajoso evaluar en una rama paralela de la ejecución plan.

En resumen, el comportamiento actual no está definido, y no hay mucho en los planes de ejecución para ayudarlo a descubrir qué sucedió (y no siempre será conveniente adjuntar un depurador para examinar los comportamientos detallados del motor, como en la publicación del blog).

Si encuentra casos en los que los problemas de evaluación escalar son importantes para el rendimiento, plantee el problema con el Soporte técnico de Microsoft. Esta es la mejor manera de proporcionar comentarios para mejorar futuras versiones del producto.

Paul White 9
fuente
3

Como dice el comentario sobre su pregunta, la respuesta es (en mi experiencia, al menos) "sí". SQL Server es generalmente lo suficientemente inteligente como para evitar el recálculo. Probablemente podría verificar esto mostrando el plan de ejecución desde SQL Server Management Studio. Cada campo calculado se designa Exprxxxxx(donde xxxxx es un número). Si sabe qué buscar, debería poder verificar que usa la misma expresión.

Para agregar a la discusión, su otra opción estética es una expresión de tabla común :

with [cte] as
(
    select
        (case when a.type = 'workover' then 'workover' else 
        (case when substring(c.category, 2, 1) = 'd' then 'drilling'
              when substring(c.category, 2, 1) = 'c' then 'completion'
              when substring(c.category, 2, 1) = 'w' then 'workover'
              else 'other' end)
         end)) as [group_key],
         *
    from
        [some_table]
)
select
    [group_key],
    count(*) as [count]
from
    [cte]
group by
    [group_key]

Respuesta corta, son funcionalmente idénticas a una vista, pero solo son válidas para su uso en la siguiente declaración. Los veo como una alternativa más legible a las tablas derivadas porque evita el anidamiento.

Aunque no son relevantes para esta pregunta, pueden hacer referencia a sí mismos y de esa manera ser utilizados para construir consultas recursivas.

Quick Joe Smith
fuente
@Quick Joe Smith: Creo que tienes razón sobre el Exprxxxxx, ya que también lo he visto. Sin embargo, si le doy un nombre a la expresión manualmente (caso ... fin) como OpType, luego uso el campo OpType en la cláusula GROUP BY, obtengo un error de que es un nombre de columna no válido.
Dr. Drew
Desafortunadamente, a menudo su única forma de especificar la expresión dos veces es usar uno de los métodos anteriores: un CTE, una vista o una consulta anidada.
Quick Joe Smith
2
A menos que también sepa sobre CROSS APPLY .
Andriy M
El uso cross applyen este caso es un poco exagerado, y muy probablemente dañaría el rendimiento al introducir una autounión innecesaria.
Quick Joe Smith
2
No creo que hayas "recibido" la sugerencia. El CROSS APPLYsimplemente define el alias de columnas en la misma fila. No es necesario unirse. por ejemploSELECT COUNT(*), hilo FROM master..spt_values CROSS APPLY (VALUES(high + low)) V(hilo) GROUP BY hilo
Martin Smith
1

El rendimiento es solo un aspecto. El otro es la mantenibilidad.

Personalmente, tiendo a hacer lo siguiente:

SELECT T.GroupingKey, SUM(T.value)
FROM
(
    SELECT 
        A.*
        (CASE WHEN A.type = 'Workover' THEN 'Workover' ELSE 
        (CASE WHEN substring(C.category, 2, 1) = 'D' THEN 'Drilling' WHEN substring(C.category, 2, 1) = 'C' THEN 'Completion' WHEN substring(C.category, 2, 1) = 'W' THEN 'Workover' ELSE 'Other' END)
        END) AS GroupingKey
    FROM Table AS A
) AS T

GROUP BY T.GroupingKey

ACTUALIZAR:

Si no le gusta anidar, puede crear VIEW para cada tabla donde necesite usar expresiones complejas.

CREATE VIEW TableExtended
AS 
SELECT 
    A.*
    (CASE WHEN A.type = 'Workover' THEN 'Workover' ELSE 
    (CASE WHEN substring(C.category, 2, 1) = 'D' THEN 'Drilling' WHEN substring(C.category, 2, 1) = 'C' THEN 'Completion' WHEN substring(C.category, 2, 1) = 'W' THEN 'Workover' ELSE 'Other' END)
    END) AS GroupingKey
FROM Table AS A

Entonces podría seleccionar sin hacer un anidamiento adicional;

SELECT GroupingKey, SUM(value)
FROM TableExtended
GROUP BY GroupingKey
Kaspars Ozols
fuente