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_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 的情况确实会发生。没关系,应该是这样,但我认为您应该尝试仅将其用作最后的手段。

【讨论】:

【参考方案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 的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

Mysql列表日期范围之间的日期,但仅限于一周中的特定日期

如何将活动记录类特定属性映射到rails中相应的通用关注方法

检查 MYSQL 表行中是不是存在特定时间范围

Rails 3:如何在特定时区获取今天的日期?

在mysql和php中按日期范围分组

oracle 查询一段时间内每一天的统计数据sql怎么写