Kann IS DISTINCT FROM irgendwie mit ANY oder ALL kombiniert werden?

13

Ist eine Postgres-Methode die Kombination IS DISTINCT FROMmit ANYoder eine andere nette Methode, um das gleiche Ergebnis zu erzielen ?

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 sagt, versuchen Sie es mit topanswers.xyz
quelle

Antworten:

7

Vielleicht so :

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

Beachten Sie, dass nicht nur die nullin der „Matrix“ , sondern auch die nullin zdieser Art und Weise verglichen wird.

Andriy M
quelle
13

Betrachtet man es als ein Grammatikproblem, ANYist definiert als (in Zeilen- und Array-Vergleichen ):

Ausdrucksoperator ANY (Array-Ausdruck)

Aber es is distinct fromist kein Operator, sondern ein "Konstrukt", wie wir in Vergleichsoperatoren erfahren haben :

Wenn dieses Verhalten nicht geeignet ist, verwenden Sie die Konstrukte IS [NOT] DISTINCT FROM

Da PostgreSQL benutzerdefinierte Operatoren hat, können wir zu diesem Zweck eine Operator / Funktions-Kombination definieren:

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

Dann kann es vorangehen ANY:

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo <!> any(array[null, 'A']);  
 Anzahl 
-------
     3
(1 Reihe)
Daniel Vérité
quelle
1
Ausgezeichnete, aufschlussreiche Antwort.
Erwin Brandstetter
Dies ist definitiv der von mir vorgeschlagenen Problemumgehung weit überlegen, insbesondere mit der Verbesserung von @ Erwin.
Andriy M
Diese Antwort und die von @ Erwin vorgeschlagenen Optimierungen sind wirklich hervorragend. Ich akzeptiere Andriys, aber es ist nur ein Fall persönlicher Präferenz: Ich bin sicher, dass viele die Eleganz von Ihnen bevorzugen werden.
Jack sagt, versuchen Sie topanswers.xyz
@JackDouglas: Ich habe eine alternative Lösung mit Standardoperatoren hinzugefügt.
Erwin Brandstetter
Das ist bedauerlich ... in jeder Hinsicht sollte kein IS DISTINCT FROMBediener sein? Scheint eher eine technische Einschränkung des Parsers zu sein als ein semantisches Problem.
Andy
10

Operator

Dies baut auf @ Daniel's cleverem Operator auf .
Erstellen Sie dabei die Funktions- / Operatorkombination mit polymorphen Typen . Dann funktioniert es für jeden Typ - genau wie das Konstrukt.
Und mach die Funktion 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
);

Eine Schnellsuche mit symbolhound war leer, sodass der Operator <!>in keinem der Module verwendet zu werden scheint.

Wenn Sie diesen Operator häufig verwenden, können Sie ihn dem Abfrageplaner näher bringen ( wie in einem Kommentar unter losthorse vorgeschlagen ). Für den Anfang können Sie die Klauseln COMMUTATORund hinzufügen NEGATOR, um das Abfrageoptimierungsprogramm zu unterstützen. Ersetzen Sie CREATE OPERATORvon oben mit diesem:

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

Und füge hinzu:

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

Die zusätzlichen Klauseln helfen jedoch nicht im vorliegenden Anwendungsfall, und einfache Indizes werden weiterhin nicht verwendet. Um dies zu erreichen, ist es viel ausgefeilter. (Ich habe es nicht versucht.) Weitere Informationen finden Sie im Kapitel "Informationen zur Bedieneroptimierung" im Handbuch.

Testfall

Der Testfall in der Frage kann nur erfolgreich sein, wenn alle Werte im Array identisch sind. Für das Array in der Frage ( '{null,A}'::text[]) ist das Ergebnis immer WAHR. Ist das beabsichtigt? Ich habe einen weiteren Test für "IS DISTINCT FROM ALL" hinzugefügt:

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 mit Standardbetreibern

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

kann fast übersetzt werden

foo = ALL (test_arr) IS NOT TRUE

foo = ALL (test_arr) ergibt ...

TRUE ..wenn alle Elemente sind foo
FALSE..wenn irgendein NOT NULLElement ist <> foo
NULL ..wenn mindestens ein Element IS NULLund kein Element ist<> foo

Der verbleibende Eckfall ist also wo
- foo IS NULL
- und test_arr besteht nur aus NULLElementen.

Wenn einer von beiden ausgeschlossen werden kann, sind wir fertig. Verwenden Sie daher den einfachen Test, wenn
- die Spalte definiert ist NOT NULL.
- oder Sie wissen, dass das Array niemals alle NULL-Werte enthält.

Sonst zusätzlich testen:

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

Wo 'A'und 'B'kann keine unterschiedlichen Werte. Erklärung und Alternativen unter dieser verwandten Frage zu SO:
Ist Array alle NULLs in PostgreSQL

Noch einmal, wenn Sie wissen , über einen beliebigen Wert, der in nicht existieren kann test_arrzum Beispiel die leere Zeichenkette '', können Sie noch vereinfachen:

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

Hier ist eine vollständige Testmatrix, um nach allen Kombinationen zu suchen:

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

Dies ist etwas ausführlicher als Andriys EXCEPTLösung , aber wesentlich schneller.

Erwin Brandstetter
quelle
Wenn die Erstellung OPERATORsollte die COMMUTATOR(und NEGATORvielleicht mit der inversen IS NOT DISTINCT FROMOperator) Klausel geliefert werden? postgresql.org/docs/current/static/xoper-optimization.html
losthorse
1
@losthorse: Ich habe etwas hinzugefügt, um das zu adressieren.
Erwin Brandstetter
Ich benutze diesen Operator, um Datensätze basierend auf app_status (integer) wie folgt zu entfernen app_status <!> any(array[3,6]). Auf Datensätze hat dies leider keine Auswirkung. Funktioniert es mit ganzen Zahlen?
M. Habib
@ M.Habib: Bitte stell deine Frage als neue Frage . (Mit allen relevanten Details!) Sie können immer einen Link zu diesem für den Kontext erstellen - und hier einen Kommentar hinterlassen, um zurück zu verlinken.
Erwin Brandstetter