如何在 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 中将 2 个表导出为 CSV [如何选择特定列?]