【问题标题】:How do I move a column (with contents) to another table in a Rails migration?如何在 Rails 迁移中将列(包含内容)移动到另一个表?
【发布时间】: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

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

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

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

【问题讨论】:

标签: ruby-on-rails data-migration rails-migrations


【解决方案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
    

    【讨论】:

      猜你喜欢
      • 2015-03-21
      • 2022-07-28
      • 1970-01-01
      • 2022-07-29
      • 1970-01-01
      • 1970-01-01
      • 2014-10-01
      • 2018-07-02
      • 1970-01-01
      相关资源
      最近更新 更多