Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

Is there an expression that returns TRUE if all the elements of a PostgreSQL array are NULL?

If it was a value other than NULL, I could of course use something like:

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

However I want to do the ALL operation with an IS NULL test, rather than a = 4 test. I don't think there's an ALL syntax for this, and the semantics around NULL are compounded with arrays I've not myself been able to think of a form that achieves it. Hence my asking Stack Overflow. ;-)

I know I could write a function in pl/sql or pl/pgsql that does this, but I'd like to see if there's a direct expression before resorting to that.

share|improve this question
    
Perhaps generate_series() can help. –  ypercube Jul 27 '11 at 23:36
    
Yes I suspect I could, though a 'closed form' expression (i.e. no subqueries!) would be preferable. I'm holding out to see if anyone can think of one. –  Edmund Jul 27 '11 at 23:49
add comment

4 Answers

up vote 4 down vote accepted

I think I got the shortest answer, while still preserving 4 = ALL (ARRAY[4,5]::integer[]); construct:

select
y, true = ALL (select unnest(z) is null)
from x
share|improve this answer
    
I like this answer than the other one, so I blogged about it ;-) anicehumble.com/2011/07/postgresql-unnest-function-do-many.html –  Michael Buen Jul 28 '11 at 6:24
    
I think that's still technically a subquery but it's short enough. Thanks. –  Edmund Jul 28 '11 at 6:51
    
I could imagine Postgresql core devs could employ some specialized algorithm if the row source of ALL/ANY/SOME query come from unnested array, i.e. it won't get the same execution plan of typical subquery. Array_fill(my other answer) approach could be faster though, as it doesn't share the execution plan of subquery, looks everything is an in-memory operation; the best way to know is to profile the speed or check the execution plan of different approaches. –  Michael Buen Jul 28 '11 at 7:23
add comment
1 = ALL(arr) IS NULL AND 2 = ALL(arr) IS NULL

1 and 2 can be any two distinct numbers.

Alternatives and performance

There are a couple of ways. I assembled a test case to see which is fastest:

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[])
   ) t1(arr);

The second is from the currently accepted answer by @michael. The columns are in order of performance of the expression. Fastest first. My proposed expression is actually many times faster than the rest. Hence my answer.

SQL Fiddle with demo & performance test.

How does it work?

The expression 1 = ALL(arr) yields

TRUE .. if all elements are 1
FALSE .. if any element is <> 1 (any element that IS NOT NULL)
NULL .. if at least one element IS NULL no element is <> 1

So, if we know a single element that cannot show up, like -1, we can simplify to:

-1 = ALL(arr) IS NULL

If any number can show up, check for two distinct numbers. The result can only be NULL for both if the array contains nothing but NULL. Voilá.

share|improve this answer
    
Thank you, that is very clever. –  Edmund Mar 8 at 3:56
add comment

Another approach to make the code shorter, use EVERY aggregate function

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

Output:

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

Another approach, generating NULLs to be used for comparison:

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

Underlying logic of the code above, this returns true:

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

Another approach, use array_fill

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

Caveat, array construct and array_fill are not symmetrical though, test these:

select array[5]

-- array[5] here has different meaning from array[5] above
select array_fill(null::int, array[5]) 
share|improve this answer
add comment

I'm not exactly proud of this but:

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

Yes, there are subqueries galore but maybe you can make it work or simplify it into something that will work.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.