每个产品的每个用户多个价格组:如何最大限度地减少查询量?

时间:2016-04-15 12:20:24

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

我的Rails(4.2)电子商务应用程序中有以下结构:

class Product < ActiveRecord::Base
  has_many :prices

  def best_price(price_groups)
    prices.where(price_group_id: price_groups).minimum(:value)
  end

  def default_price
    prices.where(price_group_id: 1).first.value
  end

end

class Price < ActiveRecord::Base
  belongs_to :price_group
  belongs_to :product
end


class PriceGroup < ActiveRecord::Base
  has_and_belongs_to_many :users
  has_many :prices
end

class User < ActiveRecord::Base
  has_and_belongs_to_many :price_groups
end

许多用户可以是许多价格组的成员,每个价格组中都有许多产品价格行。 有一个默认组,每个产品的默认价格加上可以有许多可选价格组,某些用户的产品价格特殊(降低)。 问题是 - 这样我在索引页面中列出的每个产品都会收到两个查询:

SELECT MIN("prices"."value") FROM "prices" WHERE "prices"."product_id" = $1 AND "prices"."price_group_id" IN (1, 2)  [["product_id", 3]]

SELECT  "prices".* FROM "prices" WHERE "prices"."product_id" = $1 AND "prices"."price_group_id" = $2  ORDER BY "prices"."id" ASC LIMIT 1  [["product_id", 3], ["price_group_id", 1]]

所以,这是我的问题: 是否有一些(简单)方法一次加载所有内容?就像获取具有默认和最低价格字段的产品对象列表一样。 我理解如何在单个SQL查询中完成它,但我想不出任何更自然的东西,rails-activerecord-way。

****更新: 我最终得到了

# I'm using Kaminari pagination

@products = Product.page(params[:page]).per(6)

product_ids = @products.map(&:id)

@best_prices=Price.where(product_id: product_ids, price_group_id: @user_price_groups).group(:product_id).minimum(:value)

@default_prices=Price.where(product_id: product_ids, price_group_id: 1).group(:product_id).minimum(:value)

这些群组查询会产生类似{ product_id => value }, ...的哈希值,所以我只需要在我的视图中使用@best_prices[product.id]等等。

感谢大家!

2 个答案:

答案 0 :(得分:1)

我同意@Frederick Cheung,我不喜欢自定义的SQL查询,而且我不熟悉arel,所以我的解决方案将是:

products = Product.limit(10)
product_ids = products.map(&:id)

products_hash = products.reduce({}) do |hash, product|
    hash[price.id] = {product: product} } 
    hash
end

best_prices = Price.select("MIN(value) AS min_price, product_id")
    .where(product_id: product_ids, price_group_id: price_groups)
    .group("product_id")
    .reduce({}) do |hash, price|
        hash[price.product_id] = { best_price: price.min_price }
        hash
end

default_prices = Price.where(price_group_id: 1, product_id: product_ids)
    .reduce({}) do |hash, price|
        hash[price.product_id] = {default_price: price.value }
        hash
end

# A hash like {
#  1: {product: <Product ...>, best_price: 12, default_price: 11},
#  2: {product: <Product ...>, best_price: 12, default_price: 11},
#  3: {product: <Product ...>, best_price: 12, default_price: 11}
# }
result = products_hash.deep_merge(best_prices).deep_merge(default_prices)

仍然需要三个任务,而不仅仅是一个,但这解决了N + 1问题。

答案 1 :(得分:0)

只需添加默认范围并包含价格即可解决您的问题

def func():
    return 'hello'

print eval('func()')
print globals()["func"]()
print locals()["func"]()

>>> hello
>>> hello
>>> hello