2012-03-04 32 views
2

我有两个型号获取最便宜的产品在类别中轨有效

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 

是醇” SQL好的技巧,我们‘LEFT JOIN’所有便宜的产品类别中的每一个产品,比只需要这些谁没有任何。

我想知道如何使用Rails3关系做类似的事情 - 我使用squeel,所以它也可以使用。

观察:我想过在产品上定义关系:cheaper_products,但它似乎也没有帮助。

另一个想法:也可以用子查询返回的所有最便宜的产品IDS在各自类别来解决它,但它并没有让我的解决办法(并且是那么优雅)。

注意:我知道如何用bruteforce(selector_sql)来做到这一点,但我真的很想学习更多的轨道3的方式来做到这一点。

+0

有人请吗? – gorn 2012-07-28 02:37:49

+0

我知道它不完全符合你的要求,但你可以在数据库中创建一个索引。看看http://www.postgresql.org/docs/current/static/indexes-expressional.html – Automatico 2012-07-29 14:01:51

回答

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"] 
+0

我有点希望我可以避免SQL,但你的方法无论如何鼓舞人心,我应该尝试看看如果有帮助的话。一个问题:为什么你加入:category +添加与类别无关的SQL。是否有可能首先加入cheper_products,然后再加入类别?我自己试着将产品的has_many关联定义为给我便宜的产品,但我很难在同一时间使用关联和范围(如果我想保留延迟加载机制) – gorn 2012-03-05 13:17:45

+0

我这样做是为了防止延迟加载并执行整个在一个查询中的事情。 如果您避免使用'join(:category)'并保留'includes(:cateogry)',则在调用Product.cheapest时将执行另外1个查询。 如果您避免使用'includes(:category)'并保留'joins(:category)',每次访问类别低谷产品*延迟加载*时都会执行一个查询。如果避免'连接(:category)'和'includes(:category)'',同样的事情会发生。 – Krule 2012-03-05 13:56:08

+0

而且,是的,可以首先加入便宜的产品和类别。 – Krule 2012-03-05 14:06:57

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 

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