获取最新的子消息以及没有子消息的父消息

问题描述 投票:1回答:2

以下是消息模型

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上调用distance,并返回1值(最后一个无子代的父消息)。

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

我正在使用Rails 6和Postgres 11。

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

sql ruby-on-rails postgresql activerecord greatest-n-per-group
2个回答
1
投票

[我相信您需要在[&0]中添加唯一的拼写,以便在id为空时选择消息的parent_message_id

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

需要转换为

select("DISTINCT ON (COALESCE(parent_message_id, id)) messages.*")
...
order("COALESCE(parent_message_id, 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);

向我们提供最后一条父级或子级消息的RAW 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中表示出来


1
投票

COALESCEDISTINCT ON中使用ORDER BY表达式。然后在外部查询中对结果进行排序,以获得所需的排序顺序:

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

请参阅(有详细说明):

© www.soinside.com 2019 - 2024. All rights reserved.