我试图通过减少使用ActiveRecord 3.0.9的查询数量来实现。我生成了关于'假'的200K客户和500K订单。
这是模特:
class Customer < ActiveRecord::Base
has_many :orders
end
class Orders < ActiveRecord::Base
belongs_to :customer
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :order
end
在控制器中使用此代码时:
@customers = Customer.where(:active => true).paginate(page => params[:page], :per_page => 100)
# SELECT * FROM customers ...
并在视图中使用它(我删除了HAML代码以便于阅读):
@order = @customers.each do |customer|
customer.orders.each do |order| # SELECT * FROM orders ...
%td= order.products.count # SELECT COUNT(*) FROM products ...
%td= order.products.sum(:amount) # SELECT SUM(*) FROM products ...
end
end
但是,页面呈现为每页100行的表。问题是加载速度慢,因为每个客户的订单会触发3-5次查询。这大约有300个查询来加载页面。
还有其他方法可以减少查询次数并加快页面加载速度吗?
注意:
1)我曾尝试使用includes(:orders),但它包含超过200,000个order_id。那个问题。
2)他们已被编入索引。
答案 0 :(得分:3)
如果您只使用COUNT
和SUM(amount)
,那么您真正需要的只是检索该信息而不是订单本身。这可以通过SQL轻松完成:
SELECT customer_id, order_id, COUNT(id) AS order_count, SUM(amount) AS order_total FROM orders LEFT JOIN products ON orders.id=products.order_id GROUP BY orders.customer_id, products.order_id
您可以将此包装在一个方法中,该方法通过将SQL结果重新映射到符合您要求的结构来返回一个漂亮,有序的哈希:
class Order < ActiveRecord::Base
def self.totals
query = "..." # Query from above
result = { }
self.connection.select_rows(query).each do |row|
# Build out an array for each unique customer_id in the results
customer_set = result[row[0].to_i] ||= [ ]
# Add a hash representing each order to this customer order set
customer_set << { :order_id => row[1].to_i, :count => row[2].to_i, :total => row[3].to_i } ]
end
result
end
end
这意味着您可以一次性获取所有订单计数和总计。如果你有customer_id
的索引,这在这种情况下是必不可少的,那么即使对于大量的行,查询通常也会非常快。
您可以将此方法的结果保存到@order_totals
等变量中,并在渲染表时引用它:
- @order = @customers.each do |customer|
- @order_totals[customer.id].each do |order|
%td= order[:count]
%td= order[:total]
答案 1 :(得分:0)
你可以尝试这样的事情(是的,它看起来很难看,但你想要表现):
orders = Order.find_by_sql([<<-EOD, customer.id])
SELECT os.id, os.name, COUNT(ps.amount) AS count, SUM(ps.amount) AS amount
FROM orders os
JOIN products ps ON ps.order_id = os.id
WHERE os.customer_id = ? GROUP BY os.id, os.name
EOD
%td= orders.name
%td= orders.count
%td= orders.amount
已添加:可能最好在count
中创建amount
和Orders
缓存,但您必须对其进行维护(count
可以是反缓存,但我怀疑amount
)有一个现成的配方。
答案 2 :(得分:0)
您可以使用Arel加入表(我希望尽可能避免编写原始sql)。我相信你会做一些像:
Customer.joins(:orders -> products).select("id, name, count(products.id) as count, sum(product.amount) as total_amount")
第一种方法 -
Customer.joins(:orders -> products)
- 在一个语句中引入嵌套关联。然后是第二部分 -
.select("id, name, count(products.id) as count, sum(product.amount) as total_amount")
- 准确指定您想要的列。
将这些链接起来,我相信您将获得仅使用您在select方法中指定的内容填充的Customer实例列表。你必须要小心,因为你现在手边只能读取可能处于无效状态的对象。
与所有Arel方法一样,从这些方法中获得的是ActiveRecord :: Relation实例。只有当您开始访问该数据并将其发出并执行SQL时。
我有一些基本的紧张感,我的语法不正确,但我相信这可以完全依赖于执行原始SQL。