Reputation: 23
I have a table Products and around 58000 records in it. I run two queries are as follows.
Product.where(description: 'swiss').count
It returns 0 products out of 58000 products. But when I run query below.
Product.where.not(description: 'swiss').count
it returns 2932 products out of 58000 products. I think it should return all 58000 products, because it is the reverse of first query. I did not understand why it returns only 2932 products.
Upvotes: 0
Views: 304
Reputation: 30145
If you have NULL values in your columns, this could happen, because NULL
always compares as NULL
, even to itself, but WHERE
expression must be true to include a result.
'a' = 'a'; True
'a' = 'b'; False
'a' = NULL; Results in NULL (not false!)
NULL = NULL; Results in NULL (not true!)
'a' != 'a'; False
'a' != 'b'; True
'a' != NULL; NULL
e.g. consider the table null_test
containing
id | str
----+-----
1 | <NULL>
2 | a
3 | b
When looking for a column equal to some value, the NULL
is never equal, so this will just return row 2.
SELECT id FROM null_test WHERE str = 'a';
But the same applies looking for a column not equal to some value, the NULL
is never not equal (its NULL), so this will just return row 3.
SELECT id FROM null_test WHERE str != 'a';
Thus the total of your =
and !=
is not all the rows, because you never included the NULL
rows. This is where IS NULL
and IS NOT NULL
come in (they work like you might expect = NULL
and != NULL
to work).
SELECT id FROM null_test WHERE str != 'a' OR str IS NULL;
Now you get rows 1 and 3, making this the opposite of str = 'a'
.
For ActiveRecord
, it treats the Ruby nil
value like the SQL NULL
and creates the IS NULL
checks in certain situations.
NullTest.where(str: 'a')
SELECT * FROM "null_test" WHERE "str" = 'a'
NullTest.where.not(str: 'a')
SELECT * FROM "null_test" WHERE "str" != 'a'
NullTest.where(str: nil)
SELECT * FROM "null_test" WHERE "str" IS NULL
NullTest.where.not(str: nil)
SELECT * FROM "null_test" WHERE "str" IS NOT NULL
NullTest.where(str: nil).or(NullTest.where.not(str: 'a'))
SELECT * FROM "null_test" WHERE "str" IS NULL OR "str" != 'a'
Upvotes: 1
Reputation: 36860
You have likely many records where description
is nil
and those are not included in the not
.
A way to include the 'nil' records as well would be...
Product.where('description <> ? OR description IS NULL', 'swiss')
Or alternatively
Product.where.not(description: 'swiss').or(Product.where(description: nil))
Upvotes: 0