MySQL for Rails 在特定日期范围内每天获得 AVERAGE 的最佳方法
Posted
技术标签:
【中文标题】MySQL for Rails 在特定日期范围内每天获得 AVERAGE 的最佳方法【英文标题】:Best way in MySQL or Rails to get AVG per day within a specific date range 【发布时间】:2009-01-08 03:55:53 【问题描述】:我正在尝试在 Rails 中制作图表,例如给定日期范围内每天的平均销售额
假设我有一个 products_sold 模型,它有一个“sales_price”浮动属性。但是如果某一天没有销售(例如模型/数据库中没有销售),我想简单地返回 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 语句?
感谢大家的帮助。
【问题讨论】:
【参考方案1】:您是否有理由(除了已经提到的日期)为什么不使用 ActiveRecord 中的内置组功能功能?您似乎担心“后处理”,我认为这不是真正值得担心的事情。
您在 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
的情况确实会发生。没关系,应该是这样,但我认为您应该尝试仅将其用作最后的手段。
【讨论】:
【参考方案2】:对于任何此类查询,您都需要找到一种机制来生成一个表格,其中包含您要报告的每个日期的一行。然后,您将对该表与您正在分析的数据表进行外部连接。您可能还需要使用 NVL 或 COALESCE 将空值转换为零。
困难的部分是如何生成(临时)表,其中包含您需要分析的范围的日期列表。那是 DBMS 特有的。
不过,您将日期/时间值映射到单个日期的想法是正确的。如果您想分析每周销售额,您需要使用类似的技巧 - 将所有日期映射到 ISO 8601 日期格式,例如第 1 周的 2009-W01。
此外,您最好将 DATE 格式映射到 2009-01-08 表示法,因为这样您就可以使用纯字符排序按日期顺序进行排序。
【讨论】:
【参考方案3】:稍微干一点:
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]
【讨论】:
【参考方案4】:MySQL 有返回集合的功能吗? IE。在查询的每一行上返回不同值的函数?作为 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 的语法。
【讨论】:
以上是关于MySQL for Rails 在特定日期范围内每天获得 AVERAGE 的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章