È 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.