0

我有一個Sleep模型,其實例一belongs_to模型Person的實例。我想將統計計算傳遞給後臺線程。人們自行報告他們的數據,可能會跳過一些日子。如何讓這組Rails 3.1查詢更高效?

我創建了一個Sleepstat模型,並計劃計算每天的一些統計信息,其中有一個或多個Sleep記錄的實例。但是,稍後人們可能會返回並編輯其數據,因此在此後臺任務中,我想掃描Sleepstat的現有實例以確定needs_updating標誌的狀態。

如果有人創建的某一天爲這沒有一個現有的Sleepstat一個Sleep記錄,那麼我希望後臺任務,以創建Sleepstat和計算當天的統計數據。如果有人在已存在Sleepstat的某一天添加​​了另一個Sleep記錄,那麼我需要將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' 

我的想法是,試圖抓住所有的Sleepstat S的第一,通過像查詢:

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

如果你的統計不太重來計算,你最好使用callback 每一條記錄被創建或更新的時間來計算統計:當然

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?而不是你的發言

  • ,如果你真的需要做大量計算,你應該看看arel tables,因爲它們允許你跳過記錄實例並只加載你需要的數據,提供了很好的提升
+0

關於`sleeps = Sleep.where(date:date) `...`睡眠`有字段`start_time`,我正在使用它來評估日期,所以我需要將它從時間戳轉換爲日期。但是,它存儲在UTC中。我如何將它投射到日期並保留時區信息? ('person_id =?AND CAST(start_time AS DATE)=?',self.id,the_date)`(ps使用Postgres ) – Clay 2011-12-16 18:10:58