Read more

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

Jakob Scholz
January 21, 2022Software engineer at makandra GmbH

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
Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

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
January 21, 2022Software engineer at makandra GmbH
Posted by Jakob Scholz to makandra dev (2022-01-21 14:30)