2016-11-18 76 views
0

A postlikerscomments孩子。我想根據他們對帖子進行排序。避免N + 1查詢的軌道

class Post < ApplicationRecord 
    scope :latest, -> { 
    all.sort_by(&:ranking) 
    } 

    def ranking 
    likers.count + comments.count 
    end 
end 

這就要求查詢象下面這樣:

Post Load (0.7ms) SELECT "posts".* FROM "posts" 
    (0.4ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 52]] 
    (0.4ms) SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1 [["post_id", 52]] 
    (0.2ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 53]] 
    (0.3ms) SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1 [["post_id", 53]] 

所以我儘量不要使用以下:

Post.includes(:comments, :likers).all.sort_by(&:ranking) 

這就要求查詢象下面這樣:

Post Load (0.7ms) SELECT "posts".* FROM "posts" 
    Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (52, 53, 54, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71) 
    UserPostLike Load (0.3ms) SELECT "user_post_likes".* FROM "user_post_likes" WHERE "user_post_likes"."post_id" IN (52, 53, 54, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71) 
    User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 46 
    (0.3ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 52]] 
    (0.3ms) SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1 [["post_id", 52]] 
    (0.2ms) SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = $1 [["post_id", 53]] 
    (0.3ms) SELECT COUNT(*) FROM "users" INNER JOIN "user_post_likes" ON "users"."id" = "user_post_likes"."user_id" WHERE "user_post_likes"."post_id" = $1 [["post_id", 53]] 

這是爲什麼發生,我怎麼可以這樣是嗎?

UPDATE:

我想通了,如何解決這個問題,但有一個非常好的解釋答案將是很好:

我有size更換count

初始:

class Post < ApplicationRecord 
    scope :latest, -> { 
    all.sort_by(&:ranking) 
    } 

    def ranking 
    likers.count + comments.count 
    end 
end 

後:

class Post < ApplicationRecord 
    ... 

    def ranking 
    likers.size + comments.size 
    end 
end 

然後,N+1 Query走了。當我使用counter_cache時,我發現同樣的事情發生了。在這種情況下,我沒有使用counter_cache,但我仍然必須使用size而不是count。我假設調用count迫使Rails調用COUNT SQL查詢並調用size使其使用內存中加載的記錄。使用LEFT OUTER JOIN

Post.eager_load(:comments, :likers).sort_by(&:ranking) 

預先加載加載所有協會在一份單獨的查詢:

+0

看看這個http://stackoverflow.com/a/9209705/4758119 –

回答

0

這裏的問題是兩個摺痕:

首先,sort_by立即引發一個標誌,對我來說: http://apidock.com/ruby/Array/sort%21

這是一個Array方法,這意味着你是不再構建ActiveRecord查詢,您正在進行數組轉換。

由於您包括commentslikers查詢並不像他們可能會那麼糟糕,但這是另一個問題。

作品.count的工作方式是預製計數查詢SELECT * FROM table

要獲得您想要的結果,您需要構建自己的計數和排序查詢。

看看這篇文章,希望這會給你如何將這些進一步優化一個更好的主意:你的情況 Rails 3 ActiveRecord: Order by count on association

+0

你發佈了一個mysql的例子,在Postgres中你需要聚合所有的屬性。 –

+0

請參閱我的更新 –

+0

@Зелёный你是什麼意思彙總所有屬性?我正在避免建立一個查詢來計算和排序的排名,沒有更多。 – fbelanger

0

最好的辦法是使用counter_cachelikerscomments。 更多詳細信息,你可以閱讀SHORT ARTICLE。這很容易,它會安全的時間和記憶。

如果您使用counter_cache,則不應向數據庫發出多個請求。現在你的方法是:

def ranking 
    likers_count + comments_count 
end 

在另一方面,如果你不想將列添加到表中只使用includes

class Post < ApplicationRecord 
    scope :latest, -> { 
    includes(:likers, :comments).sort_by(&:ranking) 
    } 

    def ranking 
    likers.count + comments.count 
    end 
end 

但在這種情況下,你會計算likerscomments每次當方法調用