SQL "IN subquery" when subquery can be NULL

SooDesuNe picture SooDesuNe · Dec 20, 2010 · Viewed 13.1k times · Source

I have a query that needs to return results that are NOT matched in a sub query. The sub query can return an empty result, so I need to set a default value (say 0) if the sub query returns an empty set to prevent IN (NULL) which always returns another NULL.

For example

SELECT * FROM example_table WHERE id NOT IN (subquery_that_selects_ids)

subquery_that_selects_ids can return a set of integers, i.e. (1,2,5,6) or an empty set if subquery finds no matching results.

COALESCE doesn't work here, since the sub query will likely return more than one result.

Solutions need to work in SQLite or postgresql. How can I prevent the sub query from returning an empty set?


Everyone is telling me that the query should work as written. And you are all correct. The query is being built by Rails3's AREL, as I was about to post the full query here I noticed that AREL was putting NULL in for an empty set when using array conditions.

I.E. My query in rails looked like:

Object.where("id NOT IN (?)", Object.where(other_conditions).select(:id))

when Object.where(other_conditions) evaluated to [] the ? was being replaced with NULL

So I re-write the query to look like:

Object.where("id NOT IN (" + Object.where(other_conditions).select(:id).to_sql + ")")

Problem solved.

I'm giving credit to @Michael Buen, but also upvoting anyone who told me the query would work as written, since they are correct. Thanks to @OMG Ponies and @Ted Elliott especially!

Answer

Michael Buen picture Michael Buen · Dec 20, 2010

Try:

SELECT * FROM example_table 
WHERE id NOT 
    IN (select x.id from subquery_that_selects_ids as x where x.id is not null)

I think you are complicating it a bit, NOT IN will have rows even there's no rows in subquery. Your query will work without modification. Anyway, if you really desire your subquery to yield row(s) even if the conditions wasn't satisfied, use UNION

SELECT * FROM example_table 
WHERE id NOT 
    IN (select x.id from subquery_that_selects_ids as x 
        where 1 = 0 -- empty set
        union
        select 0)

UNION eliminates duplicate anyway, UNION ALL preserve duplicates