web-dev-qa-db-ja.com

Rails移行で(内容を含む)列を別のテーブルに移動するにはどうすればよいですか?

いくつかの列を既存のテーブルから別のテーブルに移動する必要があります。 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

上記は新しい列を作成するだけですが、値は空のままです...

テーブルを手動で更新するためにデータベースにログインすることは避けたいです。

プログラムで列の値を移動する方法がある場合、パフォーマンスの特性は何ですか?行ごとに実行しますか、それとも一括で更新する方法はありますか?

29
Eero

私はこの移行を使用することになりました(テスト済み、動作し、正常にロールバックします):

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

大規模なデータベースでの行ごとの更新を回避できるので、私はそれが好きです。

35
Eero

これは、3つの移行、または3つの部分の移行として行います。最初の部分は列の追加、2番目の部分はデータのコピー、3番目の部分は列の削除です。

真ん中のステップがあなたが求めているもののようです。これは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"

これらは両方とも、プロファイルとユーザーの関連付けについて何かを想定しています。これは、間違っていると想定した場合に調整できます。

6
andrewmitchell

次の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
4
Ollie Bennett

この構文は、Postgresのそれ以降のバージョンでは機能しません。 Postges 9.4.5の@Eeroの最新の回答については、次のようにしてください。

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
4
pthamm

pdate_all および/または find_each を使用して、ハードコードされたデータベース固有のSQLステートメントを回避できます。

2
Matt Scilipoti

これは私が私のプロジェクトでしたことです:-

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
0
Umesh Malhotra