Recently I faced one scenario with NULL value with NOT IN, I have added the details here about what I learnt.
suppose we have two table Category and Product

Category

id | name
--------
1  | Category 1 
2  | Category 2 
3  | Category 3
4  | Category 4

Product

id | name       | category_id
-----------------------------
1  | Product 1  | 1
2  | Product 2  | 2
3  | Product 3  | 2
4  | Product 4  | 3
5  | Product 5  | 4
6  | Product 6  | NULL

Scenario :-
*I just want to retrieve all the Products excluding Category 1 and Category 3
so I wrote following query :-

Product.joins(:category).where("categories.name NOT IN (?)", ['Category 1', 'Category 3'])

it gives following result.

id | name       | category_id
-----------------------------
2  | Product 2  | 2
3  | Product 3  | 2
5  | Product 5  | 4

It excludes NULL values as well with category_id 1 & 2

  • I was curious why NULL values are excluded, so I was tried excluding NULL value as well.
Product.joins(:category).where("categories.name NOT IN (?)", ['Category 1', 'Category 3', nil])

A NOT IN query will excludes the rows having a NULL value.

Why this happen :-

The reason for the difference in behavior is down to the three valued logic used in SQL. Predicates can evaluate to True, False, or Unknown.

How to overcome this :-

  • You can explicitly include them using IS NULL as below.
    Product.where("products.category_id NOT IN (?) OR products.category_id IS NULL", [1, 3])
    

OR

  • you can use NOT EXISTS
    Product.where("NOT EXISTS (" + Category.where("(name = 'Category 1' OR name = 'Category 3') AND products.category_id = categories.id ").to_sql + ")")
    

O/P -

id | name       | category_id
-----------------------------
2  | Product 2  | 2
3  | Product 3  | 2
5  | Product 5  | 4
6  | Product 6  | NULL

References :-
SQL NOT IN