我怎样才能加快这段代码的速度?

时间:2012-01-10 17:16:35

标签: sql ruby-on-rails ruby ruby-on-rails-3

我有很多重复的记录,我正在尝试清除,为此,我正在运行这个:

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。

5 个答案:

答案 0 :(得分: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 :(得分:1)

如果你只需要运行一次,那有什么问题?如果它是“每日”任务,您可以使用后台作业来处理(查看延迟的作业或重新发送宝石)。

但是你可以做几件事。你是including范围内的答案吗?或使用Survey.active.includes(:answers)

对于AR模型,还有一种名为find_each的方法,在处理大型数据集时应该更快。

希望有所帮助。

答案 2 :(得分:1)

只是想一想,在这里:你确定你在WHERE子句中使用的字段是否被编入索引?

这纯粹是一个SQL问题,而不是Rails一个(同样,我是一个Rails n00b :)),但......

response_sets.survey_id, 
response_sets.completed_at 
responses.response_set_id
如果你在谈论几百行的数据集,

肯定都会设置索引。

答案 3 :(得分: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

注意:我没有对此进行测试,我建议先在测试环境中运行它。

答案 4 :(得分:0)

也许您可以通过answer_id对结果进行分组,并选择只有COUNT(*)&gt;的结果。 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的情况,但这些应该很容易在第二次运行中发现。

确保将所有内容都包装到事务中,以便在准备和重复删除期间不会更改数据。