2010-08-18 86 views
11

嘗試使用PostgreSQL檢索按日期分組的ActiveRecord對象數組。優雅的PostgreSQL Group by Ruby on Rails/ActiveRecord

更具體地說我想翻譯下面MySQL查詢:

@posts = Post.all(:group => "date(date)", 
    :conditions => ["location_id = ? and published = ?", @location.id, true], 
    :order => "created_at DESC") 

我知道SQL標準的PostgreSQL的解釋是比MySQL更嚴格,並且因此這種類型的查詢將無法工作。 ..並已閱讀了StackOverflow和其他主題上的許多帖子 - 但他們似乎都沒有在這個問題上的權威性答案

我試過各種組合的查詢和不同的子句沒有太多快樂 - 而現在我有一個相當不雅的黑客,雖然作品讓我臉紅我看着它。

什麼是使用Rails和PostgreSQL進行查詢的正確方法? (忽略這樣的事實,當然這應該在ActiveRecord級別抽象出來)

+0

「數組...按日期分組」 - 這是沒有意義的。你想達到什麼目的?你能按日期(日期)訂購嗎? – DanSingerman 2010-08-18 13:01:18

+1

除MySQL以外的任何數據庫都將拒絕非法SQL。數據庫不會猜測你今天會想要什麼結果,db只能在所有情況下得到所有正確的結果。在MySQL中使用ONLY_FULL_GROUP_BY,以上查詢也將被MySQL拒絕。 – 2010-08-18 13:37:04

+0

嗨丹 - 我試圖獲得Post對象數組,但我只想在任何給定日期(當天的最新Post)檢索一個Post。 – digitalfrost 2010-08-21 15:55:21

回答

13

您要在此處使用的PostgreSQL功能是DISTINCT ON。通過ActiveRecord進行查詢有兩種基本方法。

第一種方法是隻指定:select:order選項。當你有一個相當簡單的查詢,沒有:joins:include時,這很有用。

Post.all(
    :select => 'DISTINCT ON (date::date) *', 
    :order => 'date::date DESC, created_at DESC' 
) 

如果你有更復雜的查詢ActiveRecord的生成自己的SELECT條款,你可以使用子查詢來選擇目標的記錄。

Post.all(
    :joins => 'INNER JOIN (SELECT DISTINCT ON (date::date) id FROM posts ORDER BY date::date DESC, created_at DESC) x ON x.id = posts.id' 
) 

請注意,根據您的數據,這可能比第一種方法慢一點。如果需要,我只會使用這種方法。一定要以生產類數據爲基準。

1

我的解決辦法:

def self.columns_list 
    column_names.collect { |c| "#{table_name}.#{c}" }.join(",") 
end 

scope :selling, joins(:products).group(columns_list) 

簡單,重複性好。

0

雖然在回答諸如「每天最近發佈的帖子是什麼時候」這樣的問題時,SQL非常簡單。當你問「每天最近的帖子是什麼?」時,它並不是非常直接的。

如果不使用子SELECT(或多個SQL語句),則無法每天檢索最新的Post。這可能會爲你(使用Post.find_by_sql或類似)工作:

SELECT P.*, M.just_day, M.max_created_at 
FROM posts P 
JOIN (
    SELECT date(P2.date) AS just_day, MAX(P2.created_at) AS max_created_at 
    FROM posts P2 
    P.location_id='12345' AND P.published=true 
    GROUP BY date(P2.date) 
) AS M 
    ON AND M.max_created_at = P.created_at 
WHERE P.location_id='12345' AND P.published=true 

上面的SQL語句應該是足夠如果你可以肯定的是,兩個職位將不會有在created_at列中的值相同。如果你不能保證創建列的唯一性,那麼你需要在Ruby中過濾出重複項(這應該不是太低效,因爲大概你會在列表中循環),否則你需要執行N +1 SQL語句。 (實際上你可以做每行選擇,但AFAIK與N + 1 SQL語句一樣低效。)

這裏是你如何可以刪除重複而循環:

last_post = nil 
posts.each do |post| 
    unless post.just_day == last_past.try(:just_day) 
    # Do stuff 
    last_post = post 
    end 
end 

這就是說,你可以把它很好地只用紅寶石/ ActiveRecord的寫,如果你有足夠的幾天,一個SELECT每天ISN」牛逼太糟糕了:

days = Post.group("date(date)") 
posts = days.each { |day| Post.order('created DESC').where("date(day) = ?", day) } 

如果您正在使用分頁(說每頁10個項目),那麼這將需要爲每個頁面11個SQL語句。不是想法,但簡單可能值得效率低下。老實說,如果你希望這個查詢既經常運行,又有相當大的數據集,那麼我建議你添加一個名爲most_recent的布爾列。過去幾天的最後一篇文章不會改變。你只需要擔心從今天開始的帖子。只需設置一個cron作業即可在一天結束後運行幾分鐘以更新最後一天的值。如果你想要更新的東西,你可以每5分鐘運行一次cron作業。或者,如果你需要實時的話,那麼添加一個after_save回調函數,將當前帖子以外的所有帖子的most_recent設置爲false。

這個問題是類似的:MySQL: Getting highest score for a user