2012-03-04 63 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 

這不包括沒有產品的類別。

相關問題