有效地获得铁路类别中最便宜的产品

时间:2012-03-04 21:19:22

标签: sql ruby-on-rails-3 scope associations arel

我有两个型号

class Product < ActiveRecord::Base
  belongs_to :category
end

class Product < ActiveRecord::Base
  belongs_to :category
end

现在我想用最便宜的产品列出所有类别。通过获取所有类别,迭代它们并单独找到最便宜的产品,这很容易完成,但这会进行大量查询并且速度非常慢。我可以用sql很容易地做到这一点 - 比如

SELECT products.*, categories.*
  FROM products
  JOIN categories ON (categories.id = products.owner_id)
  LEFT JOIN products as cheaper_products ON 
   cheaper_products.category_id = epochs.category_id AND
   cheaper_products.price < products.price
  WHERE cheaper_products.owner_id IS NULL

这是一个很好的技巧,我们“LEFT JOIN”所有类别中的所有更便宜的产品到每个产品,而不仅仅是那些没有任何产品。

我想知道使用Rails3关系可以做些类似事情 - 我正在使用squeel,因此它也可以使用。

观察:我想过定义一个关系:产品上的cheaper_products,但它似乎也无济于事。

另一个想法:也可以用子查询来解决它,返回其类别中所有最便宜产品的ID,但它也不让我解决(并且不太优雅)。

注意:我知道如何使用bruteforce(selector_sql),但我真的想学习更多rails 3方法。

2 个答案:

答案 0 :(得分:2)

这是一个有趣的问题,我认为你走的是正确的道路。您可以使用纯SQL查询更轻松地解决它,然后处理ActiveRecord,因为您无法避免以任何方式编写SQL。

一种方法是在连接中将预先加载与SQL结合起来,这样只执行一个查询:

# app/models/category.rb
class Category < ActiveRecord::Base
  has_many :products
end

# app/models/product.rb
class Product < ActiveRecord::Base
  belongs_to :category

  def self.cheapest
    joins(:category, "LEFT OUTER JOIN products AS cheaper_products ON (products.category_id = cheaper_products.category_id AND cheaper_products.price < products.price)").
    where("cheaper_products.category_id IS ?", nil).
    includes(:category)
  end

end

在控制台中测试时

1.9.3-p125 :001 > Product.cheapest.to_sql
 => "SELECT \"products\".* FROM \"products\" INNER JOIN \"categories\" ON \"categories\".\"id\" = \"products\".\"category_id\" LEFT OUTER JOIN products\n      AS cheaper_products\n      ON (products.category_id = cheaper_products.category_id\n      AND cheaper_products.price < products.price) WHERE (cheaper_products.category_id IS NULL)" 

1.9.3-p125 :002 > Product.where(:category_id => 1).minimum(:price)
   (0.2ms)  SELECT MIN("products"."price") AS min_id FROM "products" WHERE "products"."category_id" = 1
 => 16.0 
1.9.3-p125 :003 > Product.where(:category_id => 2).minimum(:price)
   (0.3ms)  SELECT MIN("products"."price") AS min_id FROM "products" WHERE "products"."category_id" = 2
 => 7.0 
1.9.3-p125 :004 > Product.where(:category_id => 3).minimum(:price)
   (0.3ms)  SELECT MIN("products"."price") AS min_id FROM "products" WHERE "products"."category_id" = 3
 => 19.0 

1.9.3-p125 :005 > cheap_products = Product.cheapest
...

1.9.3-p125 :006 > cheap_products.each {|product| p [product.price, product.category.name] }
[16.0, "Category 0"]
[7.0, "Category 1"]
[19.0, "Category 2"]

答案 1 :(得分:0)

你可以试试这个:

categories = Category.includes(:products).group('categories.id,products.id').having('MIN(products.price)')

categories.each {|c| p c.products.first} # List of cheapest product in each category

这不包括没有产品的类别。