ChatGPT解决这个技术问题 Extra ChatGPT

Update one column to value of another in Rails migration

I have a table in a Rails app with hundreds of thousands of records, and they only have a created_at timestamp. I'm adding the ability to edit these records, so I want to add an updated_at timestamp to the table. In my migration to add the column, I want to update all rows to have the new updated_at match the old created_at, since that's the default for newly created rows in Rails. I could do a find(:all) and iterate through the records, but that would take hours because of the size of the table. What I really want to do is:

UPDATE table_name SET updated_at = created_at;

Is there a nicer way to do that in a Rails migration using ActiveRecord rather than executing raw SQL?


D
Deepak Mahakale

I would create a migration

rails g migration set_updated_at_values

and inside it write something like:

class SetUpdatedAt < ActiveRecord::Migration
  def self.up
    Yourmodel.update_all("updated_at=created_at")
  end

  def self.down
  end
end

This way you achieve two things

this is a repeatable process, with each possible deploy (where needed) it is executed

this is efficient. I can't think of a more rubyesque solution (that is as efficient).

Note: you could also run raw sql inside a migration, if the query gets too hard to write using activerecord. Just write the following:

Yourmodel.connection.execute("update your_models set ... <complicated query> ...")

+1 - I recently had to do exactly this and used SQL over an ActiveRecord. It's as fast as it can get.
Yourmodel.update_all 'update_at=created_at' is nicer, no? It works on a scope too.
According to the Rails guide: "the database schema should be unchanged if you do an up followed by a down". So consider def change only instead.
@EliadL a few remarks: 1) we are not changing the schema, just the contents of the database. And 2) at the time this answer was written, the change method did not yet exist, but in this case I still prefer to use explicit up and down to be more explicit (if you would want to control what the down should do).
I think your down method should put things back to how they were before the up method was run, whether it's structure or content of the database. If you can't figure out how to do that then leave the down method out and make it not able to roll back
G
Greg Dan

You can use update_all which works very similar to raw SQL. That's all options you have.

BTW personally I do not pay that much attention to migrations. Sometimes raw SQL is really best solution. Generally migrations code isn't reused. This is one time action so I don't bother about code purity.


That depends on your deployment needs. I really like to use migrations, because they allow to repeatable deploy on existing platforms and get the same result. We have several stages of deploying: dev, test, qa/acceptance, a proof of concept platform (for testing clients), a production platform: we need to be able to migrate existing data to the newly deployed version without a fault. Adding a column and making sure the data is ok is in our case NOT a one time action.
I write about using update_all inside migration file :-) You can also execute raw SQL inside migration file. However update_all is a little bit more elegant. Both will perform exactly the same.
It's usually a smart idea to declare the model in the migration, since this will prevent problems if the original model is redefined later on. Just found this article which explains everything quite nicely: complicated-simplicity.com/2010/05/…
With update_all i have no idea how to set a value of column to that of another, as the OP requested. Please demonstrate.
D
Deepak Mahakale

As gregdan wrote, you can use update_all. You can do something like this:

Model.where(...).update_all('updated_at = created_at')

The first portion is your typical set of conditions. The last portion says how to make the assignments. This will yield an UPDATE statement, at least in Rails 4.


This in 4.2 generates SET'posts'.'email' = 'options', the options is a literal string
Confirming this tip doesn't work for me too. Unsure that it's completely wrong solution. Don't donwvote @martin answer
Here is the output from the Rails console: User.update_all('updated_at = created_at') SQL (0.4ms) UPDATE "users" SET updated_at = created_at
S
Sarwan Kumar

You can directly run following command to your rails console ActiveRecord::Base.connection.execute("UPDATE TABLE_NAME SET COL2 = COL1")

For example: I want to update sku of my items table with remote_id of items tables. the command will be as following:
ActiveRecord::Base.connection.execute("UPDATE items SET sku = remote_id")


Actually this is far most "history" secure way, because in future (when some will run migrations, the model Yourmodel can be already deleted. Try to avoid using models in migrations.
P
Pavel Dusanek

Do not use application models in migrations unless you redefine them inside migration. If you use application model tahat you later change or delete your migration might fail.

Of course you can also use full power of SQL inside migrations.

Read https://makandracards.com/makandra/15575-how-to-write-complex-migrations-in-rails


W
Wilson Varghese

This is a General way of solving, without the need for Writing Query, as queries are subjected to risk.

  class Demo < ActiveRecord::Migration
    def change
     add_column :events, :time_zone, :string
     Test.all.each do |p|
       p.update_attributes(time_zone: p.check.last.time_zone)
     end
     remove_column :sessions, :time_zone
    end
  end

Please explain: why are queries subjected to risk? If you are changing deployment to a different database?
M
Magdalena

You can also add updated_at column and update its values in one migration:

class AddUpdatedAtToTableName < ActiveRecord::Migration
  def change
    add_column :table_name, :updated_at, :datetime

    reversible do |dir|
      dir.up do
        update "UPDATE table_name SET updated_at=created_at"
      end
    end
  end
end

I consider this bad practice: I prefer to separate my migrations into 1) schema changes and 2) keep data changes separate. For clarity.
D
Deepak Mahakale

As a one time operation, I would just do it in the rails console. Will it really take hours? Maybe if there are millions of records…

records = ModelName.all; records do |r|; r.update_attributes(:updated_at => r.created_at); r.save!; end;`

That's essentially what I tried first, but because there are hundreds of thousands of records that need to be changed, that will take hours (days?).
When I tested it it was going at about 50 records a second on my developer machine (not a server).
Always avoid iteration if possible, avoid using 'all' which loads every record into RAM at once, and since update_attributes already does a save automatically, the additional call to save! will make the entire operation take twice as long.