【问题标题】:Rails 5 complex query with multiple sub queries带有多个子查询的 Rails 5 复杂查询
【发布时间】:2018-11-24 20:32:14
【问题描述】:

我在使用 postgres 的 Rails 5 上,我有一个用户表和一个报告表。用户有很多报表,每天都需要创建这些报表。我想获取所有未归档、今天未完成报告的用户,并显示昨天的报告说明(如果有)。

以下是模型:

用户模型

# == Schema Information
#
# Table name: users
#
#  id         :bigint(8)        not null, primary key
#  name       :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#  archived   :boolean          default(FALSE)
#

class User < ApplicationRecord
  has_many :reports
end

报告模型

# == Schema Information
#
# Table name: reports
#
#  id         :bigint(8)        not null, primary key
#  notes      :text
#  created_at :datetime         not null
#  updated_at :datetime         not null
#  user_id    :bigint(8)
#

class Report < ApplicationRecord
  belongd_to :user
end

这是我想要从这个查询中得到的一个例子:

用户表

------------------------------------------------------------------------------------
| id |  name  | archived |         created_at         |         updated_at         |
------------------------------------------------------------------------------------
| 1  |  Jonn  |  false   | 2018-05-11 00:01:36.124999 | 2018-05-11 00:01:36.124999 |
------------------------------------------------------------------------------------
| 2  |  Sam   |  false   | 2018-05-11 00:01:36.124999 | 2018-05-11 00:01:36.124999 |
------------------------------------------------------------------------------------
| 3  | Ashley |  true    | 2018-05-11 00:01:36.124999 | 2018-05-11 00:01:36.124999 |
------------------------------------------------------------------------------------

报告表(想象一下这份报告是昨天的)

--------------------------------------------------------------------------------------
| id |  user_id  |  notes  |         created_at         |         updated_at         |
--------------------------------------------------------------------------------------
| 1  |     1     | Nothing | 2018-06-13 16:32:05.139284 | 2018-06-13 16:32:05.139284 |
--------------------------------------------------------------------------------------

愿望输出:

-------------------------------------------------------------------------------------------------------
| id |  name  | archived |         created_at         |         updated_at         | yesterdays_notes |
-------------------------------------------------------------------------------------------------------
| 1  |  Jonn  |  false   | 2018-05-11 00:01:36.124999 | 2018-05-11 00:01:36.124999 |     Nothing      |
-------------------------------------------------------------------------------------------------------
| 2  |  Sam   |  false   | 2018-05-11 00:01:36.124999 | 2018-05-11 00:01:36.124999 |       NULL       |
-------------------------------------------------------------------------------------------------------

我能够通过编写原始 SQL 获得所需的查询结果,但在尝试将其转换为活动记录查询时遇到了很多问题。这是使用scenic gem 的合适场景吗?

这是原始 SQL 查询:

SELECT u.*, (
  SELECT notes AS yesterdays_notes
  FROM reports AS r
  WHERE r.created_at >= '2018-06-13 04:00:00'
  AND r.created_at <= '2018-06-14 03:59:59.999999'
  AND r.user_id = u.id
)
FROM users AS u
WHERE u.archived = FALSE
AND u.id NOT IN (
  SELECT rr.user_id
  FROM reports rr
  WHERE rr.created_at >= '2018-06-14 04:00:00'
);

【问题讨论】:

  • 我没有经验 scenic 建议或劝阻,尽管在转换为 AR 时有什么问题。这也可以转换为连接查询。

标签: ruby-on-rails postgresql activerecord


【解决方案1】:

我会这样做:

首先,选择昨天创建的最新报告的所有活动用户,并将其分配给一个 var:

users = User.where(archived: false).joins(:reports)
.where.not('DATE(reports.created_at) IN (?)', [Date.today]) 
.where('DATE(reports.created_at) IN (?)', [Date.yesterday])
.select('users.id', 'users.name', 'notes')

现在用户 var 将拥有 .select 中列出的属性,因此您可以调用 users.map(&amp;:notes) 以查看节点列表,包括 nil / null 注释。

另一个可能派上用场的技巧是能够为您在.select 中列出的属性设置别名。例如,如果您想将users.id 存储为id,您可以使用

...
.select('users.id as id', 'users.name', 'reports.notes')

您可以致电users.map(&amp;:attributes) 来查看这些最终结构的外观

有关可用 Active Record 查询的更多信息,请参阅here

【讨论】:

  • 昨晚我没有机会尝试这个,但我可能会在午休时间试一试。仅查看您的代码示例,我认为这行不通,因为我还只需要获取今天尚未提交报告的用户。我会告诉你的。
  • @Nitsew。添加了一个额外的where 子句以过滤掉今天生成的报告
  • 我认为这实际上可行,我认为我唯一需要改变的就是使站立会议成为左外连接。稍后我会跟进你。谢谢!
【解决方案2】:
users = User.joins(:reports).where("(reports.created_at < ? OR reports.created_at BETWEEN ? AND ?) AND (users.archived = ?)", Date.today.beginning_of_day, Date.yesterday.beginning_of_day, Date.yesterday.end_of_day, false).select("users.id, users.name, reports.notes").uniq

用户将以#&lt;ActiveRecord::Relation [....]返回

可能连接返回重复记录所以使用uniq

过滤报告

reports.created_at < Date.today.beginning_of_day OR yesterday.beginning_of_day > reports.created_at < Yesterday.end_of_day

这是必需的报告为“今天未完成报告,并显示昨天的报告说明(如果有)”

还有users.archived = false

【讨论】:

  • 我相信它在 Rails 5 中的 distinct。否则很好回答
  • 我认为这不会给我想要的结果,因为我只需要今天没有报告的用户。我会在今天有空的时候玩它。
  • @Nitsew 请查看更新的答案,让我知道以获得进一步的指导。谢谢
  • 嘿@Gabbar,我在这里发布了答案,您可以查看。由于复杂性,我认为最好的方法是在这种情况下使用数据库视图。这也将变得更加困难,因为用户不会在周六或周日发布报告,所以如果当前日期是周一,我将不得不添加一个边缘案例,而不是总是获得昨天的报告,获得上周五报告。感谢您的帮助,它确实帮助我缩小了使用哪种解决方案的范围,以便能够查看其他方法并获得反馈。
【解决方案3】:

我能够从此处发布的 2 个答案中获得所需的结果,但是,在进行了更多研究之后,我认为使用 scenic gem 的数据库视图是此处的合适方法,因此我将继续前进.

感谢您的意见!如果您想了解我的决定背后的一些原因,这篇 stackoverflow 帖子很好地总结了它:https://stackoverflow.com/a/4378166/2909095

这是我最终使用scenic gem 的结果。我稍微更改了实际查询以更好地满足我的需求,但它解决了这个答案:

型号

# == Schema Information
#
# Table name: missing_reports
#
#  id                  :bigint(8)
#  name                :string
#  created_at          :datetime
#  updated_at          :datetime
#  previous_notes      :text
#  previous_notes_date :datetime

class MissingReport < ApplicationRecord
end

数据库视图

SELECT u.*, rs.notes AS previous_notes, rs.created_at AS previous_notes_date
FROM users AS u
LEFT JOIN (
  SELECT r.*
  FROM reports AS r
  WHERE r.created_at < TIMESTAMP 'today'
  ORDER BY created_at DESC
  LIMIT 1
) rs ON rs.user_id = u.id
WHERE u.archived = FALSE
AND u.id NOT IN (
  SELECT rr.user_id
  FROM standups rr
  WHERE rr.created_at >= TIMESTAMP 'today'
);

用法

def index
  @missing_reports = MissingReport.all
end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-22
    • 1970-01-01
    • 2013-03-19
    • 2017-05-19
    • 2013-05-22
    • 2020-03-31
    • 1970-01-01
    相关资源
    最近更新 更多