我有很多重复的记录,我正在尝试清除,为此,我正在运行这个:
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。
答案 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
的情况,但这些应该很容易在第二次运行中发现。
确保将所有内容都包装到事务中,以便在准备和重复删除期间不会更改数据。