2009-02-03 14 views
11

De this question, a neat answer about using COALESCE para simplificar los árboles de lógica complejos. Consideré el problema del cortocircuito.COALESCE: ¿está garantizado un cortocircuito?

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

int f(float x, float y) { 
    return x; 
} 

f(a, a/b) ; // This will result in an error if b == 0 

que 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 se evalúa el segundo caso, cuando Denominador = 0, yo esperaría para ver un error como:

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

he encontrado algunos mentionsrelated a 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?

+1

[altamente relacionada] (http://stackoverflow.com/q/7473045/73226) –

+0

Para resumir la respuesta DBA, SELECT COALESCE (1, (SELECT 1/0)) 'se ejecuta sin errores y muestra que es cortocircuito. El intérprete lo ve como una declaración abreviada de 'CASE'. –

Respuesta

8

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 la ANSI standard. 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 declaración when.

Aquí es una prueba de que es obras para funciones escalares (basada me encontré 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í es una prueba de que la implementación subyacente con CASE ejecutar consultas sub.

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. 
+0

Sí, parece que COALESCE es totalmente equivalente a CASE, y cortocircuita de la misma manera, sin embargo, como se muestra, el comportamiento de CASE no siempre es cortocircuito, lo cual es realmente bastante desagradable. –

+0

COALESCE produce cortocircuitos correctamente (incluso con subconsultas) en 11g –

+0

No ** realiza ** 2 escaneos de tabla aunque el plan muestra 2 escaneos. Esto es fácil de verificar con 'SET STATISTICS IO ON' o simplemente mira el "número de ejecuciones" en las propiedades del plan de ejecución. ** Hay ** [un problema] (http://connect.microsoft.com/SQLServer/feedback/details/336002/unnecessarily-bad-performance-for-coalesce-subquery) con 'COALESCE' que no ocurre con 'ISNULL' sin embargo. –

1

¡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 funciona!)

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

También se adelantó e hizo 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)?

+0

Eso debería caer en el primer caso. Más de una década de SQL Server, y nunca pensé que COALESCE sería un cortocircuito, porque PARECE una llamada de función. Obviamente CASE lo hace, y parece que COALESCE se define para funcionar de manera idéntica a CASE. –

+0

mi mal ... por supuesto que cae en el primer caso. Ahora es la misión de mi vida encontrar un caso donde esto no funcione :) – Learning

+0

@Learning, asegúrate de echarle un vistazo a mi respuesta expandida, corrige algunas cosas. –

3

El eficiente manera de garantizar un cortocircuito en MS SQL Server es utilizar CASO. Para la cláusula WHEN de éxito, no se evalúan otros.

COALESCE can have issues

En este caso, ¿por qué tienen tantas ramas en las construcciones COALESCE/CASE?

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

Ver mi respuesta, hay un problema subyacente con CASE que fluye hasta ISNULL, etc. ... –

+0

Sí, CASE puede hacer subconsultas pero I0m no está seguro de la relevancia de la pregunta del OP. Lo he visto como un cortocircuito, pero no me gusta personalmente debido a los escaneos de tabla o al aumento de IO (como lo demostró) – gbn

Cuestiones relacionadas