Database
 sql >> Base de Dados >  >> RDS >> Database

SQL CASE:Conheça e evite 3 aborrecimentos menos conhecidos


CASO SQL? Pedaco de bolo!

Sério?

Não até você se deparar com 3 problemas problemáticos que podem causar erros de tempo de execução e desempenho lento.

Se você está tentando escanear os subtítulos para ver quais são os problemas, não posso culpá-lo. Os leitores, inclusive eu, estão impacientes.

Acredito que você já conheça o básico do SQL CASE, portanto, não vou aborrecê-lo com longas introduções. Vamos nos aprofundar em uma compreensão mais profunda do que está acontecendo sob o capô.

1. SQL CASE nem sempre avalia sequencialmente


As expressões na instrução Microsoft SQL CASE são avaliadas principalmente sequencialmente ou da esquerda para a direita. É uma história diferente, porém, ao usá-lo com funções agregadas. Vamos a um exemplo:
-- aggregate function evaluated first and generated an error
DECLARE @value INT = 0;
SELECT CASE WHEN @value = 0 THEN 1 ELSE MAX(1/@value) END;

O código acima parece normal. Se eu perguntar qual é o resultado dessas declarações, você provavelmente dirá 1. A inspeção visual nos diz isso porque @value é definido como 0. Quando o @value é 0, o resultado é 1.

Mas esse não é o caso aqui. Dê uma olhada no resultado real do SQL Server Management Studio:
Msg 8134, Level 16, State 1, Line 4
Divide by zero error encountered.

Mas por que?

Quando expressões condicionais usam funções agregadas como MAX() no SQL CASE, elas são avaliadas primeiro. Assim, MAX(1/@value) causará o erro de divisão por zero porque @value é zero.

Esta situação é mais problemática quando escondida. Eu explico depois.

2. Expressão SQL CASE simples é avaliada várias vezes


E daí?

Boa pergunta. A verdade é que não há nenhum problema se você usar literais ou expressões simples. Mas se você usar subconsultas como uma expressão condicional, terá uma grande surpresa.

Antes de tentar o exemplo abaixo, você pode querer restaurar uma cópia do banco de dados daqui. Vamos usá-lo para o resto dos exemplos.

Agora, considere esta consulta muito simples:

SELECT TOP 1 manufacturerID FROM SportsCars


É muito simples, certo? Retorna 1 linha com 1 coluna de dados. O STATISTICS IO revela leituras lógicas mínimas.

Nota rápida :Para os não iniciados, ter leituras lógicas mais altas torna a consulta lenta. Leia isto para mais detalhes.

O Plano de Execução também revela um processo simples:

Agora, vamos colocar essa consulta em uma expressão CASE como uma 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álise


Cruze os dedos porque isso vai acabar com as leituras lógicas 4 vezes.

Surpresa! Comparado com a Figura 1 com apenas 2 leituras lógicas, isso é 4 vezes maior. Assim, a consulta é 4 vezes mais lenta. Como isso pôde acontecer? Só vimos a subconsulta uma vez.

Mas esse não é o fim da história. Confira o Plano de Execução:

Vemos 4 instâncias dos operadores Top e Index Scan na Figura 4. Se cada Top e Index Scan consomem 2 leituras lógicas, isso explica porque as leituras lógicas se tornaram 8 na Figura 3. E como cada Top e Index Scan tem um custo de 25% , também nos diz que eles são os mesmos.

Mas não termina aí. As propriedades do operador Compute Scalar revelam como a instrução inteira é tratada.

Vemos 4 expressões CASE WHEN provenientes do operador Compute Scalar Defined Values. Parece que nossa expressão CASE simples se tornou uma expressão CASE pesquisada 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;

Vamos recapitular. Havia 2 leituras lógicas para cada operador Top e Index Scan. Isso multiplicado por 4 faz 8 leituras lógicas. Também vimos 4 expressões CASE WHEN no operador Compute Scalar.

No final, a subconsulta na expressão CASE simples foi avaliada 4 vezes. Isso atrasará sua consulta.

Como evitar várias avaliações de uma subconsulta em uma expressão CASE simples


Para evitar problemas de desempenho como várias instruções CASE em SQL, precisamos reescrever a consulta.

Primeiro, coloque o resultado da subconsulta em uma variável. Em seguida, use essa variável na condição da expressão CASE simples do SQL Server, assim:
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;

Esta é uma boa correção? Vamos ver as leituras lógicas em STATISTICS IO:

Vemos leituras lógicas mais baixas da consulta modificada. Retirar a subconsulta e atribuir o resultado a uma variável é muito melhor. E o Plano de Execução? Veja abaixo.

O operador Top and Index Scan apareceu apenas uma vez, não 4 vezes. Maravilhoso!

Para viagem :Não use uma subconsulta como condição na expressão CASE. Se você precisar recuperar um valor, coloque primeiro o resultado da subconsulta em uma variável. Em seguida, use essa variável na expressão CASE.

3. Essas 3 funções incorporadas se transformam secretamente em SQL CASE


Há um segredo e a instrução CASE do SQL Server tem algo a ver com isso. Se você não sabe como essas 3 funções se comportam, você não saberá que está cometendo um erro que tentamos evitar nos pontos 1 e 2 anteriores. Aqui estão eles:
  • SE
  • COALESCE
  • ESCOLHER

Vamos examiná-los um por um.

IIF


Eu usei o Immediate IF, ou IIF, em Visual Basic e Visual Basic for Applications. Isso também é equivalente ao operador ternário do C#: ? :.

Esta função dada uma condição retornará 1 dos 2 argumentos com base no resultado da condição. E esta função também está disponível em T-SQL. A instrução CASE na cláusula WHERE pode ser usada na instrução SELECT

Mas é apenas uma cobertura de uma expressão CASE mais longa. Como nós sabemos? Vamos examinar um exemplo.
SELECT IIF((SELECT Model FROM SportsCars WHERE SportsCarID = 1276) = 'McLaren Senna', 'Yes', 'No');

O resultado desta consulta é 'Não'. No entanto, confira o Plano de Execução junto com as propriedades do Compute Scalar.

Como IIF é CASE WHEN, o que você acha que acontecerá se executar algo assim?
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));

Isso resultará em um erro de divisão por zero se @noOfPayments for 0. O mesmo aconteceu no ponto #1 anterior.

Você pode se perguntar o que causa esse erro porque a consulta acima resultará em TRUE e deve retornar 83333.33. Verifique o ponto 1 novamente.

Assim, se você estiver com um erro como esse ao usar o IIF, o SQL CASE é o culpado.

COALESCE


COALESCE também é um atalho de uma expressão SQL CASE. Ele avalia a lista de valores e retorna o primeiro valor não nulo. No artigo anterior sobre COALESCE, apresentei um exemplo que avalia uma subconsulta duas vezes. Mas usei outro método para revelar o SQL CASE no Plano de Execução. Aqui está outro exemplo que usará as mesmas técnicas.
SELECT 
COALESCE(m.Manufacturer + ' ','') + sc.Model AS Car 
FROM SportsCars sc
LEFT JOIN Manufacturers m ON sc.ManufacturerID = m.ManufacturerID

Vamos ver o Plano de Execução e os Valores Definidos Escalares de Computação.

SQL CASE está tudo bem. A palavra-chave COALESCE não está em nenhum lugar na janela Valores Definidos. Isso prova o segredo por trás dessa função.

Mas isso não é tudo. Quantas vezes você viu [Veículos].[dbo].[Estilos].[Estilo] na janela Valores definidos? DUAS VEZES! Isso é consistente com a documentação oficial da Microsoft. Imagine se um dos argumentos em COALESCE for uma subconsulta. Então, dobre as leituras lógicas e obtenha a execução mais lenta também.

ESCOLHA


Por fim, ESCOLHA. Isso é semelhante à função ESCOLHER do MS Access. Ele retorna 1 valor de uma lista de valores com base em uma posição de índice. Ele também atua como um índice em uma matriz.

Vamos ver se podemos cavar a transformação em um SQL CASE com um exemplo. Confira o código abaixo:
;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

Há o nosso exemplo CHOOSE. Agora, vamos verificar o Plano de Execução e os Valores Definidos Escalares de Computação:

Você vê a palavra-chave CHOOSE na janela Valores definidos na Figura 10? Que tal CASO QUANDO?

Como os exemplos anteriores, esta função CHOOSE é apenas uma cobertura para uma expressão CASE mais longa. E como a consulta tem 2 itens para CHOOSE, as palavras-chave CASE WHEN apareceram duas vezes. Veja a janela Valores Definidos dentro de uma caixa vermelha.

No entanto, temos vários CASE WHEN em SQL aqui. Isso é por causa da expressão CASE na consulta interna do CTE. Se você olhar com atenção, essa parte da consulta interna também aparece duas vezes.

Recomendações


Agora que os segredos foram revelados, o que aprendemos?
  1. SQL CASE se comporta de maneira diferente quando funções agregadas são usadas. Tenha cuidado ao passar argumentos para funções agregadas como MIN, MAX ou COUNT.
  2. Uma expressão CASE simples será avaliada várias vezes. Observe isso e evite passar uma subconsulta. Embora sintaticamente correto, terá um desempenho ruim.
  3. IIF, CHOOSE e COALESCE têm segredos sujos. Lembre-se disso antes de passar valores para essas funções. Ele se transformará em um SQL CASE. Dependendo dos valores, você causa um erro ou uma penalidade de desempenho.

Espero que esta visão diferente do SQL CASE tenha sido útil para você. Se sim, seus amigos desenvolvedores também podem gostar. Por favor, compartilhe em suas plataformas de mídia social favoritas. E deixe-nos saber o que você pensa sobre isso na seção de comentários.