IS DISTINCT FROM peut-il être combiné avec TOUT ou TOUT d'une manière ou d'une autre?

13

Est-ce un moyen postgres de combiner IS DISTINCT FROMavec ANYou une autre façon soignée d'obtenir le même résultat?

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 dit d'essayer topanswers.xyz
la source

Réponses:

7

Peut-être comme ça :

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

Notez que non seulement le nulldans le "tableau" mais aussi le nullin zest comparé de cette façon.

Andriy M
la source
13

Le regarder comme un problème de grammaire, ANYest défini comme (dans les comparaisons de lignes et de tableaux ):

opérateur d'expression ANY (expression de tableau)

Mais ce is distinct fromn'est pas un opérateur, c'est une "construction" comme on nous le dit dans Opérateurs de comparaison :

Lorsque ce comportement ne convient pas, utilisez la IS [NOT] DISTINCT constructions

Puisque PostgreSQL possède des opérateurs définis par l'utilisateur, nous pouvons définir un combo opérateur / fonction à cet effet:

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

Ensuite, il peut précéder ANY:

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo <!> any(array[null, 'A']);  
 compter 
-------
     3
(1 rangée)
Daniel Vérité
la source
1
Excellente réponse perspicace.
Erwin Brandstetter
C'est certainement bien supérieur à la solution de contournement que j'ai suggérée, en particulier avec l'amélioration de @ Erwin.
Andriy M
Cette réponse et les ajustements suggérés par @ Erwin sont vraiment excellents. J'accepte Andriy mais c'est juste un cas de préférence personnelle: je suis sûr que beaucoup préféreront l'élégance de la vôtre.
Jack dit d'essayer topanswers.xyz
@JackDouglas: J'ai ajouté une solution alternative avec des opérateurs standard.
Erwin Brandstetter
C'est malheureux ... à toutes fins utiles, ne devrait pas IS DISTINCT FROMêtre un opérateur? Cela ressemble à une limitation technique de l'analyseur plutôt qu'à un problème sémantique.
Andy
10

Opérateur

Cela s'appuie sur l'opérateur intelligent de @ Daniel .
Tout en y étant, créez le combo fonction / opérateur en utilisant des types polymorphes . Ensuite, cela fonctionne pour tout type - tout comme la construction.
Et faites la fonction 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
);

Une recherche rapide avec symbolhound est apparue vide, de sorte que l'opérateur <!>ne semble pas être utilisé dans aucun des modules.

Si vous utilisez beaucoup cet opérateur, vous pouvez l'étoffer davantage pour aider le planificateur de requêtes ( comme losthorse l'a suggéré dans un commentaire ). Pour commencer, vous pouvez ajouter les clauses COMMUTATORet NEGATORpour aider l'optimiseur de requête. Remplacez CREATE OPERATORpar le haut par ceci:

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

Et ajouter:

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

Mais les clauses supplémentaires n'aideront pas avec le cas d'utilisation à portée de main et les index simples ne seront toujours pas utilisés. C'est beaucoup plus sophistiqué pour y parvenir. (Je n'ai pas essayé.) Lisez le chapitre "Informations sur l'optimisation de l'opérateur" dans le manuel pour plus de détails.

Cas de test

Le scénario de test dans la question ne peut réussir que si toutes les valeurs du tableau sont identiques. Pour le tableau dans la question ( '{null,A}'::text[]), le résultat est toujours VRAI. Est-ce prévu? J'ai ajouté un autre test pour "EST DISTINCT DE TOUS":

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

Alternative avec opérateurs standard

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

peut presque être traduit en

foo = ALL (test_arr) IS NOT TRUE

foo = ALL (test_arr) rendements ...

TRUE .. si tous les éléments sont foo
FALSE.. si un NOT NULLélément est <> foo
NULL .. si au moins un élément IS NULLet aucun élément n'est<> foo

Ainsi, le cas d'angle restant est où
- foo IS NULL
- et ne se test_arr compose que d' NULLéléments.

Si l'un ou l'autre peut être exclu, nous avons terminé. Par conséquent, utilisez le test simple si
- la colonne est définie NOT NULL.
- ou vous savez que le tableau n'est jamais entièrement NULL.

Sinon, testez en plus:

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

'A'et 'B'peut être des valeurs distinctes. Explication et alternatives sous cette question connexe sur SO: le
tableau est-il tous NULL dans PostgreSQL

Encore une fois, si vous connaissez une valeur qui ne peut pas exister dans test_arr, par exemple la chaîne vide '', vous pouvez toujours simplifier:

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

Voici une matrice de test complète pour vérifier toutes les combinaisons:

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

C'est un peu plus verbeux que la EXCEPTsolution d' Andriy , mais c'est beaucoup plus rapide.

Erwin Brandstetter
la source
Lors de la création de OPERATOR, la clause COMMUTATOR(et NEGATOR, peut-être avec l' IS NOT DISTINCT FROMopérateur inverse ) doit-elle être fournie? postgresql.org/docs/current/static/xoper-optimization.html
losthorse
1
@losthorse: J'ai ajouté un peu pour répondre à cela.
Erwin Brandstetter
J'utilise cet opérateur pour éliminer les enregistrements basés sur app_status (entier) comme celui-ci app_status <!> any(array[3,6]). Malheureusement, cela n'a aucun effet sur les enregistrements. Fonctionne-t-il avec des entiers?
M. Habib
@ M.Habib: Veuillez poser votre question en tant que nouvelle question . (Avec tous les détails pertinents!) Vous pouvez toujours créer un lien vers celui-ci pour le contexte - et déposez un commentaire ici pour créer un lien.
Erwin Brandstetter