¿Cómo reescribir ES DISTINTO DE y NO ES DISTINTO DE?


¿Cómo reescribe las expresiones que contienen los operadores estándar IS DISTINCT FROM y IS NOT DISTINCT FROM en implementaciones SQL como Microsoft SQL Server 2008R2 que no los soportan?

Author: Lukasz Szozda, 2012-05-02

8 answers

El predicado IS DISTINCT FROM se introdujo como característica T151 de SQL:1999, y su negación legible, IS NOT DISTINCT FROM, se agregó como característica T152 de SQL:2003. El propósito de estos predicados es garantizar que el resultado de comparar dos valores sea Verdadero o Falso, nunca Desconocido.

Estos predicados funcionan con cualquier tipo comparable (incluyendo filas, arrays y multisets), lo que hace que sea bastante complicado emularlos exactamente. Sin embargo, SQL Server no admite la mayoría de estos tipos, por lo que podemos llegar bastante lejos comprobando si hay argumentos/operandos nulos:

  • a IS DISTINCT FROM b se puede reescribir como:

    ((a <> b OR a IS NULL OR b IS NULL) AND NOT (a IS NULL AND b IS NULL))
    
  • a IS NOT DISTINCT FROM b se puede reescribir como:

    (NOT (a <> b OR a IS NULL OR b IS NULL) OR (a IS NULL AND b IS NULL))
    

Su propia respuesta es incorrecta, ya que no considera que FALSE OR NULL se evalúa como Desconocido . Por ejemplo, NULL IS DISTINCT FROM NULL debe evaluar a False. Del mismo modo, 1 IS NOT DISTINCT FROM NULL debe evaluar a Falso. En ambos casos, sus expresiones rendimiento Desconocido.

 36
Author: Chris Bandy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-08 14:29:33

Otra solución que me gusta aprovecha el verdadero resultado booleano de dos valores de EXISTS combinado con INTERSECT. Esta solución debería funcionar en SQL Server 2005+.

  • a IS NOT DISTINCT FROM b se puede escribir como:

    EXISTS(SELECT a INTERSECT SELECT b)

Como está documentado, INTERSECT trata dos valores NULL como iguales, por lo que si ambos son NULL, entonces INTERSECT resulta en una sola fila, por lo tanto EXISTS produce true.

  • a IS DISTINCT FROM b se puede escribir como:

    NOT EXISTS(SELECT a INTERSECT SELECT b)

Este enfoque es mucho más conciso si tiene varias columnas nullables que necesita comparar en dos tablas. Por ejemplo, para devolver filas en TableB que tienen valores diferentes para Col1, Col2 o Col3 que TableA, se puede usar lo siguiente:

SELECT *
FROM TableA A
   INNER JOIN TableB B ON A.PK = B.PK
WHERE NOT EXISTS(
   SELECT A.Col1, A.Col2, A.Col3
   INTERSECT
   SELECT B.Col1, B.Col2, B.Col3);

Paul White explica esta solución con más detalle: http://sqlblog.com/blogs/paul_white/archive/2011/06/22/undocumented-query-plans-equality-comparisons.aspx

 21
Author: John Keller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-22 22:17:37

Si su implementación SQL no implementa los operadores SQL standard IS DISTINCT FROM y IS NOT DISTINCT FROM, puede reescribir las expresiones que las contengan utilizando las siguientes equivalencias:

En general:

a IS DISTINCT FROM b <==>
(
    ((a) IS NULL AND (b) IS NOT NULL)
OR
    ((a) IS NOT NULL AND (b) IS NULL)
OR
    ((a) <> (b))
)

a IS NOT DISTINCT FROM b <==>
(
    ((a) IS NULL AND (b) IS NULL)
OR
    ((a) = (b))
)

Esta respuesta es incorrecta cuando se usa en un contexto donde la diferencia entre LO DESCONOCIDO y LO FALSO importa. Creo que eso es poco común, sin embargo. Ver la respuesta aceptada por @ChrisBandy.

Si se puede identificar un valor de marcador de posición que no ocurre realmente en los datos, entonces COALESCE es una alternativa:

a IS DISTINCT FROM b <==> COALESCE(a, placeholder) <> COALESCE(b, placeholder)
a IS NOT DISTINCT FROM b <==> COALESCE(a, placeholder) = COALESCE(b, placeholder)
 12
Author: Jason Kresowaty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-09 07:51:11

Una advertencia en la reescritura ES DISTINTA y NO ES DISTINTA sería no interferir con el uso de índices, al menos cuando se utiliza SQL Server. En otras palabras, cuando se utiliza lo siguiente:

WHERE COALESCE(@input, x) = COALESCE(column, x)

SQL Server no podrá utilizar ningún índice que incluya columna. Así que en una cláusula WHERE, sería preferible utilizar la forma

WHERE @input = column OR (@input IS NULL AND column IS NULL)

Para aprovechar cualquier índice para columna. (Los paréntesis solo se utilizan para mayor claridad)

 4
Author: Boyd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-24 19:30:26

Para la referencia, la implementación más canónica (y legible) de IS [ NOT ] DISTINCT FROM sería una expresión CASE bien formateada. Para IS DISTINCT FROM:

CASE WHEN [a] IS     NULL AND [b] IS     NULL THEN FALSE
     WHEN [a] IS     NULL AND [b] IS NOT NULL THEN TRUE
     WHEN [a] IS NOT NULL AND [b] IS     NULL THEN TRUE
     WHEN [a] =               [b]             THEN FALSE
     ELSE                                          TRUE
END

Obviamente, otras soluciones (específicamente de John Keller, usando INTERSECT) son más concisas.

Más detalles aquí

 1
Author: Lukas Eder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:21

Solo para extender la respuesta de John Keller. Prefiero usar EXISTS y EXCEPT patrón:

a IS DISTINCT FROM b
<=>
EXISTS (SELECT a EXCEPT SELECT b)
-- NOT EXISTS (SELECT a INTERSECT SELECT b)

Y

a IS NOT DISTINCT FROM  b
<=>
NOT EXISTS (SELECT a EXCEPT SELECT b)
-- EXISTS (SELECT a INTERSECT SELECT b)

Por una razón en particular. NOT está alineado mientras que con INTERSECT está invertido.


SELECT 1 AS PK, 21 AS c, NULL  AS  b
INTO tab1;

SELECT 1 AS PK, 21 AS c, 2 AS b
INTO tab2;

SELECT *
FROM tab1 A
JOIN tab2 B ON A.PK = B.PK
WHERE EXISTS(SELECT A.c, A.B
              EXCEPT
              SELECT B.c, B.b);

Demostración de DBFiddle

 1
Author: Lukasz Szozda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-10 19:43:59

Estas expresiones pueden ser un buen sustituto de la ES DISTINTA DE la lógica y funcionan mejor que los ejemplos anteriores porque terminan siendo compiladas por SQL server en una sola expresión de predicado que dará lugar a aprox. la mitad del costo del operador en una expresión de filtro. Son esencialmente las mismas que las soluciones proporcionadas por Chris Bandy, sin embargo, utilizan funciones anidadas de ISNULL y NULLIF para realizar las comparaciones subyacentes.

(... obviamente ISNULL podría se sustituye por COALESCE si lo prefiere)

  • a IS DISTINCT FROM b se puede reescribir como:

    ISNULL(NULLIF(a, b), NULLIF(b, a)) IS NOT NULL

  • a IS NOT DISTINCT FROM b se puede reescribir como:

    ISNULL(NULLIF(a, b), NULLIF(b, a)) IS NULL

 0
Author: Jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-02 00:02:01
a IS NOT DISTINCT FROM b

Se puede reescribir como:

(a IS NOT NULL AND b IS NOT NULL AND a=b) OR (a IS NULL AND b is NULL)

a IS DISTINCT FROM b

Se puede reescribir como:

NOT (a IS NOT DISTINCT FROM b)
 0
Author: wojtek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-05 06:14:14