【问题标题】:Ruby Rails Complex SQL with aggregate function and DayOfWeek具有聚合函数和 DayOfWeek 的 Ruby Rails 复杂 SQL
【发布时间】:2011-06-20 10:51:21
【问题描述】:

Rails 2.3.4

我已经搜索了谷歌,并没有找到我的困境的答案。

对于这个讨论,我有两个模型。用户和条目。用户可以有多个条目(每天一个)。

条目具有值和 sent_at 日期。

我想按星期几查询并显示用户条目的平均值。因此,如果用户输入了过去 3 周的值,我想显示周日、周一等的平均值。在 MySQL 中,这很简单:

SELECT DAYOFWEEK(sent_at) as day, AVG(value) as average FROM entries WHERE user_id = ? GROUP BY 1

该查询将返回 0 到 7 条记录,具体取决于用户至少拥有一个条目的天数。

我查看了 find_by_sql,但是在搜索 Entry 时,我不想返回 Entry 对象;相反,我需要一个最多 7 天和平均值的数组...

另外,我有点担心它的性能,因为我们想在用户登录时将它加载到用户模型中,以便它可以显示在他们的仪表板上。欢迎任何建议/指点。我对 Rails 比较陌生。

【问题讨论】:

  • 经过一些进一步的研究和调整,我发现这会起作用: @ee=Entry.all(:select => "dayofweek(entries.sent_at) as Day,avg( entry.value) as Value", :group => "dayofweek(sent_at)", :conditions => ['user_id = ?', self.id]) 虽然看起来很难看,而且可能效率不高.我会检查我得到的答案,看看它们是否更好。

标签: sql ruby-on-rails aggregate dayofweek


【解决方案1】:

您可以直接查询数据库,无需使用实际的 ActiveRecord 对象。例如:

ActiveRecord::Base.connection.execute "SELECT DAYOFWEEK(sent_at) as day, AVG(value) as average FROM entries WHERE user_id = #{user.id} GROUP BY DAYOFWEEK(sent_at);"

这将为您提供 MySql::Result 或 MySql2::Result,然后您可以在此枚举上使用每个或全部来查看结果。

至于缓存,我建议使用 memcached,但任何其他 Rails 缓存策略也可以。 memcached 的好处是您可以让您的缓存在一定时间后过期。例如:

结果 = Rails.cache.fetch('user/#{user.id}/averages', :expires_in => 1.day) 做 # 你的 sql 查询和结果放在这里 结尾

这会将您的结果放入 memcached 中一天的密钥“user//averages”下。例如,如果您是 id 为 10 的用户,则您的平均值将在 memcached 中的“user/10/average”下,并且下次您执行此查询时(在同一天内),将使用缓存版本而不是实际命中数据库。

【讨论】:

  • ActiveRecord 直接数据库查询确实有效。使用直接数据库查询而不是我上面使用的 Entry.all 查询(在对我原始问题的评论中)有优势(开销)吗?
  • 这真的取决于你想如何处理你的结果。这只是偏好和格式差异。在我看来,我不喜欢将一个对象与来自聚合查询的数据混合在一起。当我可以使用字符串和数字数组代替时,没有理由用不相关的数据实例化一个 Entry 对象。
  • 我同意(关于不使用不相关数据实例化对象)。我想我会使用直接查询的方法。感谢您的帮助。
【解决方案2】:

未经测试,但这样的东西应该可以工作:

@user.entries.select('DAYOFWEEK(sent_at) as day, AVG(value) as average').group('1').all

注意:当您使用select 明确指定列时,返回的对象是只读的。 Rails 无法可靠地确定哪些列可以修改,哪些列不能修改。在这种情况下,您可能不会尝试修改选定的列,但您也可以通过结果对象修改您的 sent_atvalue 列。

查看ActiveRecord Querying Guide,以对新用户友好的格式详细了解此处发生的情况。哦,如果该查询不起作用,请回帖,以便其他可能偶然发现此问题的人可以看到(我可能会更新)。


由于 entries 返回一个数组,这将不起作用,我们可以尝试使用 join 代替:

User.where(:user_id => params[:id]).joins(:entries).select('...').group('1').all

再说一次,我不知道这是否可行。通常你可以在joins 之后指定where,但我没有看到select 在那里组合。这里有一个棘手的问题是select 可能会完全消除返回有关user 的任何数据。避免使用 find_by_* 方法可能更有意义,而是在 Entry 模型中编写一个方法,该方法只使用 select_all (docs) 调用您的查询并跳过关联映射。

【讨论】:

  • 我真的很喜欢这个外观,但它给出了一个错误:ArgumentError: wrong number of arguments (1 for 0)
  • 啊,是的,我认为@user.entries 将返回一个数组,而不是ActiveRecord::Relation。正在使用另一个可能的选项进行更新...
  • 使用 Rails 2.3.4。 .where 是 Rails 3 的东西吗? NoMethodError: #<0x1033aeb08>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-22
  • 2014-11-27
相关资源
最近更新 更多