2016-11-18 5 views
0

postにはlikerscommentsの子があります。私はそれらに基づいて投稿をソートしたい。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:

私はそれを解決する方法を考え出したが、とても素敵な説明と答えが素敵になります:

私はsizecountを交換しなければなりませんでした。初期

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は使用しませんでしたが、countの代わりにsizeを使用しなければなりませんでした。私はcountを呼んでRailsにCOUNTというSQLクエリを呼び出し、sizeを呼び出すとメモリにロードされたレコードを使用するようにします。

+0

これを見てくださいhttp://stackoverflow.com/a/9209705/4758119 –

答えて

2

あなたはそのようeager_loadを使用することができます。

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

積極的なロードはLEFT OUTER JOINを使用して単一のクエリ内のすべての関連付けをロードします。ここ

Eager Loading Associations

3 ways to do eager loading (preloading) in Rails 3 & 4

+0

この問題は彼の関連がロードされているクエリからわかるように、熱心な読み込みではありません。 – fbelanger

+0

答えをありがとうが、これは問題を解決しません。 –

+0

eager_loadは1つのクエリしか実行せず、メモリ内のすべてのデータ(アソション・レコード)をロードします。 –

0

問題が2倍で:

まず、sort_byはすぐに私のためにフラグを立て: http://apidock.com/ruby/Array/sort%21

それはあなたがある意味、Array方法であり、 ActiveRecordクエリを構築しなくても、配列変換を行っています。

commentsとが含まれているため、クエリはそれほど悪くはありませんが、ここでは別の問題です。

方法.countは、あらかじめ作成されたカウントクエリSELECT * FROM tableです。

必要な結果を得るには、独自のカウントとソートクエリを作成する必要があります。

は、この記事を見て、うまくいけば、これはあなたにこれをさらに最適化する方法について良いアイデアを与えるだろう:あなたのケースで最良の方法は、likerscommentsためcounter_cacheを使用することです Rails 3 ActiveRecord: Order by count on association

+0

あなたはmysqlの例を投稿します。Postgresではすべての属性を集計する必要があります。 –

+0

私の更新を参照してください –

+0

@Зелёныйすべての属性を集約することを意味しますか?私はランキングを数えたり、並べ替えるためのクエリを作成することをやめていました。 – fbelanger

0

を。 詳細はSHORT ARTICLEです。それは非常に簡単で、安全な時間と記憶があります。

counter_cacheを使用する場合は、DBに複数のリクエストを行うべきではありません。そして今、あなたの方法は次のようになります。

一方
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 

が、この場合には、あなたはlikersを計算しますとcommentsメソッド呼び出しのたびに

関連する問題