代码之家  ›  专栏  ›  技术社区  ›  Neil Middleton

获取带有统计信息的Rails模型

  •  1
  • Neil Middleton  · 技术社区  · 15 年前

    我在一个Rails应用程序中有几个模型。比如说产品和销售。

    一个产品有很多销售,一个销售属于一个产品。

    我要做的是获得所有产品的列表(可能有条件),然后在每个产品旁边提供销售数量的计数,类似于:

    • 产品2(22)

    虽然输出相当简单,但我无法找到一种不使用SQL收集数据的好方法。

    我还应该补充一点,我可能想对销售也适用一个条件。

    4 回复  |  直到 15 年前
        1
  •  1
  •   Josh Lindsey    15 年前

    如果您正确声明 associations ,简单如下:

    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
    
        2
  •  2
  •   Harish Shetty    15 年前

    这不是一个答案,而是这个问题的三个答案的比较。我已经给出了答案。在工作中有一些困惑 :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
    

    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语句

    方法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))
    

    方法2-2的SQL语句

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

    方法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) 
    

    按销售额计算产品的最佳方法(不含销售明细):

    按销售额计算产品的最佳方法(含销售明细): 方法2

    方法3有n+1问题。所以这是不争的。

        3
  •  1
  •   Harish Shetty    15 年前

    如果你最感兴趣的是计算每种产品的销售额,你应该选择 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])
    

    有条件地计算一批 产品 s执行以下操作:

    #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
    

    这种方法将节省大量到数据库的访问。

        4
  •  0
  •   Dmytrii Nagirniak    15 年前

    如果您不介意将数据库中的相关销售对象引入应用程序,那么 like this 应该做的工作是:

    但要注意它。

    # 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 }
    

    当做,
    Dmitriy。

    推荐文章