2009-01-08 48 views
5

我試圖在Rails的圖表中,例如每天每天的平均銷售額在指定日期範圍內最好的方式得到每天AVG特定日期範圍

假設我有一個具有「sales_price」浮動屬性的products_sold模型。但是如果特定的一天沒有銷售(例如沒有在model/db中),我想簡單地返回0.

MySQL/Rails中完成此操作的最佳方式是什麼?我知道我可以做這樣的事情:

此SQL查詢可能是完全錯誤的方式得到的,我想太

SELECT avg(sales_price) AS avg, DATE_FORMAT(created_at, '%m-%d-%Y') AS date 
    FROM products_sold WHERE merchant_id = 1 GROUP BY date; 

而得到的結果是這樣的:

 
| avg | date | 
    23 01-03-2009 
    50 01-05-2009 
    34 01-07-2009 
    ...  ... 

我想獲得的是:

 
| avg | date | 
    23 01-03-2009 
    0 01-04-2009 
    50 01-05-2009 
    0 01-06-2009 
    34 01-07-2009 
    0 01-08-2009 
    ...  ... 

我可以使用SQL來做到這一點,還是必須對結果進行後處理,以查找daterange中的哪些日期不在SQL結果集中?也許我需要一些子選擇或IF語句?

感謝任何幫助大家。

回答

7

是否有一個原因(除了已經提到的日期之外)爲什麼你不使用ActiveRecord中的內置組函數功能?你似乎擔心「後期處理」,我認爲這不是真正需要擔心的事情。

你在Rails中,所以你應該首先尋找一個Rails解決方案[1]。我首先想到的是做這樣的事情

Product.average(:sales_price, :group => "DATE(created_at)", :conditions => ["merchant_id=?", 1]) 

其中的ActiveRecord變成幾乎你所描述的SQL。假設有商戶和產品之間的宣佈has_many關聯,那麼你可能會更好使用,所以像:

ave_prices = Merchant.find(1).products.average(:sales_price, :group => "DATE(created_at)") 

(我希望你的型號爲「products_sold」的描述是一些類型的轉錄錯誤,順便說一句 - 如果沒有,你有點關閉消息與您的類命名!)

畢竟,你回到你開始的地方,但你以更常規的Rails方式(和Rails真的重視慣例!)。現在我們需要填補空白。

我假設你知道你的日期範圍,假設它被定義爲從from_dateto_date的所有日期。

date_aves = (from_date..to_date).map{|dt| [dt, 0]} 

以數組的形式構建日期的完整列表。我們不需要我們平均的日期:

ave_price_dates = ave_prices.collect{|ave_price| ave_price[0]} # build an array of dates 
date_aves.delete_if { |dt| ave_price.dates.index(dt[0]) } # remove zero entries for dates retrieved from DB 
date_aves.concat(ave_prices)  # add the query results 
date_aves.sort_by{|ave| ave[0] } # sort by date 

這很多看起來有點混亂:我認爲它可能更加清爽。我會研究構建一個Hash或Struct而不是留在數組中。


[1]我不是說不要使用SQL - 確實發生情況下的ActiveRecord無法生成最有效的查詢和你依傍find_by_sql。這很好,應該是這樣的,但我認爲你應該嘗試僅將它作爲最後的手段。

0

MySQL有設置返回函數嗎?即函數返回查詢的每一行不同的值?如從PostgreSQL的一個例子,可以這樣做:

select 'foo', generate_series(3, 5); 

這將產生一個結果集包括2列和第3行,其中左欄包含關於各行「富」和右列包含3,4的和5.

因此,假設您在MySQL中有相當於generate_series()的子查詢:您需要的是從此函數到您已有的查詢的LEFT OUTER JOIN。這將確保你看到的每個日期出現在輸出:

SELECT 
    avg(sales_price) as avg, 
    DATE_FORMAT(the_date, '%m-%d-%Y') as date 
FROM (select cast('2008-JAN-01' as date) + generate_series(0, 364) as the_date) date_range 
LEFT OUTER JOIN products_sold on (the_date = created_at) 
WHERE merchant_id = 1 
GROUP BY date; 

可能需要用這個有點擺弄獲得MySQL的語法正確。

2

對於任何此類查詢,您需要找到一種機制來爲每個要報告的日期生成一行表。然後,您將使用您正在分析的數據表對該表進行外部連接。您可能還需要與NVL或COALESCE合作將空值轉換爲零。

困難的部分正在制定如何生成包含您需要分析的範圍的日期列表的(臨時)表。這是DBMS特有的。儘管如此,將日期/時間值映射到單一日期的想法仍然值得商榷。如果您想分析每週銷售量,您需要採取類似的技巧 - 將所有日期映射到ISO 8601日期格式,例如2009年第01周的2009-W01。

此外,您最好將您的DATE格式映射到2009-01-08表示法,因爲那樣您可以使用純字符排序按日期順序進行排序。

2

要乾涸了一下:

ave_prices = Merchant.find(1).products.average(:sales_price, :group => "DATE(created_at)") 
date_aves = (from_date..to_date).map{|dt| [dt, ave_prices[dt.strftime "%Y-%m-%d"] || 0]}