when sqlserver multiple example concatenate sql sql-server tsql coalesce short-circuiting

sqlserver - COALESCE: ¿está garantizado un cortocircuito?



sql server case when multiple (3)

La forma eficiente de garantizar el cortocircuito en MS SQL Server es usar CASE. Para la cláusula WHEN de éxito, no se evalúan otros.

COALESCE puede tener problemas

En este caso, ¿por qué tener tantas ramas en los constructivos COALESCE / CASE?

SELECT Numerator ,Denominator ,CASE WHEN Denominator = 0 THEN 0 END, ELSE Numerator / Denominator END AS TestCalc FROM Fractions

A partir de esta pregunta , una respuesta clara sobre el uso de COALESCE para simplificar los árboles de lógica complejos. Consideré el problema del cortocircuito.

Por ejemplo, en funciones en la mayoría de los lenguajes, los argumentos se evalúan completamente y luego se pasan a la función. Cª:

int f(float x, float y) { return x; } f(a, a / b) ; // This will result in an error if b == 0

Eso no parece ser una limitación de la "función" COALESCE en SQL Server:

CREATE TABLE Fractions ( Numerator float ,Denominator float ) INSERT INTO Fractions VALUES (1, 1) INSERT INTO Fractions VALUES (1, 2) INSERT INTO Fractions VALUES (1, 3) INSERT INTO Fractions VALUES (1, 0) INSERT INTO Fractions VALUES (2, 0) INSERT INTO Fractions VALUES (3, 0) SELECT Numerator ,Denominator ,COALESCE( CASE WHEN Denominator = 0 THEN 0 ELSE NULL END, CASE WHEN Numerator <> 0 THEN Numerator / Denominator ELSE NULL END, 0 ) AS TestCalc FROM Fractions DROP TABLE Fractions

Si estuviera evaluando el segundo caso cuando Denominator = 0, esperaría ver un error como:

Msg 8134, Level 16, State 1, Line 1 Divide by zero error encountered.

Encontré algunas menciones relacionadas con Oracle. Y algunas pruebas con SQL Server . Parece que el cortocircuito podría romperse cuando incluye funciones definidas por el usuario.

Entonces, ¿se supone que este comportamiento está garantizado por el estándar ANSI?


¡También me sorprendió ver que la respuesta funciona! No estoy seguro de que este comportamiento esté garantizado. (¡Pero no he podido encontrar un ejemplo que no funcione!)

Cinco años de SQL , y todavía estoy sorprendido.

También seguí adelante e hice un cambio más:

INSERT INTO #Fractions VALUES (0, 0) SELECT Numerator ,Denominator ,coalesce ( CASE WHEN Denominator = 0 THEN 0 ELSE NULL END, CASE WHEN Numerator <> 0 THEN Numerator / Denominator ELSE NULL END) AS TestCalc FROM #Fractions

El resultado que obtuve fue:

Numerator Denominator TestCalc 1 1 1 1 2 0.5 1 3 0.3333333333333335 1 0 0 2 0 0 3 0 0 0 0 0

¡Ahora estoy aún más confundido! Para el caso en que num = 0 y den = 0, ¿cómo obtuve testcalc como 0 (especialmente porque eliminé el 0 después del último caso)?


Acabo de echar un vistazo al artículo vinculado y puedo confirmar que el cortocircuito puede fallar tanto para COALESCE como para ISNULL.

Parece que falla si tiene alguna sub consulta involucrada, pero funciona bien para funciones escalares y valores codificados.

Por ejemplo,

DECLARE @test INT SET @test = 1 PRINT ''test2'' SET @test = COALESCE(@test, (SELECT COUNT(*) FROM sysobjects)) SELECT ''test2'', @test -- OUCH, a scan through sysobjects

COALESCE se implementa de acuerdo con el estándar ANSI . Es simplemente una abreviatura de una declaración CASE. ISNULL no es parte del estándar ANSI. La Sección 6.9 no parece requerir un cortocircuito explícitamente, pero sí implica que se debe devolver la primera cláusula verdadera en la instrucción when .

Aquí hay algunas pruebas de que funciona para funciones basadas en escalares (lo ejecuté en SQL Server 2005 ):

CREATE FUNCTION dbo.evil ( ) RETURNS int AS BEGIN -- Create an huge delay declare @c int select @c = count(*) from sysobjects a join sysobjects b on 1=1 join sysobjects c on 1=1 join sysobjects d on 1=1 join sysobjects e on 1=1 join sysobjects f on 1=1 return @c / 0 END go select dbo.evil() -- takes forever select ISNULL(1, dbo.evil()) -- very fast select COALESCE(1, dbo.evil()) -- very fast

Aquí hay alguna prueba de que la implementación subyacente con CASE ejecutará sub consultas.

DECLARE @test INT SET @test = 1 select case when @test is not null then @test when @test = 2 then (SELECT COUNT(*) FROM sysobjects) when 1=0 then (SELECT COUNT(*) FROM sysobjects) else (SELECT COUNT(*) FROM sysobjects) end -- OUCH, two table scans. If 1=0, it does not result in a table scan.