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 UI/UX Design

UI/UX Design by makandra brand

We make sure that your target audience has the best possible experience with your digital product. You get:

  • Design tailored to your audience
  • Proven processes customized to your needs
  • An expert team of experienced designers
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)