È DISTINCT FROM può essere combinato con QUALSIASI o TUTTO in qualche modo?


13

Un modo di postgres di combinarsi IS DISTINCT FROMcon ANYo qualche altro modo pulito per ottenere lo stesso risultato?

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']);
                                   ^

Risposte:


7

Forse così :

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

Si noti che non solo nullin "array" ma anche nullin zviene confrontato in questo modo.


13

Considerandolo come un problema di grammatica, ANYviene definito come (nei confronti di righe e array ):

operatore di espressione ANY (espressione di matrice)

Ma is distinct fromnon è un operatore, è un "costrutto" come ci viene detto in Operatori di confronto :

Quando questo comportamento non è adatto, utilizzare i costrutti IS [NOT] DISTINCT FROM

Poiché PostgreSQL ha operatori definiti dall'utente, possiamo definire una combinazione operatore / funzione per questo scopo:

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

Quindi può precedere ANY:

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo <!> any(array[null, 'A']);  
 contare 
-------
     3
(1 riga)

1
Risposta eccellente e perspicace.
Erwin Brandstetter,

Questo è decisamente molto superiore alla soluzione alternativa che ho suggerito, soprattutto con il miglioramento di @Erwin.
Andriy M

Questa risposta e le modifiche suggerite da @Erwin sono davvero eccellenti. Sto accettando Andriy ma è solo un caso di preferenza personale: sono sicuro che molti preferiranno l'eleganza del tuo.
Jack dice di provare topanswers.xyz il

@JackDouglas: ho aggiunto una soluzione alternativa con operatori standard.
Erwin Brandstetter,

È un peccato ... a tutti gli effetti, non dovrebbe IS DISTINCT FROMessere un operatore? Sembra solo una limitazione tecnica del parser piuttosto che un problema semantico.
Andy,

10

Operatore

Questo si basa sull'abile operatore di Daniel .
Mentre ci sei, crea la combo funzione / operatore usando tipi polimorfici . Quindi funziona per qualsiasi tipo, proprio come il costrutto.
E rendere la funzione 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 rapida ricerca con symbolhound è risultata vuota, quindi l'operatore <!>non sembra essere in uso in nessuno dei moduli.

Se hai intenzione di utilizzare questo operatore molto, potresti approfondire un po 'di più per aiutare il pianificatore di query ( come suggerito in un commento LostHorse ). Per cominciare, è possibile aggiungere le clausole COMMUTATORe NEGATORper aiutare Query Optimizer. Sostituisci CREATE OPERATORdall'alto con questo:

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

E aggiungi:

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

Ma le clausole aggiuntive non aiuteranno il caso d'uso a portata di mano e gli indici semplici non saranno ancora utilizzati. È molto più sofisticato per raggiungere questo obiettivo. (Non ho provato.) Leggi il capitolo "Informazioni sull'ottimizzazione dell'operatore" nel manuale per i dettagli.

Caso di prova

Il caso di test nella domanda può avere esito positivo solo se tutti i valori nella matrice sono identici. Per l'array nella domanda ( '{null,A}'::text[]) il risultato è sempre VERO. È previsto? Ho aggiunto un altro test per "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 con operatori standard

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

può quasi essere tradotto in

foo = ALL (test_arr) IS NOT TRUE

foo = ALL (test_arr) rese ...

TRUE .. se tutti gli elementi sono foo
FALSE.. se qualche NOT NULLelemento è <> foo
NULL .. se almeno uno IS NULLe nessun elemento lo sono<> foo

Quindi, il rimanente caso d'angolo è dove
- foo IS NULL
- e test_arr consiste solo di NULLelementi.

Se uno dei due può essere escluso, abbiamo finito. Pertanto, utilizzare il test semplice se
- la colonna è definita NOT NULL.
- oppure si conosce la matrice non è mai tutta vuota.

Altrimenti, prova anche:

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

Dove 'A'e 'B'può essere qualsiasi valori distinti. Spiegazione e alternative sotto questa domanda correlata su SO:
array è tutto NULL in PostgreSQL

Ancora una volta, se conosci qualche valore che non può esistere test_arr, ad esempio la stringa vuota '', puoi comunque semplificare:

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

Ecco una matrice di test completa per verificare tutte le combinazioni:

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

Questo è un po 'più dettagliato della EXCEPTsoluzione di Andriy , ma è sostanzialmente più veloce.


Quando si crea la OPERATOR, deve essere fornita la clausola COMMUTATOR(e NEGATOR, forse con l' IS NOT DISTINCT FROMoperatore inverso )? postgresql.org/docs/current/static/xoper-optimization.html
losthorse

1
@losthorse: ho aggiunto un po 'affrontandolo.
Erwin Brandstetter,

Sto usando questo operatore per eliminare i record basati su app_status (numero intero) come questo app_status <!> any(array[3,6]). Sfortunatamente, non ha alcun effetto sui record. Funziona con numeri interi?
M. Habib,

@ M.Habib: si prega di porre la domanda come nuova domanda . (Con tutti i dettagli rilevanti!) Puoi sempre collegarti a questo per contesto - e rilasciare un commento qui per ricollegarti.
Erwin Brandstetter,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.