Reputation: 48521
I have these models:
class Product < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :product
end
I want to get a list of all products which have been ordered at least once along with the timestamp of when the order has been made (orders.created_at
) and the code of the order (orders.no
) and the admin who processed the orderd (orders.processed_by_admin_id
). And finally, I would want to list the output by orders.created_at
.
After some googling, I was able to come up with something like this:
Product.joins(:orders)
.group("(products.id) having count(products.id) > 0")
.select("products.*", "max(orders.created_at) as last_order_time")
.sort_by(&:last_order_time).reverse
This gets me the list of products ordered at least once, sorted out by the timestamp. However, I got stucked here being unable to fetch yet the orders.no
and orders.processed_by_admin_id
. I am not sure if the procedure I follow is the right one.
The desired output is something like:
products.* | last_order_time | order_no | order_processed_by_admin_id
Any advice appreciated.
Upvotes: 2
Views: 193
Reputation: 17538
I want to get a list of all products which have been ordered at least once along with the timestamp of when the order has been made (orders.created_at) and the code of the order (orders.no) and the admin who processed the orderd (orders.processed_by_admin_id). And finally, I would want to list the output by orders.created_at.
When writing a complicated query, I like to write the plain SQL first.
Let's start with a query to get the most recent order for each product.
select product_id, id,
row_number() over (
partition by product_id order by created_at
) as recency_ranking
from orders
;
This is called a window function. For each product, the most recent order will have a recency_ranking
of 1. Now we can join this query to our products
table.
select p.*,
x.id as order_id,
x.no as order_number,
x.order_processed_by_admin_id,
x.created_at as order_created_at
from products p
inner join (
select product_id,
id,
created_at,
row_number() over (
partition by product_id order by created_at
) as recency_ranking,
no,
order_processed_by_admin_id
from orders
) x
on x.product_id = p.id
and x.recency_ranking = 1
order by x.created_at
;
When we join another query like this it's called a subquery. Note how we join on recency_ranking = 1
.
To run this whole query in ActiveRecord, I'd suggest find_by_sql
:
products = Product.find_by_sql('select p.*, x.id ...')
products.first.order_created_at #=> '2019-01-01 ...'
Upvotes: 2