loading...

How to add columns with a default value to large tables

lucasprag profile image Lucas Arantes Originally published at grokblog.io Updated on ・2 min read

If you worked with a large database you know that you can't just run an alter table to add a column with a default value, maybe you tried it and it failed -- like me awhile ago 🤕 -- but what if you really need to do that? That's what I'm about to tell you in these four simple steps:

1. Add the column with no default

class AddCurrencyToAccounts < ActiveRecord::Migration[5.2]
  def change
    add_column :accounts, :currency, :string
  end
end
Enter fullscreen mode Exit fullscreen mode

This is going to run this SQL:

ALTER TABLE "accounts" ADD "currency" character varying
Enter fullscreen mode Exit fullscreen mode

2. When creating new records for that table, set the desired default value using your code

There are tons of ways of accomplishing it and it really depends on how you create your records, but this is good enough for the example:

account.currency ||= "USD"
Enter fullscreen mode Exit fullscreen mode

3. Backfill all your table rows with the desired default value

If you have a large database you also probably already have a way of running code for all your records asynchronously to finish it really fast -- If not let me know -- but if you don't have it, a .find_each or a .find_in_batches would probably do the trick:

Account.where(currency: nil).find_each do |account|
  # .update_attributes doesn't touch updated_at
  account.update_attributes(currency: "USD")
end
Enter fullscreen mode Exit fullscreen mode

Why not just run Account.where(currency: nil).update_all(currency: "USD") ?

Because this ☝️ is going to lock your table while your database updates all your rows, that's why we need to update individual records so the database locking only happens per row.

4. Finally, add the default value to your column 🎉

class AddDefaultToCurrencyOnAccounts < ActiveRecord::Migration[5.2]
  def change
    change_column :accounts, :currency, :string, default: "USD", null: false
  end
end
Enter fullscreen mode Exit fullscreen mode

This is going to run this SQL

ALTER TABLE "accounts"
ALTER COLUMN "currency" TYPE character varying,
ALTER COLUMN "currency" SET DEFAULT 'USD',
ALTER "currency" SET NOT NULL
Enter fullscreen mode Exit fullscreen mode

After that you can remove your code that sets the default value when creating your records from step #2 and it's done 🎉

Discussion

pic
Editor guide