2012-01-10 41 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只能有一個響應。所以,如果有重複,它會銷燬重複。

過了幾十萬行,就是很慢很慢。

那麼,我該如何加快這個過程呢?

下面是SQL調用每個那些:

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。

+1

只是一個一般的SQL提示,問問自己,你真的需要在所有查詢中執行SELECT *嗎?不幸的是,我不知道任何Ruby或RoR可以幫助你進一步:( – 2012-01-10 17:21:30

+0

你可以嘗試使用散列來跟蹤你已經看到的answer_id而不是數組,你可能還想嘗試添加一些include和使用find_each,而不是每一個都放在最外層的循環中(否則你會保留以前在ram中駐留的對象)。顯然,確保你在所有適當的列上都有索引。將'r.answer.blank?'改爲'r。 answer_id.blank'會節省很多查詢(但是如果你沒有外鍵意味着你不會捕獲'dangling'answer_id – 2012-01-10 17:31:08

+1

嘗試在事務中包裝整個粉碎('Survey.transaction do' ... 'end')。這可以讓Postgresql變得更快。另外,看看你是否可以使用psql直接驅動postgres進行清理,繞過rails/activerecord。 – 2012-01-10 19:06:57

回答

2

我認爲你可能會從錯誤的角度來攻擊它。首先,您絕對不應該將不良數據放入數據庫中。我無法真正瞭解數據庫模型的外觀,但模型中的一些驗證可能會阻止您像這樣清理數據庫。在Rails中加載真正大的數據集是一件痛苦的事,而且它真的很慢並且內存很大。

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

批次尖端(加)

的ActiveRecord並不真正具有大型結果集正常工作。如果你有will_paginate或類似的東西,你可以輕鬆地循環遍歷整個數據集。

(1..Survey.total_pages).each do |p| 
    Survey.paginate(:page => p, :per_page => 30).each do |survey| 
    # your loop but with less memory overhead 
+1

我已經修復了導致重複的問題,所以我肯定是從進攻中受益直角在這裏。 :) – Shpigford 2012-01-10 17:56:02

+0

好吧,錯過了你正在尋找一次修復,添加了一個快速的小解決方案,以儘量減少內存開銷,這可能是減緩運行速度。 – sunkencity 2012-01-10 20:20:34

1

如果您只需要運行一次,那有什麼問題?如果是「日常」任務,則可以使用後臺工作來處理該問題(查看延遲的工作或重置寶石)。

但有幾件事你可以做。你在including範圍內的答案?或使用Survey.active.includes(:answers)

還有一種方法,稱爲find_each AR模型應該更快時處理大型數據集。

希望有所幫助。

1

只是一個想法,在這裏:你確定你在WHERE子句中使用的字段被索引?

這是一個純粹的SQL問題,而不是一個Rails的一個(一樣好,我是一個Rails的n00b :)),但...

response_sets.survey_id, 
response_sets.completed_at 
responses.response_set_id 

絕對應該都對它們設置的索引,如果你」重新談論數百行數據集。

+0

是的,我很確定我已經掌握了索引。 – Shpigford 2012-01-10 19:52:44

+1

我能想到的唯一值得做的事情就是對這些查詢進行一些計時度量,以查看最大的時間處罰所在。 在Postgres客戶端手動執行某些查詢並不會有什麼傷害,看看你從數據庫中得到了什麼樣的性能......至少可以告訴你是否正在使用Rails代碼或SQL,並且可能會讓SO類型繼續下去......! – existentialist 2012-01-10 21:34:13

1

我認爲這是一個使用SQL最好解決的問題,而不是在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 

注意的:我沒有測試過這一點,我建議先運行它針對測試環境。

0

也許按answer_id對結果進行分組並僅選擇COUNT(*)> 1的結果?

它可以去像這樣:

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

然後通過所有這些answer_ids並銷燬所有,但第一個:

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

這會給你通過分組的所有ID數組每個答案ID。只剝去第一個元素,摧毀其餘的元素。

我不確定你的模特,所以我把剩下的留給你。但是在實際操作之前,它應該給你提供如何準備數據的線索。我的代碼也不會選擇answer_id IS NULL的情況,但這些應該很容易在第二次運行中找到。

確保將所有內容都包含在事務中,以便在準備和重複消除過程中不會更改數據。