2012-01-10 10 views
2

私は取り除くために、そして、私は現在、これを実行していることをやろうとしている重複するレコードがたくさんあります、すべてのアクティブな調査を見つけこのコードブロックを高速化するにはどうすればよいですか?

Survey.active.each do |survey| 
    survey.response_sets.completed.each do |set| 
    answer_ids = [] 
    set.responses.each do |r| 
     if r.answer.blank? 
     r.destroy 
     else 
     if answer_ids.include? r.answer_id 
      r.destroy 
     else 
      answer_ids << r.answer_id 
     end 
     end 
    end 
    end 
end 

を各調査の応答セットを取得します、各応答セットの個々の応答を入力します。

次に、応答セット内の別の応答のためにanswer_idが存在する場合に、応答が重複であるかどうかを調べます。与えられた応答セット内には、与えられたanswer_idに対して1つの応答のみが存在することができる。したがって、重複がある場合、重複を破棄します。

数十万行を超えると、が非常に遅くなります。

どのように処理速度を上げることができますか?

Survey.active 
SELECT "surveys".* FROM "surveys" WHERE "surveys"."active" = 't' 

survey.response_sets.completed 
SELECT "response_sets".* FROM "response_sets" WHERE ("response_sets".survey_id = 12345) AND (completed_at IS NOT NULL) 

set.responses 
SELECT "responses".* FROM "responses" WHERE ("responses".response_set_id = 54321) 

私はRailsの3.0.6とPostgreSQLを実行している:ここで

は、SQLは、それらのそれぞれを求めています。

+1

一般的なSQLのヒントは、本当にすべてのクエリで 'SELECT *'を実行する必要があるのでしょうか?残念ながら、私はRubyやRoRを知っていません。( –

+0

あなたは配列ではなく既に見たanswer_idsを追跡するためにハッシュを試してみることができます。また、いくつかのインクルードを追加してfind_eachを使うこともできます。 (もしそうでなければ、あなたは以前にramに常駐していたオブジェクトを保持しています)明らかに、適切なすべてのカラムにインデックスが付いていることを確認してくださいr.answer.blank?を 'rに変更する。 answer_id.blank'はたくさんのクエリを保存します(ただし、外部キーを持たない場合は、 'dangling' answer_idsをキャッチしません) –

+1

トランザクション内でスマッシュ全体をラップしようとします( 'Survey.transaction do' ... Postgresqlをもっと速くすることができます。また、psqlを使ってpostgresをrails/activerecordをバイパスしてクリーンアップに直接送ることができるかどうかを確認してください。 –

答えて

2

あなたは間違った角度からこれを攻撃している可能性があります。最初に悪いデータをデータベースに入れないでください。私はあなたのデータベースモデルがどのように見えるのかは分かりませんが、モデルのいくつかのバリデーションによって、このようにデータベースをきれいにする必要がないかもしれません。本当に大きなデータセットをRailsにロードするのは苦痛で、それは本当に遅く、メモリが空いています。

# maybe something like this? 
class Responses < ActiveRecord::Base 
    validates_uniqueness_of :answer_id, :scope => :id 
end 

バッチ先端(追加)

ActiveRecordのは、本当に大きな結果セットでうまく動作しません。 will_paginateや類似のものがあれば、完全なデータセットを簡単にループすることができます。

+1

重複を引き起こしていた問題を修正しました。私は間違いなくここに直角。 :) – Shpigford

+0

OK、修正を一度探していましたが、おそらく実行を遅くしているメモリのオーバーヘッドを最小限に抑えるためのすばやいソリューションが追加されました。 – sunkencity

1

これを1回だけ実行する必要がある場合は、何が問題ですか?それが「毎日」の仕事であれば、それを処理するためにバックグラウンドジョブを使用することができます(遅れたジョブまたはresque gemを見てください)。

しかし、あなたができることはいくつかあります。範囲内の答えはincludingですか?あるいは大規模なデータセットを扱う場合速くする必要がありますARモデルのfind_eachと呼ばれる方法もありますSurvey.active.includes(:answers)

を使用しています。

希望に役立ちます。

1

WHERE句で使用しているフィールドがインデックスに登録されていることを確認してください。

純粋なSQLの問題ではなく、Railsの1(全く同じように、私は:) RailsのN00B午前)です

、しかし...

response_sets.survey_id, 
response_sets.completed_at 
responses.response_set_id 

は間違いなくすべてが君ならば、それらに設定されたインデックスを持つ必要があります数百行のデータセットについて話しています。

+0

ええ、私はかなりのインデックスをカバーしていると思う。 – Shpigford

+1

私が考えることができる他の唯一のものは、これらのクエリに関するいくつかのタイミングメトリクスです。ペナルティが最大の時間がどこにあるのかを確認するだけです。 Postgresクライアントでいくつかのクエリを手作業で実行して、データベースからどのようなパフォーマンスが得られているかを確認することはできません。少なくとも、大きなヒットが取り込まれているかどうかはわかりますRailsのコードやSQLを使って、SO型にもう少し詳しい情報を与えるかもしれません...! – existentialist

1

私はこれがRubyの各レコードを反復する代わりに、SQLを使って解決するのが最も良い問題だと思います。あなたが運転

#Delete responses that do not have a corresponding answer 
#AND delete responses that have a duplicate answer_id keeping only one response for each answer_id 
ActiveRecord::Base.execute <<-SQL 
    DELETE FROM responses 
    WHERE (responses.answer_id IS NULL) OR 
    (
    responses.id NOT IN (
     -- build a list of the response ids you want to keep 
     SELECT responses.id 
     FROM responses 
     INNER LEFT JOIN 
     (
     -- get a list of responses with a unique answer id 
     SELECT DISTINCT responses.answer_id 
     FROM responses 
    ) 
     -- join responses to itself on the unique list of answer ids 
     -- keeping only a single record for each answer id 
     as answer_ids ON responses.answer_id = answer_ids.answer_id 
    ) 
) 
SQL 

NOTEのこのタイプを行う必要があるとき

SQLは、まだ強力なツールです:私はこれをテストしていないと私は最初のテスト環境に対してそれを実行することをお勧めします。

0

あなたの結果をanswer_idでグループ化し、COUNT(*)> 1のもののみを選択してください。

それはこのような何かに行くことができます:

duplicate_sets.group_by(:answer_id) {|...| 

これはあなたによってグループ化されたすべてのIDの配列を与える。そして、すべてが、最初の1これらすべてのanswer_idsを通過し、破壊する

survey.response_sets.completed.all(
    :group_by => "answer_id", 
    :select => "id, answer_id, COUNT(*) AS count_duplicates", 
    :conditions => "count_duplicates > 1") 

をそれぞれの答えID。最初の要素を取り除き、残りの要素を破壊してください。

私はあなたのモデルについてはわからないので、私はあなたに残ります。しかし、実際に作業する前にデータを準備する方法を手がかりにする必要があります。私のコードではanswer_id IS NULLのケースも選択されていませんが、これらは2回目の実行で見つけやすいはずです。

準備中にデータが変更されず、削除が重複しないように、すべてをトランザクションにラップしてください。

関連する問題