Как я могу сделать этот набор запросов Rails 3.1 более эффективным? - PullRequest
0 голосов
/ 16 декабря 2011

У меня есть модель Sleep, экземпляры которой belongs_to экземпляр модели Person.Я хочу перейти к вычислению статистики в фоновом потоке.Люди сами сообщают свои данные и могут пропустить несколько дней.

Я создал модель Sleepstat и планирую рассчитать некоторую статистику для каждого дня, для которого имеется один или несколько зарегистрированных экземпляров Sleep.Люди могут вернуться и отредактировать свои данные позже, поэтому в этой фоновой задаче я хочу просканировать существующие экземпляры Sleepstat, чтобы определить состояние флага needs_updating.

Если кто-то создает запись Sleep в день, для которого нет существующего Sleepstat, тогда я хочу, чтобы фоновая задача создала Sleepstat и вычислила статистику за этот день.Если кто-то добавляет дополнительную запись Sleep к дню, в котором есть существующий Sleepstat, тогда я хочу пометить Sleepstat как нужное обновление и обновить его новыми данными, чтобы поддерживать статистику в актуальном состоянии.

Я думал сделать следующее:

  1. Запустить запрос, чтобы вернуть все Sleep записи, принадлежащие рассматриваемому Person.Для этого я использовал этот запрос, который работает так, как я и ожидал:

    all_sleeps = Sleep.select('start_time,end_time,multiday,time_zone,in_progress').where(:person_id => self.id)
    
  2. Создать массив уникальных start_time дат:

    days_recorded = []
      for sleep in all_sleeps
        days_recorded.push sleep.start_time.to_date
      end
      days_recorded = days_recorded.uniq 
    
  3. Для каждого из days_recorded посмотрите, существует ли Sleepstat.Если нет, создайте его и рассчитайте статистику.Если это так, проверьте, является ли оно needs_updating.Если это так, рассчитайте статистику.Если нет, то перейдите к следующему элементу в days_recorded.

    days_recorded.each do |d|
        stat = Sleepstat.where(:date => d).first
    
        if stat.nil?
          # No record, so create one because we have data for that day and calculate stats
          ...
    
        else
          # There is a record. Evaluate whether it needs to be updated
    
          if stat.needs_updating?
            # Update the Sleepstat
            ...
    
          end
    
        end
    end
    

Этот подход приводит к множеству независимых запросов:

Sleepstat Load (0.2ms)  SELECT "sleepstats".* FROM "sleepstats" WHERE "sleepstats"."date" = '2011-12-10'
Sleepstat Load (0.2ms)  SELECT "sleepstats".* FROM "sleepstats" WHERE "sleepstats"."date" = '2011-12-11'
Sleepstat Load (0.2ms)  SELECT "sleepstats".* FROM "sleepstats" WHERE "sleepstats"."date" = '2011-12-12'
Sleepstat Load (0.2ms)  SELECT "sleepstats".* FROM "sleepstats" WHERE "sleepstats"."date" = '2011-12-13'

MyИдея заключалась в том, чтобы попытаться сначала захватить все Sleepstat с помощью запроса, подобного:

existing_stats = Sleepstat.where(:date => days_recorded)

, а затем выполнить итерацию по ним на шаге 3. Моя попытка выглядела так:

    existing_stats = Sleepstat.where(:date => days_recorded)

    days_recorded.each do |d|
        stat = existing_stats.where(:date => d)

        if stat.nil? || stat.length == 0
          # No record, so create one because we have data for that day and calculate stats
          ...

        else
          # There is a record. Evaluate whether it needs to be updated

          if stat.needs_updating?
            # Update the Sleepstat
            ...

          end

        end
    end

Это привело к большому количеству более сложных отдельных запросов:

Sleepstat Load (0.5ms)  SELECT "sleepstats".* FROM "sleepstats" WHERE "sleepstats"."date" IN ('2011-12-07', '2011-12-06', '2011-12-08', '2011-12-09', '2011-12-10', '2011-12-11', '2011-12-12', '2011-12-13', '2011-12-14', '2011-12-15') AND "sleepstats"."date" = '2011-12-10'
Sleepstat Load (0.9ms)  SELECT "sleepstats".* FROM "sleepstats" WHERE "sleepstats"."date" IN ('2011-12-07', '2011-12-06', '2011-12-08', '2011-12-09', '2011-12-10', '2011-12-11', '2011-12-12', '2011-12-13', '2011-12-14', '2011-12-15') AND "sleepstats"."date" = '2011-12-11'
Sleepstat Load (0.6ms)  SELECT "sleepstats".* FROM "sleepstats" WHERE "sleepstats"."date" IN ('2011-12-07', '2011-12-06', '2011-12-08', '2011-12-09', '2011-12-10', '2011-12-11', '2011-12-12', '2011-12-13', '2011-12-14', '2011-12-15') AND "sleepstats"."date" = '2011-12-12'
Sleepstat Load (0.4ms)  SELECT "sleepstats".* FROM "sleepstats" WHERE "sleepstats"."date" IN ('2011-12-07', '2011-12-06', '2011-12-08', '2011-12-09', '2011-12-10', '2011-12-11', '2011-12-12', '2011-12-13', '2011-12-14', '2011-12-15') AND "sleepstats"."date" = '2011-12-13'

Как повысить эффективность этого процесса, чтобы я не обращался к базе данных так много раз?

1 Ответ

1 голос
/ 16 декабря 2011

если ваша статистика не слишком тяжелая для вычисления, вам лучше использовать обратный вызов для вычисления статистики каждый раз, когда запись создается или обновляется:

Class Sleep < ActiveRecord::Base
  before_save :create_or_update_stats

  def create_or_update_stats
    # avoid calculation if record is new or if nothing changed
    return unless ( self.new_record? || self.changed? ) 

    date = self.start_time.to_date
    stats  = Sleepstat.find_or_create_by_date( date )
    sleeps = Sleep.where( date: date )

    # now calculate the stats and save them.
  end
end

РЕДАКТИРОВАТЬ Конечно, вам придется добавить обратный вызов также уничтожить.Вы получаете дух.

дополнительные советы:

  • не используйте синтаксис for.Он внутренне вызывает each, так зачем?
  • это делает то же самое, что вы делаете в 2):

    all_sleeps.map{|s| s.start_date.to_date }.uniq
    # or even this
    all_sleeps.map( &:start_date ).map( &:to_date ).uniq
    
  • , чтобы проверить, является ли отношение пустым, используйте stat.exists? вместо вашегооператор

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