Можно ли как-нибудь объединить «ОТЛИЧИЕ ОТ» с ЛЮБОЙ или ВСЕЙ?

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']);
                                   ^
Джек говорит, попробуйте topanswers.xyz
источник

Ответы:

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массив «in», но и nullin z.

Андрей М
источник
13

Рассматривая это как проблему грамматики, ANYопределяется как (в сравнениях строк и массивов ):

оператор выражения 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
Отличный, проницательный ответ.
Эрвин Брандштеттер
Это определенно намного лучше предложенного мной обходного пути, особенно с улучшением @ Erwin.
Андрей М
Этот ответ и подсказки @ Эрвина действительно превосходны. Я принимаю Андрея, но это всего лишь случай личных предпочтений: я уверен, что многие предпочтут вашу элегантность.
Джек говорит, попробуйте topanswers.xyz
@JackDouglas: я добавил альтернативное решение со стандартными операторами.
Эрвин Брандштеттер
Это прискорбно ... для всех намерений и целей, не должен ли IS DISTINCT FROMбыть оператор? Похоже, просто техническое ограничение синтаксического анализатора, а не семантическая проблема.
Энди
10

оператор

Это основано на умном операторе @ Дэниела .
Находясь в этом, создайте комбо функции / оператора, используя полиморфные типы . Тогда это работает для любого типа - так же, как конструкция.
И сделать функцию 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
);

Быстрый поиск по Symbolhound оказался пустым, поэтому оператор <!>, похоже, не используется ни в одном из модулей.

Если вы собираетесь многократно использовать этот оператор, вы можете еще немного его уточнить, чтобы помочь планировщику запросов ( как, например, в примечании ). Для начала, вы можете добавить 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 = <!>
);

Но дополнительные пункты не помогут с вариантом использования под рукой, и простые индексы все равно не будут использоваться. Это гораздо сложнее, чтобы достичь этого. (Я не пробовал.) Подробности читайте в главе «Информация по оптимизации оператора» в руководстве.

Прецедент

Тестовый пример в вопросе может быть успешным, только если все значения в массиве идентичны. Для массива в question ( '{null,A}'::text[]) результат всегда TRUE. Это предназначено? Я добавил еще один тест для "ОТЛИЧАЕТСЯ ОТ ВСЕХ":

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элементов.

Если любой из них может быть исключен, мы сделали. Поэтому используйте простой тест if
- столбец определен NOT 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]). К сожалению, это не влияет на записи. Это работает с целыми числами?
М. Хабиб
@ М.Хабиб: Пожалуйста, задайте свой вопрос как новый вопрос . (Со всеми соответствующими деталями!) Вы всегда можете связаться с этим для контекста - и оставить комментарий здесь, чтобы вернуться.
Эрвин Брандштеттер,