Bisakah IS DISTINCT FROM dikombinasikan dengan ANY atau ALL entah bagaimana?

13

Apakah cara postgres menggabungkan IS DISTINCT FROMdengan ANYatau cara lain yang rapi untuk mendapatkan hasil yang sama?

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 berkata coba topanswers.xyz
sumber

Jawaban:

7

Mungkin seperti ini :

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

Perhatikan bahwa tidak hanya nulldi dalam "array" tetapi juga nulldi zsedang dibandingkan dengan cara ini.

Andriy M
sumber
13

Melihatnya sebagai masalah tata bahasa, ANYdidefinisikan sebagai (dalam Perbandingan Baris dan Array ):

operator ekspresi APAPUN (ekspresi array)

Tetapi is distinct frombukan operator, ini adalah "konstruk" seperti yang diperintahkan pada Operator Perbandingan :

Ketika perilaku ini tidak cocok, gunakan IS [BUKAN] BANGUN DARI konstruksi

Karena PostgreSQL memiliki operator yang ditentukan pengguna, kami dapat menentukan kombo operator / fungsi untuk tujuan ini:

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

Maka dapat mendahului ANY:

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo <!> any(array[null, 'A']);  
 menghitung 
-------
     3
(1 baris)
Daniel Vérité
sumber
1
Jawaban luar biasa dan berwawasan luas.
Erwin Brandstetter
Ini jelas jauh lebih unggul daripada solusi yang saya sarankan, terutama dengan peningkatan @ Erwin.
Andriy M
Jawaban ini dan tweak yang disarankan oleh Erwin sangat bagus. Saya menerima Andriy tetapi itu hanya kasus preferensi pribadi: Saya yakin banyak yang lebih suka keanggunan Anda.
Jack bilang coba topanswers.xyz
@JackDouglas: Saya menambahkan solusi alternatif dengan operator standar.
Erwin Brandstetter
Sangat disayangkan ... untuk semua maksud dan tujuan, bukankah seharusnya IS DISTINCT FROMoperator? Sepertinya hanya keterbatasan teknis pengurai daripada masalah semantik.
Andy
10

Operator

Ini berdasarkan operator pintar @ Daniel .
Saat berada di sana, buat combo fungsi / operator menggunakan tipe polimorfik . Kemudian ia bekerja untuk semua jenis - seperti konstruksi.
Dan buat fungsinya 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
);

Pencarian cepat dengan symbolhound muncul kosong, sehingga operator <!>tampaknya tidak digunakan dalam modul apa pun.

Jika Anda akan sering menggunakan operator ini, Anda dapat menyempurnakannya untuk membantu perencana kueri ( seperti saran kuda yang hilang dalam komentar ). Sebagai permulaan, Anda bisa menambahkan COMMUTATORdan NEGATORklausa untuk membantu pengoptimal kueri. Ganti CREATE OPERATORdari atas dengan ini:

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

Dan tambahkan:

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

Tetapi klausa tambahan tidak akan membantu dengan kasus penggunaan di tangan dan indeks polos masih tidak akan digunakan. Jauh lebih canggih untuk mencapainya. (Saya belum mencoba.) Baca bab "Informasi Optimalisasi Operator" di manual untuk detailnya.

Kasus cobaan

Kasus uji dalam pertanyaan hanya dapat berhasil jika semua nilai dalam array identik. Untuk larik dalam pertanyaan ( '{null,A}'::text[]) hasilnya selalu BENAR. Apakah itu dimaksudkan? Saya menambahkan tes lain untuk "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

Alternatif dengan operator standar

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

dapat hampir diterjemahkan ke

foo = ALL (test_arr) IS NOT TRUE

foo = ALL (test_arr) hasil ...

TRUE .. jika semua elemen adalah foo
FALSE.. jika ada NOT NULLelemen <> foo
NULL .. jika setidaknya satu elemen IS NULLdan tidak ada elemen<> foo

Jadi, kasing sudut yang tersisa adalah tempat
- foo IS NULL
- dan test_arr terdiri dari NULLelemen.

Jika salah satu dari mereka dapat dikesampingkan, kita sudah selesai. Karena itu, gunakan tes sederhana jika
- kolom didefinisikan NOT NULL.
- atau Anda tahu array tidak pernah semua NULL.

Selain itu, tes tambahan:

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

Di mana 'A'dan 'B'bisa ada nilai yang berbeda. Penjelasan dan alternatif di bawah pertanyaan terkait ini pada SO:
Apakah array semua NULL di PostgreSQL

Sekali lagi, jika Anda tahu tentang nilai apa pun yang tidak dapat ada test_arr, misalnya string kosong '', Anda masih dapat menyederhanakan:

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

Ini adalah matriks tes lengkap untuk memeriksa semua kombinasi:

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

Ini sedikit lebih bertele-tele daripada solusi AndriyEXCEPT , tetapi jauh lebih cepat.

Erwin Brandstetter
sumber
Saat membuat klausa OPERATOR, haruskah COMMUTATOR(dan NEGATOR, mungkin dengan IS NOT DISTINCT FROMoperator terbalik ) diberikan? postgresql.org/docs/current/static/xoper-optimization.html
losthorse
1
@losthorse: Saya menambahkan sedikit mengatasi itu.
Erwin Brandstetter
Saya menggunakan operator ini untuk menghilangkan catatan berdasarkan app_status (integer) seperti ini app_status <!> any(array[3,6]). Sayangnya, itu tidak berpengaruh pada catatan. Apakah itu berfungsi dengan bilangan bulat?
M. Habib
@ M. Habib: Silakan ajukan pertanyaan Anda sebagai pertanyaan baru . (Dengan semua detail yang relevan!) Anda selalu dapat menautkan ini untuk konteks - dan memberikan komentar di sini untuk menautkan kembali.
Erwin Brandstetter