¿Puede IS DISTINCT FROM combinarse con CUALQUIERA o TODO de alguna manera?

13

Es una manera de combinar postgres IS DISTINCT FROMcon ANYo alguna otra forma elegante de conseguir el mismo resultado?

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo <> any(array[null, 'A']);

 count
-------
     1
(1 row)

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo is distinct from any(array[null, 'A']);  

ERROR:  syntax error at or near "any"
LINE 3: where foo is distinct from any(array[null, 'A']);
                                   ^
Jack dice que intente topanswers.xyz
fuente

Respuestas:

7

Quizás así :

select foo
     , exists (values (null), ('A') except select foo) chk_any
     , not exists (values (null), ('A') intersect select foo) chk_all
from ( values ('A'),('Z'),(null) ) z(foo);

 foo | chk_any | chk_all
-----+---------+---------
 A   | t       | f
 Z   | t       | t
     | t       | f

Tenga en cuenta que no solo se compara nullen el "conjunto" sino también nullen el de zesta manera.

Andriy M
fuente
13

Mirándolo como un problema de gramática, ANYse define como (en las comparaciones de filas y matrices ):

operador de expresión ANY (expresión de matriz)

Pero is distinct fromno es un operador, es una "construcción" como se nos dice en Operadores de comparación :

Cuando este comportamiento no es adecuado, use las construcciones IS [NOT] DISTINCT FROM

Dado que PostgreSQL tiene operadores definidos por el usuario, podemos definir un combo de operador / función para este propósito:

create function is_distinct_from(text, text) returns bool as 
'select $1 is distinct from $2;' language sql;

create operator <!> (
 procedure=is_distinct_from(text,text),
 leftarg=text, rightarg=text
);

Entonces puede preceder ANY:

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo <!> any(array[null, 'A']);  
 contar 
-------
     3
(1 fila)
Daniel Vérité
fuente
1
Excelente, perspicaz respuesta.
Erwin Brandstetter
Esto definitivamente es muy superior a la solución sugerida, especialmente con la mejora de @ Erwin.
Andriy M
Esta respuesta y los ajustes sugeridos por @ Erwin son realmente excelentes. Estoy aceptando a Andriy, pero es solo un caso de preferencia personal: estoy seguro de que muchos preferirán la elegancia tuya.
Jack dice que intente topanswers.xyz
@JackDouglas: agregué una solución alternativa con operadores estándar.
Erwin Brandstetter
Eso es lamentable ... para todos los efectos, ¿no debería IS DISTINCT FROMser un operador? Parece solo una limitación técnica del analizador en lugar de un problema semántico.
Andy
10

Operador

Esto se basa en el operador inteligente de @Daniel .
Mientras lo hace, cree el combo de función / operador utilizando tipos polimórficos . Entonces funciona para cualquier tipo, al igual que la construcción.
Y hacer la función IMMUTABLE.

CREATE FUNCTION is_distinct_from(anyelement, anyelement)
  RETURNS bool LANGUAGE sql IMMUTABLE AS 
'SELECT $1 IS DISTINCT FROM $2';

CREATE OPERATOR <!> (
  PROCEDURE = is_distinct_from(anyelement,anyelement),
  LEFTARG  = anyelement
, RIGHTARG = anyelement
);

Una búsqueda rápida con Symbolhound quedó vacía, por lo que el operador <!>no parece estar en uso en ninguno de los módulos.

Si va a utilizar mucho este operador, puede desarrollarlo un poco más para ayudar al planificador de consultas ( como los sugeridos en un comentario ). Para empezar, puede agregar las cláusulas COMMUTATORy NEGATORpara ayudar al optimizador de consultas. Reemplazar CREATE OPERATORdesde arriba con esto:

CREATE OPERATOR <!> (
  PROCEDURE = is_distinct_from(anyelement,anyelement),
  LEFTARG  = anyelement
, RIGHTARG = anyelement
, COMMUTATOR = <!>
, NEGATOR = =!=
);

Y añadir:

CREATE FUNCTION is_not_distinct_from(anyelement, anyelement)
  RETURNS bool LANGUAGE sql IMMUTABLE AS 
'SELECT $1 IS NOT DISTINCT FROM $2';

CREATE OPERATOR =!= (
  PROCEDURE = is_not_distinct_from(anyelement,anyelement),
  LEFTARG  = anyelement
, RIGHTARG = anyelement
, COMMUTATOR = =!=
, NEGATOR = <!>
);

Pero las cláusulas adicionales no ayudarán con el caso de uso en cuestión y los índices simples todavía no se utilizarán. Es mucho más sofisticado lograr eso. (No lo he intentado). Lea el capítulo "Información de optimización del operador" en el manual para obtener más detalles.

Caso de prueba

El caso de prueba en la pregunta solo puede tener éxito si todos los valores de la matriz son idénticos. Para la matriz en la pregunta ( '{null,A}'::text[]) el resultado es siempre VERDADERO. ¿Es eso intencionado? Agregué otra prueba para "ES DISTINTO DE TODO":

SELECT foo
     , foo <!> ANY ('{null,A}'::text[]) AS chk_any
     , foo <!> ALL ('{null,A}'::text[]) AS chk_all
FROM (
   VALUES ('A'),('Z'),(NULL)
   ) z(foo)

 foo | chk_any | chk_all
-----+---------+---------
 A   | t       | f
 Z   | t       | t
     | t       | f

Alternativa con operadores estándar

foo IS DISTINCT FROM ANY (test_arr) -- illegal syntax

puede casi ser traducido a

foo = ALL (test_arr) IS NOT TRUE

foo = ALL (test_arr) rinde ...

TRUE .. si todos los elementos son foo
FALSE.. si algún NOT NULLelemento es <> foo
NULL .. si al menos un elemento IS NULLy ningún elemento es<> foo

Entonces, el caso de la esquina restante es donde
- foo IS NULL
- y test_arr consiste en nada más que NULLelementos.

Si cualquiera de los dos puede descartarse, ya hemos terminado. Por lo tanto, use la prueba simple if
- la columna está definida NOT NULL.
- o que sabe la matriz no es todos los valores NULL.

De lo contrario, prueba adicionalmente:

AND ('A' = ALL(test_arr) IS NOT NULL OR 
     'B' = ALL(test_arr) IS NOT NULL OR
     foo IS NOT NULL)

Donde 'A'y 'B'puede ser cualquier valor distinto. Explicación y alternativas bajo esta pregunta relacionada sobre SO:
¿Es la matriz todos NULL en PostgreSQL

Nuevamente, si conoce algún valor que no puede existir test_arr, por ejemplo, la cadena vacía '', aún puede simplificar:

AND ('' = ALL(test_arr) IS NOT NULL OR
     foo IS NOT NULL)

Aquí hay una matriz de prueba completa para verificar todas las combinaciones:

SELECT foo, test_arr
     , foo = ALL(test_arr) IS NOT TRUE  AS test_simple
     , foo = ALL(test_arr) IS NOT TRUE
       AND ('A' = ALL(test_arr) IS NOT NULL OR
            'B' = ALL(test_arr) IS NOT NULL OR 
            foo IS NOT NULL)            AS test_sure 
FROM (
   VALUES ('A'),('Z'),(NULL)
   ) v(foo)
CROSS JOIN (
   VALUES ('{null,A}'::text[]),('{A,A}'),('{null,null}')
   ) t(test_arr)

 foo |  test_arr   | test_simple | test_sure
-----+-------------+-------------+-----------
 A   | {NULL,A}    | t           | t
 A   | {A,A}       | f           | f   -- only TRUE case
 A   | {NULL,NULL} | t           | t
 Z   | {NULL,A}    | t           | t
 Z   | {A,A}       | t           | t
 Z   | {NULL,NULL} | t           | t
     | {NULL,A}    | t           | t
     | {A,A}       | t           | t
     | {NULL,NULL} | t           | f   -- special case

Esto es un poco más detallado que la EXCEPTsolución de Andriy , pero es sustancialmente más rápido.

Erwin Brandstetter
fuente
Al crear el OPERATOR, caso de que el COMMUTATOR(y NEGATOR, tal vez con la inversa IS NOT DISTINCT FROMdel operador) Cláusula suministrar? postgresql.org/docs/current/static/xoper-optimization.html
losthorse
1
@losthorse: agregué un poco para abordar eso.
Erwin Brandstetter
Estoy usando este operador para eliminar registros basados ​​en app_status (entero) como este app_status <!> any(array[3,6]). Desafortunadamente, no tiene ningún efecto en los registros. ¿Funciona con enteros?
M. Habib
@ M.Habib: por favor haga su pregunta como una nueva pregunta . (¡Con todos los detalles relevantes!) Siempre puede vincular a este para el contexto, y dejar un comentario aquí para vincular de nuevo.
Erwin Brandstetter