【问题标题】:How to use postgres AVG() function with order clause from Rails如何使用带有来自 Rails 的 order 子句的 postgres AVG() 函数
【发布时间】:2017-01-08 08:11:12
【问题描述】:

我有一个模型CompanyCompany 有很多DailyData。 而DailyDatavolumedate

要计算最近 10 个工作日的平均交易量,我写道:

class Array
  def sum
    inject(0) { |result, el| result + el }
  end

  def mean
    sum.to_d / size
  end
end

company = Company.first
company.daily_data.order(date: :desc).limit(10).pluck(:volume).mean

这段代码运行良好,但我想使用 postgres AVG() 函数。

company.daily_data.select('AVG(volume) as average_volume').order(date: :desc)

此代码以错误结束:

PG::GroupingError: ERROR:  column "daily_data.date" must appear in the GROUP BY clause or be used in an aggregate function

但是如果我把.group(:date) 放在方法链中,sql 会返回多个结果。 如何使用 postgresql AVG() 函数获取最近 10 卷的平均值?

【问题讨论】:

    标签: sql ruby-on-rails ruby postgresql rails-activerecord


    【解决方案1】:

    这样的 ActiveRecord 查询:

    company.daily_data.select('AVG(volume) as average_volume').order(date: :desc)
    

    真的没有多大意义。 avg 是 SQL 中的聚合函数,因此它需要对一组行进行操作。但是您不是告诉数据库如何对行进行分组,而是告诉数据库计算整个表的平均容量,然后按最终结果集中不存在的值对单个值进行排序。

    limit 扔进:

    company.daily_data
           .select('AVG(volume) as average_volume')
           .order(date: :desc)
           .limit(10)
    

    也无济于事,因为limit 是在order 之后应用的,到那时您已经将数据库与您尝试的avg(volume) 混淆了。

    如果我在 SQL 中执行此操作,我可能会使用派生表,例如:

    select avg(volume) as average_volume
    from (
      select volume
      from where_ever...
      where what_ever...
      order by date desc
      limit 10
    ) dt
    

    FROM 子句中的派生表找到您想要的 volumes,然后整个查询平均这 10 个volumes。

    或者,您可以使用子查询来获取感兴趣的行:

    select avg(volume) as average_volume
    from where_ever...
    where id in (
      select id
      from where_ever...
      where what_ever...
      order by date desc
      limit 10
    )
    

    使用 ActiveRecord 实现子查询方法相当简单,如下所示:

    ten_most_recent = company.daily_data.select(:id).order(:date => :desc).limit(10)
    company.daily_data.where(:id => ten_most_recent).average(:volume)
    

    如果您在第二行末尾调用to_sql,您应该会看到类似于子查询 SQL 的内容。

    您还可以使派生表方法与 ActiveRecord 一起使用,但它不太自然。 ActiveRecord 中有一个from method,它将采用 ActiveRecord 查询来构建from (select ...) 派生表结构,但您需要确保手动命名派生表:

    ten_most_recent = company.daily_data.select(:volume).order(:date => :desc).limit(10)
    AnyModelAtAll.from(ten_most_recent, 'dt').average('dt.volume')
    

    您必须对 average 使用字符串参数并包含 dt. 前缀,以防止 ActiveRecord 尝试添加自己的表名。

    当然,您会将所有这些东西隐藏在某个方法中,以便隐藏细节。也许是daily_data 关联的扩展方法:

    has_many :daily_data, ... do
      def average_volume(days)
        #...
      end
    end
    

    这样你就可以说:

    company.daily_data.average_volume(11)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-08
      • 2023-03-16
      • 1970-01-01
      • 1970-01-01
      • 2021-01-21
      • 1970-01-01
      相关资源
      最近更新 更多