Чи можна ВІДДІЛИТИ ВІД ЧАСУВАННЯ З будь-яким чи ВСІМ?


13

Є Postgres спосіб об'єднання IS DISTINCT FROMз ANYабо яким -небудь іншим акуратним способом отримати той же результат?

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

Відповіді:


7

Можливо, так :

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

Зауважте, що таким чином порівнюється не тільки null"масив", але й nullin z.


13

Розглядаючи це як граматичну проблему, ANYвизначається як (у порівняннях рядків та масивів ):

оператор виразів будь-який (масив вираз)

Але is distinct fromце не оператор, це "конструкція", про що нам кажуть в операторах порівняння :

Якщо така поведінка не підходить, використовуйте конструкції IS [NOT] DISTINCT FROM

Оскільки PostgreSQL має визначені користувачем оператори, для цього ми можемо визначити комбінацію оператора / функції:

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

Тоді це може передувати ANY:

select count(*)
from (select 'A' foo union all select 'Z' union all select null) z
where foo <!> any(array[null, 'A']);  
 рахувати 
-------
     3
(1 ряд)

1
Відмінна, прониклива відповідь.
Ервін Брандстеттер

Це, безумовно, набагато перевершує рішення, яке я запропонував, особливо з вдосконаленням @ Ервіна.
Андрій М

Ця відповідь та запропоновані Ервіном виправлення справді чудові. Я приймаю Андрія, але це лише випадок особистих уподобань: я впевнений, що багато хто віддасть перевагу вашій витонченості.
Джек каже, спробуйте topanswers.xyz

@JackDouglas: Я додав альтернативне рішення зі стандартними операторами.
Ервін Брандстеттер

Це прикро ... за всіма намірами та цілями не слід IS DISTINCT FROMбути оператором? Здається, це лише технічне обмеження аналізатора, а не семантичне питання.
Енді,

10

Оператор

Це спирається на розумного оператора @ Daniel .
Перебуваючи в ньому, створіть функцію / оператор комбо, використовуючи поліморфні типи . Тоді він працює для будь-якого типу - як і конструкція.
І зробити функцію 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
);

Швидкий пошук з символікою виявився порожнім, тому оператор, <!>здається, не використовується в жодному з модулів.

Якщо ви збираєтеся використовувати цей оператор багато, ви можете сформувати його ще для допомоги планувальнику запитів ( наприклад, Losthorse, запропонованому в коментарі ). Для початку ви можете додати пункти COMMUTATORта NEGATORпункти для оптимізатора запитів. Замініть CREATE OPERATORзверху цим:

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

І додайте:

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

Але додаткові пропозиції не допоможуть у випадку використання, а звичайні індекси все ще не використовуються. Домогтися цього набагато складніше. (Я не намагався.) Докладніше прочитайте розділ "Інформація про оптимізацію оператора" .

Тестовий випадок

Тестовий випадок у питанні може досягти успіху лише за умови, що всі значення масиву однакові. Для масиву у питанні ( '{null,A}'::text[]) результат завжди є ІСТИНИМ. Це призначено? Я додав ще один тест для "ВІДДАЛЕ ВСІХ":

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

Альтернатива зі стандартними операторами

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

може майже бути переведені

foo = ALL (test_arr) IS NOT TRUE

foo = ALL (test_arr) врожайність ...

TRUE .. якщо всі елементи є foo
FALSE.. якщо будь-який NOT NULLелемент є <> foo
NULL .. якщо хоча б один елемент IS NULLі жоден елемент не є<> foo

Отже, інший кутовий випадок - де
- foo IS NULL
- і test_arr складається з нічого, крім NULLелементів.

Якщо будь-кого можна виключити, ми закінчили. Тому використовуйте простий тест, якщо
- стовпець визначений NOT NULL.
- або ви знаєте, що масив ніколи не є всім NULL.

Ще, тестуйте додатково:

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

Де 'A'і 'B'можуть бути будь-які окремі значення. Пояснення та альтернативи під цим пов’язаним питанням на SO:
Чи масив усіх NULL в PostgreSQL

Знову ж таки, якщо ви знаєте про будь-яке значення, яке не може існувати test_arr, наприклад, порожній рядок '', ви все одно можете спростити:

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

Ось повна тестова матриця для перевірки всіх комбінацій:

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

Це трохи докладніше, ніж рішення АндріяEXCEPT , але істотно швидше.


При створенні групи OPERATOR, якщо COMMUTATORNEGATOR, можливо , з зворотним IS NOT DISTINCT FROMоператором) пункту поставляється? postgresql.org/docs/current/static/xoper-optimization.html
Losthorse

1
@losthorse: я додав трохи вирішення цього питання.
Ервін Брандстетер

Я використовую цей оператор для усунення записів на основі app_status (цілого числа), як це app_status <!> any(array[3,6]). На жаль, це не впливає на записи. Це працює з цілими числами?
М. Хабіб

@ M.Habib: Будь ласка, поставте своє запитання як нове запитання . (З усіма релевантними деталями!) Ви завжди можете посилатись на цей контекст для контексту - і залиште тут коментар, щоб повернутись назад.
Ервін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.