¿CASO SQL? ¡Pan comido!
¿En serio?
No hasta que te encuentres con 3 problemas problemáticos que pueden causar errores de tiempo de ejecución y un rendimiento lento.
Si está tratando de escanear los subtítulos para ver cuáles son los problemas, no puedo culparlo. Los lectores, incluyéndome a mí, están impacientes.
Confío en que ya conoce los conceptos básicos de SQL CASE, por lo que no lo aburriré con largas introducciones. Profundicemos en una comprensión más profunda de lo que sucede debajo del capó.
1. SQL CASE no siempre evalúa secuencialmente
Las expresiones en la instrucción CASE de Microsoft SQL se evalúan principalmente de forma secuencial o de izquierda a derecha. Sin embargo, es una historia diferente cuando se usa con funciones agregadas. Pongamos un ejemplo:
-- aggregate function evaluated first and generated an error
DECLARE @value INT = 0;
SELECT CASE WHEN @value = 0 THEN 1 ELSE MAX(1/@value) END;
El código anterior parece normal. Si le pregunto cuál es el resultado de esas declaraciones, probablemente dirá 1. La inspección visual nos dice que debido a que @value está establecido en 0. Cuando @value es 0, el resultado es 1.
Pero ese no es el caso aquí. Eche un vistazo al resultado real de SQL Server Management Studio:
Msg 8134, Level 16, State 1, Line 4
Divide by zero error encountered.
¿Pero por qué?
Cuando las expresiones condicionales usan funciones agregadas como MAX() en SQL CASE, se evalúa primero. Por lo tanto, MAX(1/@value) causará el error de división por cero porque @value es cero.
Esta situación es más problemática cuando está oculta. Lo explicaré más tarde.
2. La expresión SQL CASE simple se evalúa varias veces
¿Y qué?
Buena pregunta. La verdad es que no hay ningún problema si usas expresiones literales o simples. Pero si usa subconsultas como una expresión condicional, se llevará una gran sorpresa.
Antes de probar el ejemplo a continuación, es posible que desee restaurar una copia de la base de datos desde aquí. Lo usaremos para el resto de los ejemplos.
Ahora, considere esta consulta muy simple:
SELECT TOP 1 manufacturerID FROM SportsCars
Es muy simple, ¿verdad? Devuelve 1 fila con 1 columna de datos. STATISTICS IO revela lecturas lógicas mínimas.
Nota rápida :Para los no iniciados, tener lecturas lógicas más altas hace que la consulta sea lenta. Lea esto para obtener más detalles.
El Plan de Ejecución también revela un proceso simple:
Ahora, pongamos esa consulta en una expresión CASE como una subconsulta:
-- Using a subquery in a SQL CASE
DECLARE @manufacturer NVARCHAR(50)
SET @manufacturer = (CASE (SELECT TOP 1 manufacturerID FROM SportsCars)
WHEN 6 THEN 'Alfa Romeo'
WHEN 21 THEN 'Aston Martin'
WHEN 64 THEN 'Ferrari'
WHEN 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Análisis
Cruza los dedos porque esto va a acabar con las lecturas lógicas 4 veces.
¡Sorpresa! En comparación con la Figura 1 con solo 2 lecturas lógicas, esto es 4 veces mayor. Por lo tanto, la consulta es 4 veces más lenta. ¿Cómo pudo pasar eso? Solo vimos la subconsulta una vez.
Pero ese no es el final de la historia. Consulta el Plan de Ejecución:
Vemos 4 instancias de los operadores Top and Index Scan en la Figura 4. Si cada Top and Index Scan consume 2 lecturas lógicas, eso explica por qué las lecturas lógicas se convirtieron en 8 en la Figura 3. Y dado que cada Top and Index Scan tiene un costo del 25% , también nos dice que son lo mismo.
Pero no termina ahí. Las propiedades del operador Calcular escalar revelan cómo se trata la instrucción completa.
Vemos 4 expresiones CASE WHEN provenientes del operador Compute Scalar Defined Values. Parece que nuestra expresión CASE simple se convirtió en una expresión CASE buscada como esta:
DECLARE @manufacturer NVARCHAR(50)
SET @manufacturer = (CASE
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 6 THEN 'Alfa Romeo'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 21 THEN 'Aston Martin'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 64 THEN 'Ferrari'
WHEN (SELECT TOP 1 manufacturerID FROM SportsCars) = 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
Recapitulemos. Hubo 2 lecturas lógicas para cada operador de escaneo superior e índice. Esto multiplicado por 4 hace 8 lecturas lógicas. También vimos 4 expresiones CASE WHEN en el operador Compute Scalar.
Al final, la subconsulta en la expresión CASE simple se evaluó 4 veces. Esto retrasará su consulta.
Cómo evitar evaluaciones múltiples de una subconsulta en una expresión CASE simple
Para evitar un problema de rendimiento como la declaración CASE múltiple en SQL, necesitamos reescribir la consulta.
Primero, coloque el resultado de la subconsulta en una variable. Luego, use esa variable en la condición de la expresión CASE de SQL Server simple, así:
DECLARE @manufacturer NVARCHAR(50)
DECLARE @ManufacturerID INT -- create a new variable
-- store the result of the subquery in a variable
SET @ManufacturerID = (SELECT TOP 1 manufacturerID FROM SportsCars)
-- use the new variable in the simple CASE expression
SET @manufacturer = (CASE @ManufacturerID
WHEN 6 THEN 'Alfa Romeo'
WHEN 21 THEN 'Aston Martin'
WHEN 64 THEN 'Ferrari'
WHEN 108 THEN 'McLaren'
ELSE 'Others'
END)
SELECT @manufacturer;
¿Es esta una buena solución? Veamos las lecturas lógicas en ESTADÍSTICAS IO:
Vemos lecturas lógicas más bajas de la consulta modificada. Quitar la subconsulta y asignar el resultado a una variable es mucho mejor. ¿Qué tal el Plan de Ejecución? Véalo a continuación.
El operador Top and Index Scan apareció solo una vez, no 4 veces. ¡Maravilloso!
Para llevar :No utilice una subconsulta como condición en la expresión CASE. Si necesita recuperar un valor, primero coloque el resultado de la subconsulta en una variable. Luego, use esa variable en la expresión CASE.
3. Estas 3 funciones integradas se transforman en secreto en SQL CASE
Hay un secreto, y la declaración CASE de SQL Server tiene algo que ver con eso. Si no sabe cómo se comportan estas 3 funciones, no sabrá que está cometiendo un error que tratamos de evitar en los puntos 1 y 2 anteriores. Aquí están:
- FII
- COALESCER
- ELIGE
Examinémoslos uno por uno.
FII
Utilicé IF inmediato o IIF en Visual Basic y Visual Basic para aplicaciones. Esto también es equivalente al operador ternario de C#:
Esta función dada una condición devolverá 1 de los 2 argumentos basados en el resultado de la condición. Y esta función también está disponible en T-SQL. La declaración CASE en la cláusula WHERE se puede usar dentro de la declaración SELECT
Pero es solo una capa de azúcar de una expresión CASE más larga. ¿Como sabemos? Examinemos un ejemplo.
SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'McLaren Senna', 'Yes', 'No');
El resultado de esta consulta es "No". Sin embargo, consulte el Plan de ejecución junto con las propiedades de Compute Scalar.
Dado que IIF es CASO CUANDO, ¿qué crees que sucederá si ejecutas algo como esto?
DECLARE @averageCost MONEY = 1000000.00;
DECLARE @noOfPayments TINYINT = 0; -- intentional to force the error
SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'SF90 Spider', 83333.33,MIN(@averageCost / @noOfPayments));
Esto dará como resultado un error de división por cero si @noOfPayments es 0. Lo mismo sucedió en el punto n.º 1 anterior.
Puede preguntarse qué causa este error porque la consulta anterior dará como resultado VERDADERO y debería devolver 83333.33. Verifique el punto #1 nuevamente.
Por lo tanto, si se encuentra con un error como este al usar IIF, SQL CASE es el culpable.
FLUIR
COALESCE también es un atajo de una expresión SQL CASE. Evalúa la lista de valores y devuelve el primer valor no nulo. En el artículo anterior sobre COALESCE, presenté un ejemplo que evalúa una subconsulta dos veces. Pero utilicé otro método para revelar el CASO SQL en el Plan de Ejecución. Aquí hay otro ejemplo que usará las mismas técnicas.
SELECT
COALESCE(m.Manufacturer + ' ','') + sc.Model AS Car
FROM SportsCars sc
LEFT JOIN Manufacturers m ON sc.ManufacturerID = m.ManufacturerID
Veamos el Plan de Ejecución y el Cómputo de Valores Definidos por Escalar.
SQL CASE está bien. La palabra clave COALESCE no se encuentra en ninguna parte de la ventana Valores definidos. Esto prueba el secreto detrás de esta función.
Pero eso no es todo. ¿Cuántas veces viste [Vehículos].[dbo].[Estilos].[Estilo] en la ventana Valores definidos? ¡DOS VECES! Esto es consistente con la documentación oficial de Microsoft. Imagínese si uno de los argumentos en COALESCE es una subconsulta. Luego, duplique las lecturas lógicas y obtenga también la ejecución más lenta.
ELIGE
Finalmente, ELEGIR. Esto es similar a la función ELEGIR de MS Access. Devuelve 1 valor de una lista de valores basada en una posición de índice. También actúa como un índice en una matriz.
Veamos si podemos cavar la transformación en un CASO SQL con un ejemplo. Mira el código a continuación:
;WITH McLarenCars AS
(
SELECT
CASE
WHEN sc.Model IN ('Artura','Speedtail','P1/ P1 GTR','P1 LM') THEN '1'
ELSE '2'
END AS [type]
,sc.Model
,s.Style
FROM SportsCars sc
INNER JOIN Styles s ON sc.StyleID = s.StyleID
WHERE sc.ManufacturerID = 108
)
SELECT
Model
,Style
,CHOOSE([Type],'Hybrid','Gasoline') AS [type]
FROM McLarenCars
Ahí está nuestro ejemplo ELEGIR. Ahora, revisemos el plan de ejecución y los valores definidos escalar de cálculo:
¿Ve la palabra clave ELEGIR en la ventana Valores definidos en la Figura 10? ¿Qué tal CASO CUANDO?
Al igual que los ejemplos anteriores, esta función ELEGIR es solo una capa de azúcar para una expresión CASE más larga. Y dado que la consulta tiene 2 elementos para ELEGIR, las palabras clave CASO CUANDO aparecieron dos veces. Vea la ventana Valores definidos encerrada en un cuadro rojo.
Sin embargo, aquí tenemos múltiples CASE WHEN en SQL. Eso se debe a la expresión CASE en la consulta interna del CTE. Si observa detenidamente, esa parte de la consulta interna también aparece dos veces.
Puntos para llevar
Ahora que se han revelado los secretos, ¿qué hemos aprendido?
- SQL CASE se comporta de manera diferente cuando se usan funciones agregadas. Tenga cuidado al pasar argumentos a funciones agregadas como MIN, MAX o COUNT.
- Una expresión CASE simple se evaluará varias veces. Obsérvelo y evite pasar una subconsulta. Aunque es sintácticamente correcto, funcionará mal.
- IIF, CHOOSE y COALESCE tienen secretos sucios. Téngalo en cuenta antes de pasar valores a esas funciones. Se transformará en un CASO SQL. Según los valores, provoca un error o una penalización de rendimiento.
Espero que esta versión diferente de SQL CASE le haya sido útil. Si es así, a tus amigos desarrolladores también les puede gustar. Por favor, compártalo en sus plataformas de redes sociales favoritas. Y háganos saber lo que piensa al respecto en la sección de Comentarios.