Как я могу ускорить этот блок кода? - PullRequest
2 голосов
/ 10 января 2012

У меня есть много повторяющихся записей, которые я пытаюсь отсеять, и для этого я сейчас запускаю:

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 ]

2 голосов
/ 10 января 2012

Я думаю, что вы можете атаковать это не с того угла. Вы никогда не должны допускать плохие данные в базу данных. Я не могу понять, как выглядит ваша модель базы данных, но некоторые проверки в моделях могут помешать вам очистить базу данных, как эта. Загрузка действительно больших наборов данных в 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 голос
/ 11 января 2012

Я думаю, что эту проблему лучше всего решить с помощью 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

ПРИМЕЧАНИЕ : я не проверял это и рекомендую сначала запустить его в тестовой среде.

1 голос
/ 10 января 2012

Просто подумайте: вы уверены, что поля, которые вы используете в предложениях WHERE, проиндексированы?

Это проблема SQL, а не Rails (точно также, яRails n00b :)), но ...

response_sets.survey_id, 
response_sets.completed_at 
responses.response_set_id

определенно должны иметь все индексы, если вы говорите о наборах данных из нескольких сотен строк.

1 голос
/ 10 января 2012

Если вам нужно выполнить это только один раз, в чем проблема?Если это «ежедневное» задание, вы можете использовать фоновое задание для этого (посмотрите на отложенное задание или восстановите самоцветы).

Но есть несколько вещей, которые вы могли бы сделать.Вы including ответы в объеме?или используйте Survey.active.includes(:answers)

Существует также метод, называемый find_each для моделей AR, который должен быть быстрее при работе с большими наборами данных.

Надеюсь, что поможет.*

0 голосов
/ 11 января 2012

Может быть, сгруппировать ваши результаты по 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, но его легко обнаружить во втором прогоне.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...