O DISTINCT FROM pode ser combinado com QUALQUER ou TUDO de alguma forma?

13

É uma maneira de combinar IS DISTINCT FROMcom o postgres ANYou alguma outra maneira interessante de obter o mesmo 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 diz que tenta topanswers.xyz
fonte

Respostas:

7

Talvez assim :

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

Observe que não apenas o nullna "matriz", mas também o nullin zestão sendo comparados dessa maneira.

Andriy M
fonte
13

Olhando para isso como um problema de gramática, ANYé definido como (em Comparações de linhas e matrizes ):

operador de expressão ANY (expressão de matriz)

Mas is distinct fromnão é um operador, é uma "construção", como nos dizem em Comparison Operators :

Quando esse comportamento não é adequado, utilize o [não] é DISTINCT FROM construções

Como o PostgreSQL possui operadores definidos pelo usuário, podemos definir uma combinação de operador / função 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
);

Então pode preceder ANY:

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo <!> any(array[null, 'A']);  
 contagem 
-------
     3
(1 linha)
Daniel Vérité
fonte
1
Resposta excelente e perspicaz.
Erwin Brandstetter
Definitivamente, isso é muito superior à solução que sugeri, especialmente com a melhoria do @ Erwin.
Andriy H
Esta resposta e os ajustes sugeridos por @ Erwin são realmente excelentes. Estou aceitando Andriy, mas é apenas um caso de preferência pessoal: tenho certeza que muitos preferirão a elegância de vocês.
Jack diz que tente topanswers.xyz
@JackDouglas: adicionei uma solução alternativa aos operadores padrão.
Erwin Brandstetter
Isso é lamentável ... para todos os efeitos, não deveria IS DISTINCT FROMser um operador? Parece apenas uma limitação técnica do analisador e não uma questão semântica.
Andy
10

Operador

Isso se baseia no operador inteligente de @ Daniel .
Enquanto estiver nisso, crie a combinação função / operador usando tipos polimórficos . Então funciona para qualquer tipo - assim como a construção.
E faça a função 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
);

Uma pesquisa rápida com o symbolhound apareceu vazia, para que o operador <!>não pareça estar em uso em nenhum dos módulos.

Se você for usar muito esse operador, poderá aprimorá-lo um pouco mais para ajudar o planejador de consultas ( como o último sugerido em um comentário ). Para iniciantes, você pode adicionar as cláusulas COMMUTATORe NEGATORpara ajudar o otimizador de consultas. Substitua CREATE OPERATORde cima por este:

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

E adicione:

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 = <!>
);

Mas as cláusulas adicionais não ajudarão no caso de uso em questão e índices simples ainda não serão usados. É muito mais sofisticado conseguir isso. (Não tentei.) Leia o capítulo "Informações sobre otimização do operador" no manual para obter detalhes.

Caso de teste

O caso de teste na pergunta só poderá ter sucesso se todos os valores na matriz forem idênticos. Para a matriz na questão ( '{null,A}'::text[]), o resultado é sempre VERDADEIRO. Isso é pretendido? Adicionei outro teste para "IS DISTINCT FROM ALL":

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 com operadores padrão

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

quase pode ser traduzido para

foo = ALL (test_arr) IS NOT TRUE

foo = ALL (test_arr) rendimentos ...

TRUE .. se todos os elementos forem foo
FALSE.. se algum NOT NULLelemento for <> foo
NULL .. se pelo menos um elemento IS NULLe nenhum elemento for<> foo

Portanto, a caixa de canto restante é onde
- foo IS NULL
- e test_arr consiste em nada além de NULLelementos.

Se qualquer um deles puder ser descartado, estamos prontos. Portanto, use o teste simples se
- a coluna estiver definida NOT NULL.
- ou você sabe que a matriz nunca é todos os NULLs.

Senão, teste adicionalmente:

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

Onde 'A'e 'B'podem ser quaisquer valores distintos. Explicação e alternativas sob esta pergunta relacionada no SO:
A matriz é todos os NULLs no PostgreSQL

Novamente, se você souber de algum valor que não possa existir test_arr, por exemplo, a sequência vazia '', ainda poderá simplificar:

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

Aqui está uma matriz de teste completa para verificar todas as combinações:

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

Isso é um pouco mais detalhado que a EXCEPTsolução de Andriy , mas é substancialmente mais rápido.

Erwin Brandstetter
fonte
Ao criar a OPERATOR, a cláusula COMMUTATOR(e NEGATOR, talvez com o IS NOT DISTINCT FROMoperador inverso ) deve ser fornecida? postgresql.org/docs/current/static/xoper-optimization.html
losthorse
1
@losthorse: eu adicionei um pouco sobre isso.
Erwin Brandstetter
Estou usando esse operador para eliminar registros com base no app_status (número inteiro) como este app_status <!> any(array[3,6]). Infelizmente, isso não afeta os registros. Funciona com números inteiros?
M. Habib
@ M.Habib: Por favor, faça sua pergunta como nova pergunta . (Com todos os detalhes relevantes!) Você sempre pode vincular este a um contexto - e deixar um comentário aqui para vincular novamente.
Erwin Brandstetter