2009-01-08 41 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]}