在rails 4

时间:2015-11-25 14:44:33

标签: ruby-on-rails ruby-on-rails-4 activerecord

我的habtmProduct型号之间存在Category关系。 我正在尝试编写一个搜索至少包含2个类别的产品的查询。

我使用以下代码:

p = Product.joins(:categories).group("product_id").having("count(product_id) > 1")
p.length # 178

当迭代它时,每次我调用product.categories时,它都会对数据库进行新的调用 - 不好。 我想阻止这些来电并获得相同的结果。做更多的研究我已经看到我可以包含(includes)我的categories表,它会将所有表加载到内存中,因此当没有必要再次调用数据库时迭代。所以我使用以下代码:

p2 = Product.includes(:categories).joins(:categories).group("product_id").having("count(product_id) > 1")
p2.length # 178 - I compared and the objects are the same as last query

以下是我感到困惑的事情:

p.first.eql? p2.first # true
p.first.categories.eql? p2.first.categories # false
p.first.categories.length # 2
p2.first.categories.length # 1

为什么使用includes查询我得到正确的对象,但我没有得到categories关系?

2 个答案:

答案 0 :(得分:2)

它与group方法有关。您的p2仅包含每种产品的第一个类别。

您可以将其分解为两个查询:

product_ids = Product.joins(:categories).group("product_id").having("count(product_id) > 1").pluck(:product_id)

result = Product.includes(:categories).find(product_ids)

是的,你打了几次数据库,但至少你在迭代时没有去数据库。

答案 1 :(得分:1)

您必须知道includes与联接不起作用(joins只会压制前者)。

另外当include关联ActiveRecord确定它是否会使用eager_load(使用左连接)或preload(使用单独的查询)时。包含只是其中之一的包装。

事情是preload与联接有关!所以你可以这样做:

products = Product.preload(:categories). # this will trigger a separate query
               joins(:categories).       # this will build the relevant query
               group("products.id").
               having("count(product_id) > 1").
               select("products.*")

请注意,这也会使数据库命中两次,但您不会有任何O(n)查询。