【问题标题】:consolidating multiple column counts into a single query with Rails 4 Activerecord使用 Rails 4 Activerecord 将多个列计数合并到一个查询中
【发布时间】:2014-12-15 22:33:56
【问题描述】:

Rails 4.1,Postgres 9.3,部署到 Heroku

我正在尝试减少对数据库的调用次数。

我有一张大表,用于调查,其中包含多个布尔列,例如 role_composerrole_performer 等。

控制器有多个查询,如

@sample = Survey.where(...whatever...)
@Composers = @sample.count("case when role_composer then true end")
...
@Performers = @sample.count("case when role_performer then true end")

这可以正常工作,但会导致对数据库的许多单独查询仅在选择中的表达式不同。有没有办法将其构造为具有多个聚合/计算列的查询?我也有使用 average() 和表达式的查询,但最常见的是 count()。

在 postgres 中这是有效的:

SELECT count(case when role_composer then true end) as "COMPOSERS", count(case when role_performer then true end) as "PERFORMERS" from surveys;

有什么方法可以使用@sample 上的 Activerecord 方法而不是求助于 find_by_sql()?

我尝试了各种方法都没有成功:.count().count().count([array]).select("count(...) as col1, count(...) as col2").select(["count(...) as col1", "count(...) as col2"])

提前感谢您的任何回答。

【问题讨论】:

  • 使用find_by_sql() 可能是将多个计数合并到单个查询中的唯一方法。我不认为 Rails 提供了更多。
  • 我能想到的唯一另一件事是创建一个视图,但这仅适用于查询的子集。例如 count() & average()。
  • 您的select('count(...) as col1, ...') 方法发生了什么?
  • @mu,Activerecord 似乎无法识别聚合并返回关系而不是单个结果。它也不会为别名列名分配方法,因此无法使用关系。 a=@sample.select(["case when role_composer then true end as iscomposer","case when role_teacher then true end as isteacher" ])a.iscomposerNoMethodError: undefined method iscomposer' for #<:activerecord_relation:0x000000045e82e8>` 表达式周围的括号没有帮助
  • 对评论的格式表示歉意。 Markdown 并没有像我想象的那样奏效。

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


【解决方案1】:

如果您记住两件事,您的 .select("count(...) as col1, count(...) as col2") 版本应该可以正常工作:

  1. M.where(...).select(...) 即使查询只返回一行,也会返回多个内容。
  2. inspect 输出中没有出现某些内容并不意味着它不存在。

您在没有 GROUP BY 的情况下进行聚合,因此您只会返回一行。要打开该行,您可以说first

counts = Survey.where(...)
               .select('count(case when role_composer then true end) as composers,  count(case when role_performer then true end) as performers')
               .first

这将为您提供counts 中的Survey 实例。如果您在控制台中查看counts,您会看到如下内容:

#<Survey > 

inspect 输出仅包含列中的值(即 Survey 类知道的内容),但 composersperformers 将在那里。但是,由于 ActiveRecord 不知道它们应该是什么类型,所以它们将作为字符串输出:

composers  = counts.composers.to_i
performers = counts.performers.to_i

如果您去寻找它,您的select 中的所有内容都会在那里。

【讨论】:

  • 我在一个表达式中几乎完全尝试了这个 [where.select.first.colname] 并得到了一个错误。你的信念和榜样让我看得更近,有一个我无法解释的微妙之处。如果您将 .first 从计数的初始定义中排除,它会起作用。包括 .first 会引发异常。除此之外,您完全正确,我将标记为正确答案。谢谢!更新示例代码的正确方法是什么,以便那些发现它的人得到正确指出?顺便说一句,我没有为此使用检查。
  • 你遇到了什么异常?
  • 在 Rails 控制台中,Survey.where().select().first 产生 PG::GroupingError 因为添加了 order by surveys.id 子句。也许是 postgres 适配器中的错误?推迟使用first 或放入一个虚拟的order 方法可以避免它。我想我应该将此报告给 Rails 错误收集器?
  • .first 在 Rails3 中工作正常,我没有设置任何 Rails4 的东西来检查。您是否有默认范围或添加 ORDER BY 的任何内容,或者 ActiveRecord 是否假装知道它在做什么?在任何情况下,您都应该能够在 (counts = Survey.where(...).select(...).to_a.first) 中输入 to_a 以获取 Array#first,而不是 AR 试图做的任何事情。
  • 原来这是 Rails 4 中的设计&在 API 文档中,但来自 Rails 3 我没想到。使用 first() 根据 id 添加订单。我首先通过使用 .order("1").first 添加一个虚拟订单来解决它。更好的解决方案是使用 take() 而不是 first()。感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-22
  • 2018-09-05
  • 2022-01-20
  • 2015-02-02
  • 1970-01-01
  • 2014-06-27
  • 2018-01-31
相关资源
最近更新 更多