2011-07-13 57 views
3

我試圖通過減少使用ActiveRecord 3.0.9查詢的數量來實現。我生成了關於'虛擬'200K客戶和500K訂單。ActiveRecord的opitimization - 最好的方式來查詢所有在一次?

這裏的模型:

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)我已經嘗試使用包括(:訂單),但它包括了超過20萬個order_ids。這是問題。

2)他們已經索引。

回答

3

如果你只使用COUNTSUM(amount)那麼你真正需要的是隻檢索信息而不是訂單本身。這是很容易與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 

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結果到適合您的需求結構將返回一個不錯的,有序的散列的方法把這個包這意味着您可以一次獲取所有訂單計數和總計。如果您在customer_id上有一個索引,這在這種情況下勢在必行,那麼即使對於大量的行,查詢通常也會非常快。

可以將此方法的結果保存到作爲@order_totals這樣的變量,並引用它渲染你的表時:

- @order = @customers.each do |customer| 
    - @order_totals[customer.id].each do |order| 
    %td= order[:count] 
    %td= order[:total] 
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 

補充:也許最好是在Orders創建countamount緩存,但你會要保持它(count可以反緩存,但我懷疑是有準備的食譜amount)。

0

你可以加入表與阿雷爾(我更喜歡避免編寫原始SQL時可能)。我相信,你的例子中,你會做這樣的事情:

Customer.joins(:orders -> products).select("id, name, count(products.id) as count, sum(product.amount) as total_amount") 

第一method--

Customer.joins(:orders -> products) 

--pulls在嵌套的關聯一個聲明。然後第二部分 -

.select("id, name, count(products.id) as count, sum(product.amount) as total_amount") 

- 確切指定您想要返回的列。

鏈這些,我相信你會得到一個客戶實例列表,只填充您在select方法中指定的內容。你必須小心,因爲你現在手頭只能讀取可能處於無效狀態的對象。

與所有Arel方法一樣,您從這些方法獲得的是ActiveRecord :: Relation實例。只有當你開始訪問數據時,它纔會出來並執行SQL。

我有一些基本的緊張,我的語法是不正確的,但我相信這可以做,而不依賴於執行原始SQL。

相關問題