SQL 查询中的 CTE 范围

SQL CTE scope in query

我有 tables ChatMessagesChatGroupsChatGroupMemberships。一个用户可以在 0..N 个组中,组中可以有 1..N 个聊天消息。第一条消息是在组启动后创建的,它有点像 "alive" ping。

我正在优化重建用户对话列表的方式。该列表非常标准,您可能从任何社交网站都知道它:

| Chat with User X -> [last message in that chat group]
| Group chat named ABC -> [same]

到目前为止我所做的是我简单地查询了 ChatGroupMemberships 的列表,其中 userId = X (x 正在登录用户)然后对于这个集合中的每个条目我都选择了最新消息在该组中,然后在服务器上订购整个列表(而不是在数据库上这样做)。

我在这个项目中尽可能使用 NHibernate 创建查询,因此上面的函数如下:

public ChatMessage GetLastMessageInGroup(int groupId)
{
    return session.CreateCriteria<ChatMessage>()
    .AddOrder(Order.Desc("Date"))
    .Add(Restrictions.Eq("RecipientId", groupId))
    .SetMaxResults(1)
    .List<ChatMessage>().FirstOrDefault();
 }

现在我很确定这是我在那里做的一个非常丑陋的解决方案,随着用户创建越来越多的聊天组,该对话列表的重建时间开始花费越来越多的时间(我通过AJAX 但仍然)。现在一个用户通常是大约 50 个组的成员。

因此需要对此进行优化,我现在正在做的是将该列表的加载拆分为更小的批次。当用户滚动时,列表会动态加载条目。

我正在处理的函数如下所示:

public List<ChatGroupMembershipTimestamp> GetMembershipsWhereUserId(int userId, int take, int skip)
{

} 

我花了好几个小时学习 CTE,并得出以下结论:

WITH ChatGroupMemberships AS
( 
    SELECT p.Date, p.RecipientId, p.RecipientType, p.Id, p.userId
    ROW_NUMBER() OVER(PARTITION BY p.RecipientId, p.userId ORDER BY p.Id DESC)
    AS rk FROM ChatMessages p
)

SELECT s.date, s.recipientId as groupId, s.recipientType as groupType, s.userId
FROM ChatGroupMemberships s

WHERE s.rk = 1 
Order by s.date desc;

每个组中每个用户的returns 最后一条(最新)消息。问题是我的用户不是每个这些组的成员所以我需要以某种方式 join?ChatGroupMemberships table 上检查是否有该用户 ID 为 userId

的行

上面的查询说明如下:

我可能也把这个复杂化了,我原来的计划是执行:

select m.id as messageId, groupId, date
from ChatGroupMemberships g 
join ChatMessages m on g.groupId = m.recipientId
where g.userId = XXX <-- user's id
order by m.date desc

产量:

但是在这里我只需要每个 groupId 的最顶行,我试图用上面的查询来做。 抱歉,这可能会造成混淆(由于我缺乏适当的术语/知识),而且这是一个相当长的问题。

如果需要任何说明,我非常乐意合作。

最后附上tables

的设计方案

聊天消息:

聊天组成员资格:

聊天组:

WITH messages_ranked AS
( 
    SELECT p.Date, p.RecipientId, p.RecipientType, p.Id, p.userId
    ROW_NUMBER() OVER(PARTITION BY p.RecipientId, p.userId ORDER BY p.Id DESC) AS rk 
    FROM ChatMessages p
    JOIN ChatGroupMemberships g 
    on p.recipientId = g.groupId
    where g.user_id = XXX
)

SELECT s.date, s.recipientId as groupId, s.recipientType as groupType, s.userId
FROM messages_ranked s

WHERE s.rk = 1 
Order by s.date desc;

我看不到你的照片,但如果你的计划只是制作你的 'original plan' 的最新版本,那么它会是这样的:

with chatMessages as ( 

    select  m.*,

            rk = row_number() over(
                partition by m.recipientid, m.userid 
                order by m.id desc
            )

    from    chatMessages m

)

select      m.id as messageId, g.groupId, m.date
from        chatGroupMemberships g 
join        chatMessages m on g.groupId = m.recipientId and m.rk = 1
where       g.userId = XXX <-- user's id
order by    m.date desc

要点是,您不要试图从 chatMessages 中得到 chatGroupMemberships。您只需对 chatMessages 进行所需的过滤,然后像以前一样使用它。

话虽这么说。如果您担心的是 'my user is not a member of every of these groups',我不知道您的原始查询在任何意义上都对您有用。

根据您的 C# 代码,.Add(Restrictions.Eq("RecipientId", groupId)),我想知道您是否可能需要更改

g.userId = XXX

m.recipientId = XXX

无论如何,为什么 g.groupId 匹配 m.recipientId。这似乎是一个错误,如果不是,也很奇怪。