Có thể kết hợp DISTINCT TỪ với BẤT K or hoặc TẤT CẢ bằng cách nào đó?


13

Là một cách postgres kết hợp IS DISTINCT FROMvới ANYhoặc một số cách gọn gàng khác để có được kết quả tương tự?

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

Câu trả lời:


7

Có lẽ như thế này :

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

Lưu ý rằng không chỉ nulltrong "mảng" mà cả phần nulltrong zcũng được so sánh theo cách này.


13

Nhìn vào nó như một vấn đề ngữ pháp, ANYđược định nghĩa là (trong So sánh Row và Array ):

toán tử biểu thức ANY (biểu thức mảng)

Nhưng is distinct fromkhông phải là toán tử, đó là "cấu trúc" như chúng ta đã nói trong Toán tử so sánh :

Khi hành vi này là không phù hợp, sử dụng LÀ [NOT] khác biệt với cấu trúc

Vì PostgreSQL có các toán tử do người dùng định nghĩa, chúng tôi có thể định nghĩa một tổ hợp toán tử / hàm cho mục đích này:

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

Sau đó, nó có thể đi trước ANY:

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo <!> any(array[null, 'A']);  
 đếm 
-------
     3
(1 hàng)

1
Tuyệt vời, câu trả lời sâu sắc.
Erwin Brandstetter

Điều này chắc chắn vượt trội hơn nhiều so với cách giải quyết mà tôi đề xuất, đặc biệt là với sự cải thiện của @ Erwin.
Andriy M

Câu trả lời này và các chỉnh sửa được đề xuất của @ Erwin thực sự xuất sắc. Tôi chấp nhận của Irina nhưng đó chỉ là một trường hợp sở thích cá nhân: Tôi chắc chắn nhiều người sẽ thích sự thanh lịch của bạn.
Jack nói hãy thử topanswers.xyz

@JackDoumund: Tôi đã thêm một giải pháp thay thế với các toán tử tiêu chuẩn.
Erwin Brandstetter

Điều đó thật đáng tiếc ... cho tất cả ý định và mục đích, không nên IS DISTINCT FROMlà một nhà điều hành? Có vẻ như chỉ là một giới hạn kỹ thuật của trình phân tích cú pháp chứ không phải là một vấn đề ngữ nghĩa.
Andy

10

Nhà điều hành

Đây là tòa nhà trên nhà điều hành thông minh của @ Daniel .
Trong khi ở đó, tạo tổ hợp hàm / toán tử bằng cách sử dụng các loại đa hình . Sau đó, nó hoạt động cho bất kỳ loại - giống như các cấu trúc.
Và làm cho các chức năng 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
);

Một tìm kiếm nhanh với biểu tượng xuất hiện trống rỗng, do đó toán tử <!>dường như không được sử dụng trong bất kỳ mô-đun nào.

Nếu bạn sẽ sử dụng toán tử này rất nhiều, bạn có thể bổ sung thêm một số thứ khác để hỗ trợ trình hoạch định truy vấn ( như bị mất đề xuất trong một bình luận ). Để bắt đầu, bạn có thể thêm COMMUTATORNEGATORcác mệnh đề để hỗ trợ trình tối ưu hóa truy vấn. Thay thế CREATE OPERATORtừ trên bằng cái này:

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

Và thêm:

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

Nhưng các điều khoản bổ sung sẽ không giúp ích cho trường hợp sử dụng và các chỉ mục đơn giản vẫn sẽ không được sử dụng. Nó phức tạp hơn nhiều để đạt được điều đó. (Tôi chưa thử.) Đọc chương "Thông tin tối ưu hóa toán tử" trong hướng dẫn để biết chi tiết.

Trường hợp thử nghiệm

Trường hợp thử nghiệm trong câu hỏi chỉ có thể thành công nếu tất cả các giá trị trong mảng giống hệt nhau. Đối với mảng trong câu hỏi ( '{null,A}'::text[]) kết quả luôn luôn ĐÚNG. Đó có phải là dự định? Tôi đã thêm một bài kiểm tra khác cho "IS DISTINCT TỪ TẤT CẢ":

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

Thay thế với các nhà khai thác tiêu chuẩn

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

gần như có thể được dịch sang

foo = ALL (test_arr) IS NOT TRUE

foo = ALL (test_arr) mang lại ...

TRUE .. nếu tất cả các phần tử là foo
FALSE.. nếu bất kỳ NOT NULLphần tử nào là <> foo
NULL .. nếu ít nhất một phần tử IS NULLvà không có phần tử nào là<> foo

Vì vậy, trường hợp góc còn lại là nơi
- foo IS NULL
- test_arr không có gì ngoài NULLcác phần tử.

Nếu một trong hai có thể được loại trừ, chúng tôi đã hoàn thành. Do đó, sử dụng thử nghiệm đơn giản nếu
- cột được xác định NOT NULL.
- hoặc bạn biết mảng không bao giờ là tất cả NULL.

Khác, kiểm tra bổ sung:

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

Ở đâu 'A''B'có thể là bất kỳ giá trị riêng biệt. Giải thích và giải pháp thay thế theo câu hỏi liên quan này trên SO:
Là mảng tất cả các NULL trong PostgreQuery

Một lần nữa, nếu bạn biết về bất kỳ giá trị nào không thể tồn tại test_arr, ví dụ chuỗi rỗng '', bạn vẫn có thể đơn giản hóa:

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

Dưới đây là một ma trận thử nghiệm hoàn chỉnh để kiểm tra tất cả các kết hợp:

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

Đây là một chút dài dòng hơn so với giải pháp của AndroidEXCEPT , nhưng về cơ bản là nhanh hơn.


Khi tạo OPERATOR, nên COMMUTATOR(và NEGATOR, có lẽ với nghịch đảo IS NOT DISTINCT FROMđiều hành) khoản được cung cấp? postgresql.org/docs/civerse/static/xoper-optimization.html
Losthorse

1
@losthorse: Tôi đã thêm một chút địa chỉ đó.
Erwin Brandstetter

Tôi đang sử dụng toán tử này để loại bỏ các bản ghi dựa trên app_status (số nguyên) như thế này app_status <!> any(array[3,6]). Thật không may, nó không có bất kỳ ảnh hưởng đến hồ sơ. Nó hoạt động với số nguyên?
M. Habib

@ M.Habib: Hãy đặt câu hỏi của bạn như câu hỏi mới . (Với tất cả các chi tiết có liên quan!) Bạn luôn có thể liên kết với cái này cho ngữ cảnh - và gửi bình luận ở đây để liên kết lại.
Erwin Brandstetter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.