Reputation: 4248
I'm trying to figured out the best way to count sold products by a given date range on orders by a specific family.
These are my simplified models:
Order
placed_onOrderItem
order_id, product_id, amountProduct
family_idFamily
So, given now some dates, say d1
and d2
, I need to count how many Product
of a given Family
are in those Order
.
The desired output would be something like this:
# all these are products from the same family sold in the last week
[
{"product_24": 3435},
{"product_34": 566},
{"product_83": 422}
…
]
I know how to do it looping all over the orders, but I think there should be a better way.
Upvotes: 3
Views: 180
Reputation: 554
Not the full answer, as you have two pretty detailed ones already - but some notes
I'd setup scopes ... because we're not sure if how vertical your db is ... also, do avoid any time range issues, ensure you get the whole day before feeding the mess into the query. These are just examples not syntax checked or recommended best forms
# ensure we are getting whole of each day
full_d1 = d1.beginning_of_day
full_d2 = d2.end_of_day
# change the order based on whatever you have more of
scope :orders -> {where(created_at: full_d1..full_d2)}
scope :family -> {where(product: family_id)}
# use something like - swap order based on db conditions
Order.orders.family # ... then add the rest of what they are throwing down or .size / .count
Also, as noted elsewhere in S/O you can cache counts on has_many relationships - link
Upvotes: 1
Reputation: 780
Assuming your data model and variables it should be something like:
OrderItem.joins(:order)
.joins(product: :family)
.where(orders: {created_at: d1..d2})
.where(products: {family_id: <YOUR_FAMILY_ID>})
.group(:product_id)
.sum(:amount)
This will generate the following sql:
SELECT
SUM("order_items"."amount") AS sum_amount,
"order_items"."product_id" AS order_items_product_id
FROM "order_items"
INNER JOIN "orders" ON "orders"."id" = "order_items"."order_id"
INNER JOIN "products" ON "products"."id" = "order_items"."product_id"
INNER JOIN "families" ON "families"."id" = "products"."family_id"
WHERE
("orders"."created_at" BETWEEN ? AND ?)
AND "products"."family_id" = ?
GROUP BY "order_items"."product_id"
and return the following structure:
=> [{product_id => <sum of this product id since d1 until d2 for family_id>}, ...]
Also I'm assuming you want to sum the amount of each product. Let me know if works for you.
Upvotes: 3
Reputation: 1198
class CreateOrders < ActiveRecord::Migration[5.1]
def change
create_table :orders do |t|
t.timestamps
end
end
end
class CreateOrderItems < ActiveRecord::Migration[5.1]
def change
create_table :order_items do |t|
t.integer :order_id, index:true
t.integer :product_id, index:true
t.integer :amount
t.timestamps
end
end
end
class CreateProducts < ActiveRecord::Migration[5.1]
def change
create_table :products do |t|
t.text :name
t.integer :family_id, index:true
t.timestamps
end
end
end
class CreateFamilies < ActiveRecord::Migration[5.1]
def change
create_table :families do |t|
t.text :name
t.timestamps
end
end
end
class Family < ApplicationRecord
has_many :products
end
class Order < ApplicationRecord
has_many :order_items
has_many :products, through: :order_items
end
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Product < ApplicationRecord
belongs_to :family
has_many :order_items
end
irb(main):015:0> Order.joins(:order_items).joins(:products).where("products.family_id":2).where("orders.created_at": [(Time.now).to_date..(Time.now + 1.day).to_date]).count
(0.6ms) SELECT COUNT(*) FROM "orders" INNER JOIN "order_items" ON "order_items"."order_id" = "orders"."id" INNER JOIN "order_items" "order_items_orders_join" ON "order_items_orders_join"."order_id" = "orders"."id" INNER JOIN "products" ON "products"."id" = "order_items_orders_join"."product_id" WHERE "products"."family_id" = ? AND ("orders"."created_at" BETWEEN '2018-04-20' AND '2018-04-21') [["family_id", 2]]
=> 3
Upvotes: 1