Поведение bool_and и bool_or со значениями NULL - PullRequest
1 голос
/ 24 января 2020

Я работаю с функциями агрегирования bool_or и bool_and, чтобы агрегировать некоторые записи и посмотреть, есть ли разногласия по конкретному столбцу. Согласно официальной документации :

bool_and(expression)    true if all input values are true, otherwise false
bool_or(expression)     true if at least one input value is true, otherwise false

Однако этот тестовый запрос:

SELECT bool_or(val),bool_and(val) FROM UNNEST(array[true,NULL]::bool[]) t(val)

Выход true для обоих столбцов.

I думаю, bool_and исключает NULL значения. Есть ли способ использовать встроенные функции агрегирования, чтобы приведенный выше запрос возвращал true и NULL?

Ответы [ 2 ]

2 голосов
/ 24 января 2020

Для того, что мне нужно было сделать, я использовал два дополнительных столбца, которые вы можете увидеть в обновленном тестовом запросе:

SELECT
  bool_or(val),
  bool_and(val),
  bool_or(val IS NULL),
  bool_and(val IS NULL)
FROM UNNEST(array[true,NULL]::bool[]) t(val)

Этого достаточно, чтобы охватить все три значения, которые я могу иметь для этого столбца.

Обратите внимание, что использование COUNT(DISTINCT val) не будет работать, поскольку NULL не включено

Я надеюсь, что это может кому-то помочь!

1 голос
/ 24 января 2020

Да, похоже, NULL входные данные игнорируются этими агрегатами.

Этот тип глупости почти наверняка исходит из стандарта SQL (хотя я не собираюсь платить ) $ 200 , чтобы узнать наверняка). Другие стандартные агрегаты, такие как sum(var), работают таким образом, и кажется, что они, вероятно, просто экстраполируются оттуда, не принимая во внимание разницу между арифметическими c и логическими операциями, когда дело доходит до обработки null значений.

Я не думаю, что есть способ обойти это; Я полагаю, что единственный способ убедить эти функции вернуть NULL - передать им пустой набор данных. (Кроме того, тот, кто настаивал на том, что sum() из нулевых строк должен быть NULL, а не 0, должен быть зафиксирован ...)

К счастью, Postgres бесконечно расширяем и определяет Ваши агрегаты довольно тривиальны:

CREATE FUNCTION boolean_and(boolean, boolean) RETURNS boolean AS
  'SELECT $1 AND $2'
LANGUAGE SQL IMMUTABLE;

CREATE AGGREGATE sensible_bool_and(boolean)
(
  STYPE = boolean,
  INITCOND = true,
  SFUNC = boolean_and,
  -- Optionally, to allow parallelisation:
  COMBINEFUNC = boolean_and, 
  PARALLEL = SAFE
);

Если вам просто нужно это для одноразового запроса, и вы не хотите (или не имеете разрешения) добавить новое определение агрегата в базу данных, вы можете поместить их в временную схему локального соединения, определив и сославшись на них как pg_temp.boolean_and() / pg_temp.sensible_bool_and().
(Если вы используете пул соединений, вы можете удалить их, когда вы готово.)

Обратите внимание, что это примерно в 10 раз медленнее, чем встроенный bool_and() (хотя вряд ли это будет узким местом во многих реальных c случаях использования); SQL boolean значения распределяются в куче и являются неизменяемыми, поэтому boolean_and() необходимо выделять новое значение для каждой итерации, тогда как LANGUAGE C функциям разрешено обновлять накопитель на месте. Если производительность вызывает беспокойство, и вы готовы / можете собрать и развернуть свой собственный модуль C, то (как и в случае большинства внутренних функций) вы можете довольно легко скопировать и вставить bool_and() реализация и настроить его в соответствии с вашими потребностями.

Но все это отчасти излишне, если у вас нет реальной необходимости в этом. На практике я бы, вероятно, вместо решения @ Люка go.

...