【问题标题】:How do I select previous row in active record query?如何在活动记录查询中选择上一行?
【发布时间】:2018-08-23 14:16:06
【问题描述】:

我是 Rails 新手,我的第一个项目是为 Redmine 制作插件。我需要找出问题状态更改之间经过了多长时间。

我通过对每个状态更改执行以下操作来计算:

  • 从创建日期减去之前状态更改的创建日期
  • 如果这是问题的第一次状态更改,请减去问题的创建日期。

我在 PostgreSQL 中使用lag 函数编写了以下代码来访问前一行,这给了我想要的输出。

SELECT issues.subject, journals.id,
COALESCE(
(journals.created_on - lag(journals.created_on) 
 OVER (PARTITION BY journals.journalized_id ORDER BY journals.created_on)),
journals.created_on - issues.created_on
) AS time_elapsed
FROM journals 
INNER JOIN journal_details ON journal_details.journal_id = journals.id
INNER JOIN issues ON issues.id = journals.journalized_id 
WHERE journal_details.prop_key = 'status_id';

...在 Rails 中我做到了...

Journal.joins(:details, :issue)
.where(:journal_details => {prop_key: :status_id})
.select("issues.subject, journals.id")

但是,为了选择上一行,我不能使用 Postgres 的函数,因为我需要此代码与数据库无关。

搞了几个小时,我只能想出一个选择前一行的子查询的想法:

subquery = "SELECT j.created_on FROM journals AS j 
INNER JOIN journal_details ON journal_details.journal_id = j.id 
WHERE journal_details.prop_key = 'status_id' 
AND j.journalized_id = journals.journalized_id 
AND j.id < journals.id ORDER BY j.id DESC LIMIT 1"

Journal.joins(:details, :issue)
.where(:journal_details => {prop_key: :status_id})
.select("issues.subject, journals.id, 
COALESCE(journals.created_on - (#{subquery}), 
(journals.created_on - issues.created_on)) AS time_elapsed")

这不是一个可行的选项,因为它对每一行执行一个多选择查询,我需要一个性能有效的解决方案。 (也不是 DRY。)

所以我的问题是,如何使用活动记录查询界面(如果可以的话,当然)来选择上一行?或者至少,解决这个问题的最佳实践/rails 方式是什么?

【问题讨论】:

  • "为了选择上一行,我不能使用 Postgres 的函数,因为我需要此代码与数据库无关。" 函数 LAG() 也适用于 MySQL 8.0 等最流行的 DBMS . Oracle 10g Release 2 (10.2)+(我相信可以更早),PostgreSQL 8.4+,SQL Server(MSSQL) 版本 2012+,SQLite 在我看来使用 LAG() 它与数据库无关
  • 此外,如果您担心与数据库无关.. LIMIT 仅适用于 MySQL、MariaDB(MySQL 的分支)和 PostgreSQL。所以你不用担心使用LAG() 不是数据库不可知论者
  • 如果您知道要在 SQL 中进行的查询,您也可以执行 raw query,或者发布一个您无法完全融入 ActiveRecord 语法的有效查询,因为这可能会得到更多有针对性的帮助。您也可以考虑在dba.stackexchange.com 上询问特定于 SQL(而不是 ORM)的问题。

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


【解决方案1】:

抱歉,如果我在这里忽略了一些复杂性,但似乎最简单的解决方案就是在 rails 中跟踪以前的记录?

previous_detail = nil

journal.journal_details.each do |detail|
  if previous_detail
    # do processing with current and prevous details
  else
    # do progessing with current detail and journal
  end
  previous_detail = detail
end

【讨论】:

  • 我在第一次尝试时尝试了这个,但如果我在 rails 中进行处理(计算时间),那么我将无法使用该结果数据进行排序或分组。我的意思是我也可以在应用层做这些,但它会慢得多。这就是为什么我试图在数据库端计算它并将其作为查询结果的列。
  • 是的,我明白了。一种想法是您的插件可以迁移新字段,并且可以通过 before_create 回调填充列,并且有一个初始种子来填充现有记录中的新字段,最终它会使查询更加高效。跨度>
猜你喜欢
  • 2017-10-22
  • 1970-01-01
  • 1970-01-01
  • 2021-10-30
  • 1970-01-01
  • 2014-02-14
  • 2014-02-25
  • 1970-01-01
  • 2020-02-28
相关资源
最近更新 更多