PostgreSQL: "WHERE NOT <column> = '<value>'" statements do not include NULL values

Posted . Visible to the public. Repeats.

Sometimes we write plain SQL queries in migrations so we don't have to mock ActiveRecord classes. These two migrations do the same:

class Migration1 < ActiveRecord::Migration[5.2]
  class User < ActiveRecord::Base; end

  def up
    add_column :users, :trashed, :boolean
    
    User.update_all(trashed: false)
  end
end

class Migration2 < ActiveRecord::Migration[5.2]
  def up
    add_column :users, :trashed, :boolean

    update("UPDATE users SET trashed = #{quoted_false}")
  end
end

The plain SQL migration is less code, but has a pitfall when combined with a condition. Let's say, we want to trash all users that don't have blue hair, and the database contains users with the hair_color 'blue', 'green, '' and NULL. Then the following migration will be wrong:

class Migration < ActiveRecord::Migration[5.2]
  def up
    add_column :users, :trashed, :boolean

    update("UPDATE users SET trashed = #{quoted_false} WHERE hair_color = 'blue'")
    update("UPDATE users SET trashed = #{quoted_true} WHERE NOT hair_color = 'blue'")
  end
end

Looks right, but it is not! All users where hair_color is NULL will end up with trashed being NULL, too. So we have to add OR hair_color IS NULL to the query:

class Migration < ActiveRecord::Migration[5.2]
  def up
    add_column :users, :trashed, :boolean

    update("UPDATE users SET trashed = #{quoted_false} WHERE hair_color = 'blue'")
    update("UPDATE users SET trashed = #{quoted_true} WHERE NOT hair_color = 'blue' OR hair_color IS NULL")
  end
end

Conclusion:

Is might be more elegant to simply use the Migration1 solution from the first example.

Note

If need to copy data, the plain SQL solution can still be the right choice, e. g. if you want to copy columns:
update("UPDATE users SET birth_name = last_name WHERE NOT hair_color = '' OR hair_color IS NULL")

Jakob Scholz
Last edit
Jakob Scholz
License
Source code in this card is licensed under the MIT License.
Posted by Jakob Scholz to makandra dev (2022-01-21 13:30)