2016-11-18 112 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每次当方法调用