Получение модели рельсов со статистикой - PullRequest
1 голос
/ 26 февраля 2010

У меня есть пара моделей в приложении Rails. Допустим, это продукты и продажи.

Товар имеет много продаж, и продажа принадлежит продукту.

Я хочу получить список всех товаров (возможно, с условиями), а рядом с каждым товаром указать количество продаж, подобное:

  • Продукт 1 (10)
  • Продукт 2 (22)

Хотя вывод довольно прост, я не могу найти хороший способ сбора данных без использования SQL.

Я должен также добавить, что я могу захотеть применить условие и к продажам.

Ответы [ 4 ]

2 голосов
/ 26 февраля 2010

Это не ответ, а сравнение трех ответов на этот вопрос. Я уже дал свой ответ. Существует путаница с работой :joins и :include в ActiveRecord.find. Поэтому я потратил некоторое время на анализ журнала SQL для трех решений.

Подход 1: Получить счет продаж, используя count

  #To return the sales count for each product
  ps_count_hash = Product.count(:joins => [:sales], :group => "products.id") # sql 1.1

  # To print the product id and sales count
  ps_count_hash.each do | product_id, sales_count|
   p "Product#{product_id} - (#{sales_count})"
  end

  # To print the product details and sales count
  # get all the products associated 
  Product.find_all_by_id(ps_count_hash.keys).each |product|  #sql 1.2
   p "Product[id = #{product.id}, name = #{product.name}] - (#{ps_count_hash[product.id]})"
  end

Подход 2: Получить продукты через join

  Product.find(:all, :joins=>[:sales]).each do |product| #sql 2.1
   p "Product[id = #{product.id}, name = #{product.name}] - (#{product.sales.size})" # sql 2.2 - 2.(2+N)
  end

Подход 3: Получить продукты через include

  Product.find(:all, :include=>[:sales]).each do |product| #sql 3.1 and 3.2
   p "Product[id = #{product.id}, name = #{product.name}] - (#{product.sales.size})" 
  end

Теперь давайте посмотрим на операторы SQL, сгенерированные этими тремя подходами

SQL-операторы для подхода 1 - 2 SQL

SELECT count(*) AS count_all, products.id AS products_id FROM `products` INNER JOIN `sales` ON sales.product_id = products.id GROUP BY products.id 
SELECT `products`.* FROM `products` WHERE (`products`.`id` IN (1,2))

SQL-операторы для подхода 2 - 2 SQL

SELECT * FROM `products` 
SELECT `sales`.* FROM `sales` WHERE (`sales`.product_id IN (1,2)) 

SQL-операторы для подхода 3 - N + 1 SQL

SELECT `products`.* FROM `products` INNER JOIN `sales` ON sales.product_id = products.id 
SELECT * FROM `sales` WHERE (`sales`.product_id = 1) 
SELECT * FROM `sales` WHERE (`sales`.product_id = 2) 

Лучший подход для подсчета продукта по продажам (без данных о продажах): Подход 1

Лучший подход для подсчета продукта по продажам (с подробностями продаж): Подход 2

Подход 3 имеет проблему N + 1. Так что это вне раздора.

1 голос
/ 26 февраля 2010

Если вас больше всего интересует подсчет продаж по продукту, вам следует воспользоваться функцией counter_cache. Это гарантирует, что вы всегда будете получать счет из таблицы products вместо расчетов JIT.

# products table should have a column called sales_count
class Product < ActiveRecord:Base
  has_many :sales
end

class Sales < ActiveRecord:Base
  belongs_to :product, :counter_cache => true
end

Направляющие позаботятся об увеличении / уменьшении столбца sales_count при создании / удалении продаж. Теперь вы можете получить счет продаж, выполнив следующие действия:

Product.first.sales_count

Чтобы условно подсчитать sales на одном product объекте, сделайте следующее:

Product.first.sales.count(:conditions => ["amount > ? ", 200])

Для условного подсчета партии product с выполните следующие действия:

#returns a ordered hash
product_sales_count = Product.count(:joins => [:sales], :conditions => 
            ["amount > ? ", 200], :group =>"products.id").each do |product_id, sales_count|
   p "Product #{product_id} = #{sales_count}"
end

# If you need the product objects get all of them in one call and match the count
products = Product.find(:all, :joins => [:sales], :conditions => 
             ["amount > ? ", 200], :group =>"products.id").each do |product|

   p "Product #{product.name} = #{product_sales_count[product.id]}"

end

Такой подход сэкономит много поездок в базу данных.

1 голос
/ 26 февраля 2010

Если вы правильно объявляете ассоциации , это так просто:

Product.find(:all, :include => :sales).each do |product|
  puts product.name + " (#{product.sales.size})"
end

РЕДАКТИРОВАТЬ:
Ассоциации - это богатые коллекции, в которых можно искать, если нужно применить условие:

@products = Product.find(:all, :include => :sales)
@products.each do |product|
  # Find product sales only from yesterday
  yesterdays_sales = product.sales.find(:all, :conditions => { :created_at => Date.yesterday }
end
0 голосов
/ 26 февраля 2010

Если вы не против перенести связанные объекты Sales из базы данных в приложение, тогда что-то , подобное этому , должно выполнить эту работу:

Но следите за этим.

# Preload all the Sales for our Products, sow we won't end up with `N+1` issue.
selected_products = Product.all :joins => :sales, :conditions => {'orders.successful' => true}

# Then use it like a normal association:
selected_products.each { |p| puts p.sales.size }

С уважением,
Дмитрий.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...