2012-01-10 3 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, которые помогут вам :( –

+0

вы можете попробовать использовать хэш для отслеживания answer_ids, который вы уже видели, а не массива. Возможно, вы также захотите добавить некоторые дополнения и использовать find_each, а не каждый в самом внешнем цикле (в противном случае вы будете хранить все объекты, которые вы ранее смотрели на резидентные в ram). Очевидно, убедитесь, что у вас есть индексы во всех соответствующих столбцах. Изменение 'r.answer.blank?' на 'r. answer_id.blank' сэкономит много запросов (но если у вас нет внешних ключей, значит, вы не поймаете «болтающиеся» answer_ids –

+1

Попробуйте обернуть весь smash в транзакции ('Survey.transaction do' ... 'end'), что может позволить Postgresql идти быстрее. Также посмотрите, можете ли вы просто использовать psql для непосредственного управления postgres для очистки, минуя рельсы/activerecord. –

ответ

2

Я думаю, что вы можете атаковать это с неправильного угла. Вы никогда не должны допускать попадание плохих данных в базу данных. Я не могу понять, как выглядит ваша модель базы данных, но некоторые проверки в моделях могут помешать вам очистить базу данных, как это. Загрузка действительно больших наборов данных в Rails - это боль, и она очень медленная и голодная.

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

Batch наконечник (добавлено)

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

+0

ОК, пропустил то, что вы искали один раз над исправлением, добавил небольшое небольшое решение для минимизации накладных расходов памяти, что, вероятно, замедляет работу. – sunkencity

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

+1

Единственная другая вещь, которую я могу думать, может стоить того, чтобы сделать некоторые временные метрики по этим запросам, просто чтобы увидеть, где лежит самое большое временное наказание. Это не помешает выполнить некоторые из запросов в клиенте Postgres, чтобы узнать, какую производительность вы получаете от базы данных ... это, по крайней мере, скажет вам, принимаются ли большие хиты в Rails-код или SQL, и может дать SO-типа немного больше, чтобы продолжить ...! – existentialist

1

Я думаю, что это проблема, которую лучше всего решить с помощью SQL вместо повторения каждой записи в рубине.

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) {|...| 

Это даст вам массив всех идентификаторов, сгруппированных по каждый идентификатор ответа. Просто разделите первый элемент, уничтожьте остальные.

Я не уверен в ваших моделях, поэтому оставил вам все остальное. Но он должен дать вам понять, как подготовить свои данные, прежде чем на самом деле работать над ним. Мой код также не выбирает случай answer_id IS NULL, но их следует легко обнаружить во втором прогоне.

Обязательно оберните все в транзакцию, чтобы ваши данные не изменялись во время подготовки и устранения дубликатов.

Смежные вопросы