以下是消息模型
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来解决此问题,但想知道是否还有一种方法可以解决该问题。
[我相信您需要在[&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);
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中表示出来
在COALESCE
和DISTINCT 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;
请参阅(有详细说明):