【问题标题】:Get the latest child messages and also parent messages that are childless获取最新的子消息以及无子的父消息
【发布时间】:2020-05-07 19:44:40
【问题描述】:

以下是Message模型

class Message < ApplicationRecord
  belongs_to :parent_message, class_name: 'Message', optional: true
  has_many :child_messages, foreign_key: :parent_message_id, class_name: "Message"
  has_many :message_participants

  scope :latest_messages_by_participant, -> (user_id) do
    select("DISTINCT ON (parent_message_id) messages.*").
        joins(:message_participants).
        where(message_participants: { user_id: user_id }).
        order("parent_message_id, created_at DESC")
  end
end

message_participants 对每条消息以及发送或接收该消息的各种人都有记录。它上面有一个 user_id。

上述latest_messages_by_participant 范围的问题在于它能够获取所有子消息,但它只获取最后一条父消息。这是因为我们在 parent_message_id 上调用 DISINTICT ON,而对于无子父消息,此值为 NULL,因此它只是在 NULL 上调用 distinct 并返回 1 个值(最后一个无子父消息)。

如何在单个查询中获取所有最新消息,包括最新的子消息和最新的无子父消息?

我正在使用 Rails 6 和 Postgres 11。

PS:我还应该指出一个次要问题,即消息是在 created_at ASC 中返回的。 created_at DESC 能够获取最新的子消息,但不会对整个集合进行排序。我可以通过调用 .reverse 来解决这个问题,但想知道是否也有办法解决这个问题。

【问题讨论】:

  • 实际表定义(CREATE TABLE 语句)和一些示例数据将很有帮助。一个普通的 SQL 语句也可以。
  • 如果您关心性能,我们需要更多信息。考虑此处的说明:stackoverflow.com/tags/postgresql-performance/info。最重要的是:每个用户和消息 id 上平均有多少行?
  • 谢谢@ErwinBrandstetter,目前,性能不是一个考虑因素,但当事情发生变化时肯定会评估。

标签: sql ruby-on-rails postgresql activerecord greatest-n-per-group


【解决方案1】:

DISTINCT ONORDER BY 中使用COALESCE 表达式。
并在外部查询中对结果进行排序以获得所需的排序顺序:

SELECT *
FROM  (
   SELECT DISTINCT ON (COALESCE(m.parent_message_id, m.id))
          m.*
   FROM   messages m
   JOIN   message_participants mp ON ...
   WHERE  mp.user_id = ...
   ORDER  BY (COALESCE(m.parent_message_id, m.id)), created_at DESC
   )
ORDER  BY created_at;

参见(附详细说明):

性能?

对于每个用户和消息 ID 很少 行,DISTINCT ON 通常是最快的解决方案之一。对于 many 行,有(很多)更快的方法。取决于更多信息,如评论。

【讨论】:

    【解决方案2】:

    我相信您需要在您的 distinct on 和 order by 中添加一个 coalese,以便在 parent_message_id 为空时选择消息的 id

    select("DISTINCT ON (parent_message_id) messages.*")
    ...
    order("parent_message_id, created_at DESC")
    

    需要转化为

    select("DISTINCT ON (COALESCE(parent_message_id, messages.id)) messages.*")
    ...
    order("COALESCE(parent_message_id, messages.id), created_at DESC")
    

    现在,您还没有提供示例数据库表和预期或完整的模型定义,所以我推断出很多事情。这是最小的表定义(据我了解),在我建议的修改后由 AR 生成的原始 sql 查询 [这是我们想要的查询,给出下面的架构] 和结果。

    设置

    CREATE TABLE messages (
      id int primary key
    , parent_message_id int references messages(id)
    , created_at timestamp default current_timestamp
    ); 
    INSERT INTO messages (id, parent_message_id) values 
      (1, NULL) -- parent message with children
    , (2, 1)
    , (3, 1)
    , (4, NULL) -- parent message without children
    , (5, NULL) -- another parent message without children
    ;
    CREATE TABLE message_participants (
      user_id int
    , message_id int references messages(id)
    )
    INSERT INTO message_participants values (1, 1), (2, 2), (3, 3), (1, 4), (2, 5);
    

    为我们提供最后一条父或子消息的原始 SQL 查询:

    SELECT DISTINCT ON (COALESCE(parent_message_id, messages.id)) messages.*
    FROM messages
    JOIN message_participants ON message_participants.message_id = messages.id
    WHERE message_participants.user_id = ? -- replace by user_id
    ORDER BY COALESCE(parent_message_id, messages.id), created_at DESC
    

    结果

    给定user_id = 1,上面的查询返回结果:

     id | parent_message_id |         created_at
    ----+-------------------+----------------------------
      1 |                   | 2020-05-11 13:50:00.857589
      4 |                   | 2020-05-11 13:50:00.857589
    (2 rows)
    

    给定user_id = 2,上面的查询返回结果:

     id | parent_message_id |         created_at
    ----+-------------------+----------------------------
      2 |                 1 | 2020-05-11 13:50:00.857589
      5 |                   | 2020-05-11 13:52:01.261975
    (2 rows)
    

    对整体结果进行排序:

    created_at DESC 能够获取最新的子消息,但不会对整个集合进行排序。我可以通过调用 .reverse 来解决这个问题,但想知道是否也有办法解决这个问题。

    要在数据库中进行排序,可以将上述查询包装在一个 cte 中

    示例:

    WITH last_messages AS (
    SELECT DISTINCT ON (COALESCE(parent_message_id, messages.id)) messages.*
    FROM messages
    JOIN message_participants ON message_participants.message_id = messages.id
    WHERE message_participants.user_id = 2
    ORDER BY COALESCE(parent_message_id, messages.id), created_at DESC
    )
    SELECT * FROM last_messages ORDER BY created_at;
    

    但是,我不能 100% 确定这将如何在 AR 中表达

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-08
      • 2011-09-09
      • 2017-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多