Reputation: 8503
Activerecord question.. How to optimize this query..
Prefectures (have many) Cities (have many) Shops (have many) Sales (have many) Brands
I'd like to get a list of one sale per prefecture, which is yet to end.. and then list the brands available at the sale.
The nesting is making this kind of tricky for me!
Here is what I came up with, though it's pretty ugly & I think it can be optimized at the query level rather than getting all unfinished sales..
#Get all sales which are yet to finish, ordered by finish date
upcoming_sales = Sale.includes([{:shop => {:city => :prefecture}}, :brands])
.where("finish > ?", Date.today)
.order('start ASC, finish ASC')
.select(['shop.city.prefecture.name', 'brands.name'])
#filter down to a single sale per prefecture
@sales = upcoming_sales.each_with_object({}){ |s,o|
o[s.shop.city.prefecture.name] = o[s.shop.city.prefecture.name] ||= s
}
Upvotes: 2
Views: 782
Reputation: 18530
How about something like this?
class Sale < ActiveRecord::Base
belongs_to :shop
has_many :brands
def self.first_sale_per_prefecture()
first_sale_id_per_prefecture = %(
select max(sales.id)
from sales
inner join shops on shop_id = shops.id
inner join cities on city_id = cities.id
where finish > #{Date.today}
group by prefecture_id
order by finish desc)
where("sales.id in (#{first_sale_id_per_prefecture})").includes(:brands, :shop => {:city => :prefecture})
end
end
Upvotes: 2
Reputation: 18198
I'm going to try this using Arel
class Sale < ActiveRecord::Base
belongs_to :shop
class << self
# Returns only Sale objects with obj.finish > today
# add on other ActiveRecord queries:
# Sale.unfinished.all
# Sale.unfinished.all :limit => 10
def unfinished
where(Sale.arel_table[:finish].gt(Date.today))
end
# should select only one set of unfinished sales,
# and where the prefecture name is distinct
def distinct_prefecture
Sale.unfinished.joins({:shop => {:city => :prefecture}}).where(Prefecture.arel_table[:name].distinct)
end
end
end
Then, where you want it:
@sales = Sale.distinct_prefecture \
.includes(:brands]) \ # should already include the other stuff with joins
.order('start ASC, finish ASC')
@brand_list = @sales.collect{|s| s.brands}
If you want a limited result, this should be ok:
@sales = Sale.distinct_prefecture \
.limit(10) \
.includes(:brands]) \
.order('start ASC, finish ASC')
Upvotes: 0
Reputation: 2663
You could get the upcoming sales and then join to shops => cities => prefectures and SELECT DISTINCT prefecture_id
this would ensure you only have one sale per prefecture. Something like this:
@sales = Sale.includes([{:shop => :prefecture},:brands])
.order('finish DESC')
.where("finish > ?", Date.today)
.joins(:shops => { :cities => :prefectures })
.select('sales.*, DISTINCT prefecture.id')
Upvotes: 2