Массив всех NULL в PostgreSQL

Существует ли выражение, возвращающее TRUE, если все элементы массива PostgreSQL равны NULL?

Если бы это было значение, отличное от NULL, я мог бы, конечно, использовать что-то вроде:

SELECT 4 = ALL (ARRAY[4,5]::integer[]);

Однако я хочу выполнить операцию ALL с тестом IS NULL, а не с тестом = 4. Я не думаю, что для этого существует синтаксис ALL, а семантика вокруг NULL усугубляется массивами, которые я сам не смог придумать в форме, которая бы достигала этого. Отсюда мой вопрос о переполнении стека. ;-)

Я знаю, что мог бы написать функцию в pl/sql или pl/pgsql, которая делает это, но я хотел бы посмотреть, есть ли прямое выражение, прежде чем прибегать к этому.


person Edmund    schedule 27.07.2011    source источник
comment
Возможно, generate_series() сможет помочь.   -  person ypercubeᵀᴹ    schedule 28.07.2011
comment
Да, я подозреваю, что мог бы, хотя выражение «закрытой формы» (т.е. без подзапросов!) было бы предпочтительнее. Я держусь, чтобы посмотреть, может ли кто-нибудь подумать об одном.   -  person Edmund    schedule 28.07.2011


Ответы (5)


Я думаю, что получил самый короткий ответ, сохранив при этом конструкцию 4 = ALL (ARRAY[4,5]::integer[]);:

Живой тест: https://www.db-fiddle.com/f/6DuB1N4FdcvZdxKiHczu5y/1

select
y, true = ALL (select unnest(z) is null)
from x
person Michael Buen    schedule 28.07.2011
comment
Этот ответ мне нравится больше, чем другой, поэтому я написал об этом в блоге ;-) anicehumble.com/2011/07/postgresql-unnest-function-do-many.html - person Michael Buen; 28.07.2011
comment
Я думаю, что технически это все еще подзапрос, но он достаточно короткий. Спасибо. - person Edmund; 28.07.2011
comment
Я мог бы представить, что разработчики ядра Postgresql могут использовать какой-то специализированный алгоритм, если источник строки запроса ALL/ANY/SOME исходит из невложенного массива, то есть он не получит тот же план выполнения типичного подзапроса. Подход Array_fill (мой другой ответ) может быть быстрее, поскольку он не разделяет план выполнения подзапроса, похоже, что все операции выполняются в памяти; лучший способ узнать это — профилировать скорость или проверить план выполнения различных подходов. - person Michael Buen; 28.07.2011

1 = ALL(arr) IS NULL AND 2 = ALL(arr) IS NULL

1 и 2 могут быть любыми двумя разными числами.

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

Есть несколько способов. Я собрал тестовый пример, чтобы увидеть, какой из них самый быстрый:

SELECT arr
     , 1 = ALL(arr) IS NULL AND 2 = ALL(arr) IS NULL      AS chk_simpl
     , TRUE = ALL (SELECT unnest(arr) IS NULL)            AS chk_michael
     , (SELECT bool_and(e IS NULL) FROM unnest(arr) e)    AS chk_bool_and
     , NOT EXISTS (SELECT unnest(arr) EXCEPT SELECT null) AS chk_exist
FROM  (
   VALUES
     ('{1,2,NULL,3}'::int[])
   , ('{1,1,1}')
   , ('{2,2,2}')
   , ('{NULL,NULL,NULL}'::int[])
   , ('{}'::int[])
   ) t1(arr);

Второй — из принятого в настоящее время ответа @michael. Столбцы расположены в порядке выполнения выражения. Сначала самый быстрый. Предложенное мной выражение на самом деле во много раз быстрее, чем остальные. Отсюда мой ответ.

Обратите внимание на разные результаты для ввода пустого массива. (Какой ответ вы ожидаете для пустого массива?)

db‹›fiddle здесь — с демонстрацией и тестом производительности< br /> Старый sqlfiddle

Как это работает?

Выражение 1 = ALL(arr) дает

TRUE .. если все элементы 1
FALSE .. если какой-либо элемент <> 1 (любой элемент, который IS NOT NULL)
NULL .. если хотя бы один элемент IS NULL и ни один элемент не <> 1

Итак, если мы знаем один элемент, который не может отображаться, например -1, мы можем упростить его до:

-1 = ALL(arr) IS NULL

Если может появиться любое число, проверьте наличие двух различных чисел. Результат может быть только NULL для обоих, если массив не содержит ничего, кроме NULL. Вуаля.

person Erwin Brandstetter    schedule 07.03.2014
comment
Спасибо, это очень умно. - person Edmund; 08.03.2014
comment
Ваше решение отличается от других тем, что возвращает false для пустых массивов. Решение от @ezequiel-tolnay array_remove(arr, NULL) = '{}' работает почти так же быстро, но возвращает true для пустого массива. - person Jolbas; 14.05.2021
comment
Хорошая точка зрения. Я добавил пустой массив в тестовый пример выше. Что должно быть возвращено для пустого массива при задании вопроса Is array all NULLs? я бы сказал false - или, может быть, null? Но я думаю, это подлежит обсуждению. - person Erwin Brandstetter; 15.05.2021
comment
Однозначно спорно. Если вместо этого вопрос Is array nothing but NULLs?, я бы предпочел true, а также 1 = ALL('{}') и даже NULL = ALL('{}') дает true - person Jolbas; 16.05.2021
comment
(1 = ALL(arr) AND 2 = ALL(arr)) IS NOT FALSE также может быть альтернативой получению true для пустого массива. - person Jolbas; 16.05.2021

Я не то чтобы горжусь этим, но:

=> select not exists (
    select 1
    from (select all unnest(ARRAY[NULL, NULL, NULL]) is null as x) as dt
    where x = 'f'
);
 ?column? 
----------
 t
(1 row)

=> select not exists (
    select 1
    from (select all unnest(ARRAY[NULL, 11, NULL]) is null as x) as dt
    where x = 'f'
);
 ?column? 
----------
 f
(1 row)

Да, существует множество подзапросов, но, возможно, вы сможете заставить их работать или упростить до чего-то, что будет работать.

person mu is too short    schedule 28.07.2011

Другой способ сделать код короче, использовать КАЖДУЮ агрегатную функцию.

create table x
(
y serial,
z int[]
);

insert into x(z) values(array[null,null,null]::int[])
insert into x(z) values(array[null,7,null]::int[])
insert into x(z) values(array[null,3,4]::int[])
insert into x(z) values(array[null,null,null,null]::int[])


with a as
(
    select y, unnest(z) as b
    from x
)
select y, every(b is null)
from a 
group by y
order by y

Выход:

 y | every
---+-------
 1 | t
 2 | f
 3 | f
 4 | t
(4 rows)

Другой подход, генерирующий значения NULL для сравнения:

select  y, 
    z = 
    (select array_agg(null::int) 
     from generate_series(1, array_upper(z, 1) )) as IsAllNulls
from    x

В соответствии с логикой приведенного выше кода это возвращает true:

SELECT ARRAY[NULL,NULL]::int[] = ARRAY[NULL,NULL]::int[]

Другой подход: используйте array_fill.

select  y, z = array_fill(null::int, array[ array_upper(z, 1) ] )
from    x

Предостережение: конструкция массива и array_fill не симметричны, проверьте их:

select array[5]

-- array[5] here has different meaning from array[5] above
select array_fill(null::int, array[5]) 
person Michael Buen    schedule 28.07.2011

Просто ради разнообразия вариантов я использовал для этого:

select array_remove(ARRAY[null::int, null, null], null) = '{}'

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

NEW.arrvalue := CASE WHEN array_remove(NEW.arrvalue, null) <> '{}' THEN NEW.arrvalue END;
person Ezequiel Tolnay    schedule 02.04.2015
comment
Это хорошо, потому что почти так же быстро, как Erwins, но работает как для значений, так и для null. И он возвращает то же самое, что и 1 = ALL(arr) для пустого массива. - person Jolbas; 18.05.2021