Wednesday, May 2, 2018

Count sold products by specific families in orders by date

Leave a Comment

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_on
  • OrderItem order_id, product_id, amount
  • Product family_id
  • Family

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.

3 Answers

Answers 1

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.

Answers 2

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 

Answers 3

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

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment