如何在 Rails 迁移中将列(包含内容)移动到另一个表?

Posted

技术标签:

【中文标题】如何在 Rails 迁移中将列(包含内容)移动到另一个表?【英文标题】:How do I move a column (with contents) to another table in a Rails migration? 【发布时间】:2011-09-02 09:32:48 【问题描述】:

我需要将一些列从一个现有表移动到另一个。我如何使用 Rails 迁移来做到这一点?

class AddPropertyToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :someprop, :string
    remove_column :profiles, :someprop
  end

  def self.down
    add_column :profiles, :someprop, :string
    remove_column :users, :someprop
  end
end

上面只是创建了新的列,但是值是空的...

我想避免登录数据库手动更新表。

如果有以编程方式移动列值的方法,性能特征是什么?它会逐行更新,还是有办法批量更新?

【问题讨论】:

看this helps 【参考方案1】:

我会将此作为三个迁移或三部分迁移。第一部分是添加列,第二部分是复制数据,第三部分是删除列。

听起来中间步骤就是你要问的,你可以在 ruby​​ 中通过循环所有用户并设置属性来做到这一点,如下所示:

Users.each do |user|
   user.someprop = user.profile.some_prop
   user.save
end 

我不喜欢这种方式,因为它非常慢。我建议像这样执行原始 sql:

execute "UPDATE users u, profiles p SET u.someprop=p.someprop WHERE u.id=p.user_id"

这些都假设您的个人资料/用户关联,如果我假设错误,您可以调整。

【讨论】:

谢谢,我最终使用了“执行”调用。但是将迁移分成三个迁移有什么好处呢?如果执行调用中的语句失败,则整个迁移将停止,那么将它们合二为一真的有任何风险或缺点吗? 我刚刚发现 有一个非常好的理由将其分成三个独立的迁移。 mysql 无法在事务中进行迁移,因此如果它在中途崩溃,您将不得不手动清理混乱。不过 Postgres 用户应该没问题。 "在支持带有更改架构的语句的事务的数据库上(例如 PostgreSQL 或 SQLite3),迁移被包装在一个事务中。如果数据库不支持这一点(例如 MySQL),那么当一个迁移失败,成功的部分将不会回滚。您将不得不回滚手动进行的更改。 (2013 年 2 月 15 日——guides.rubyonrails.org/migrations.html【参考方案2】:

我最终使用了这个迁移(经过测试,它可以工作,并且成功回滚):

class AddPropertyToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :someprop, :string
    execute "UPDATE users u, profiles p SET u.someprop = p.someprop WHERE u.id = p.user_id"
    remove_column :profiles, :someprop
  end

  def self.down
    add_column :profiles, :someprop, :string
    execute "UPDATE profiles p, users u SET p.someprop = u.someprop WHERE p.user_id = u.id"
    remove_column :users, :someprop
  end
end

我喜欢它,因为它避免了对大型数据库的逐行更新。

【讨论】:

这不适用于 (PostgreSQL) 9.4.5。第一个逗号有语法错误。请参阅下面的更新版本 请注意,行为会因表之间的关系而异。例如,如果用户有多个配置文件,则在迁移后,每个具有匹配 id 的配置文件都将获得 :someprop 集。如果有一些用户没有配置文件, :someprop 数据将会丢失。在某些用例中,这很好,只是需要注意。 如何将列移动到新创建的表中?例如我想将 :someprop 移动到配置文件,但配置文件还不存在? 聚会有点晚了......但我的 2 美分:1) 很好的解决方案。 2) 这里有一个需要注意的陷阱:pedro.herokuapp.com/past/2011/7/13/…。它实际上表明,从现有表中删除列时,您可能需要分两步进行部署。【参考方案3】:

您可以使用 update_all 和/或 find_each 避免硬编码的数据库特定 sql 语句

【讨论】:

【参考方案4】:

对我(postgreSQL 9.1)来说,RAW SQL 不起作用。我已经改了:

" UPDATE users u
  SET someprop = (SELECT p.someprop
                  FROM profiles p
                  WHERE u.id = p.user_id );"

【讨论】:

您需要在该语句的末尾添加一个分号【参考方案5】:

该语法不适用于更高版本的 Postgres。对于 @Eero 对 Postges 9.4.5 的更新答案,请执行以下操作:

class AddPropertyToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :someprop, :string
    execute "UPDATE users u SET someprop = (SELECT p.someprop FROM profiles p WHERE u.id = p.user_id);"
    remove_column :profiles, :someprop
  end

  def self.down
    add_column :profiles, :someprop, :string
    execute "UPDATE profiles p SET someprop = (SELECT u.someprop FROM users u WHERE p.user_id = u.id);"
    remove_column :users, :someprop
  end
end

【讨论】:

不用子查询也可以实现;见my answer。【参考方案6】:

以下UPDATE 语法适用于最近的 Postgres 版本并避免子查询:

class MoveSomePropertyToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :some_property, :string
    execute "UPDATE users u SET some_property = p.some_property FROM profiles p WHERE u.id = p.user_id;"
    remove_column :profiles, :some_property
  end

  def self.down
    add_column :profiles, :some_property, :string
    execute "UPDATE profiles p SET some_property = u.some_property FROM users u WHERE p.user_id = u.id;"
    remove_column :users, :some_property
  end
end

【讨论】:

注意,这与accepted answer 非常相似,但仅将UPDATE users u, profiles p 替换为UPDATE users u,以避免在以后的Postgres 版本中出现语法错误。【参考方案7】:

这是我在项目中所做的:-

class MoveColumnDataToUsersTable < ActiveRecord::Migration[5.1]
  def up
    add_column :users, :someprop, :string
    User.find_each do |u|
        Profile.create!(user_id: u.id, someprop: someprop)
    end
    remove_column :profiles, :someprop
  end

  def down
    add_column :profiles, :someprop, :someprop_data_type
    Profile.find_each do |p|
      User.find_by(id: p.user_id).update_columns(someprop: p.someprop)   
    end
    Profile.destroy_all
  end
end

【讨论】:

以上是关于如何在 Rails 迁移中将列(包含内容)移动到另一个表?的主要内容,如果未能解决你的问题,请参考以下文章

Rails 3 迁移:添加参考列?

在 Rails 中将 2 个表导出为 CSV [如何选择特定列?]

如何在 Rails 中将对象从一个控制器传递到另一个控制器?

带有长文本的 Rails 3 迁移

如何在pyqt5中将按钮移动到另一个框架?

如何在Mysql大表中将数据从表移动到另一个表