MySQL或Rails在特定日期范围内每天获得AVG的最佳方式
我正在尝试在Rails中制作图表,例如在给定日期范围内每天的平均销售额
假设我有一个product_sold模型,它具有“sales_price”浮点属性。 但是如果特定日期没有销售(例如模型/ 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;
得到这样的结果:
| 平均| 日期| 23 01-03-2009 50 01-05-2009 34 01-07-2009 ......
我想得到的是:
| 平均| 日期| 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执行此操作,还是必须对结果进行后处理以查找日期范围中的哪些日期不在SQL结果集中? 也许我需要一些子选择或IF语句?
感谢大家的帮助。
是否有原因(除了已经提到的日期之外)为什么不使用ActiveRecord中的内置组function? 你好像担心“后期处理”,我认为这并不值得担心。
你在Rails中,所以你应该首先寻找一个Rails解决方案[1]。 我的第一个想法是做类似的事情
Product.average(:sales_price, :group => "DATE(created_at)", :conditions => ["merchant_id=?", 1])
哪个ActiveRecord变成了你描述的SQL。 假设Merchant和Product之间有一个声明的has_many
关联,那么你可能会更好地使用它,所以类似于:
ave_prices = Merchant.find(1).products.average(:sales_price, :group => "DATE(created_at)")
(我希望你对模型的描述为“products_sold”是某种转录错误,顺便说一下 – 如果不是,你的课程命名就有些消息!)
毕竟,你回到了你开始的地方,但是你以更传统的Rails方式到达那里(Rails真的很重视惯例!)。 现在我们需要填补空白。
我假设你知道你的日期范围,让我们说它定义为从from_date
到to_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
。 这很好,它应该是这样的,但我认为你应该尝试只使用它作为最后的手段。
对于任何此类查询,您将需要找到一种机制来生成一个表,其中包含您要报告的每个日期的一行。 然后,您将使用正在分析的数据表对该表进行外连接。 您可能还必须使用NVL或COALESCE将空值转换为零。
困难的部分是研究如何生成(临时)表,其中包含您需要分析的范围的日期列表。 这是DBMS特有的。
但是,您将日期/时间值映射到单个日期的想法仍然存在。 如果你想分析每周销售情况,你需要采取类似的技巧 – 将所有日期映射到ISO 8601日期格式,如2009-W01第01周。
此外,您最好将DATE格式映射到2009-01-08表示法,因为这样您就可以使用普通字符排序按日期顺序排序。
干涸了一下:
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]}
MySQL是否具有设置返回function? 即在查询的每一行返回不同值的函数? 作为PostgreSQL的一个例子,你可以这样做:
select 'foo', generate_series(3, 5);
这将生成一个由2列和3行组成的结果集,其中左列在每行上包含’foo’,右列包含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的语法。
- 尝试使用rake db创建数据库时出现此错误:在Google上创建可以SQL服务器
- Windows上是否支持Datamapper的dm_mysql_adapter gem?
- TypeError:nil不是符号
- Ruby On Rails检索mysql数据库数据’a’不同’b’
- 无法连接:在“读取初始通信数据包”时丢失与MySQL服务器的连接,系统错误:0
- rails COUNT SELECT DISTINCT
- Unicorns吃掉了不考虑池大小的MySQL连接–Rails
- 对于#<ActiveRecord :: ConnectionAdapters :: MysqlAdapter,未定义的方法`explain'
- Rails 3有mysql问题