【问题标题】:SQL CTE scope in query查询中的 SQL CTE 范围
【发布时间】:2020-05-14 05:41:58
【问题描述】:

我有表 ChatMessages、ChatGroups 和 ChatGroupMemberships。一个用户可以在 0..N 个群组中,而在群组中可以有 1..N 条聊天消息。第一条消息是在组启动后创建的,它有点像“活着”的 ping。

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

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

到目前为止,我所做的是我只是查询了ChatGroupMemberships 的列表,其中userId = X(x 正在登录用户),然后对于此集合中的每个条目,我选择了该组中的最新消息,然后订购服务器上的整个列表(建议在 DB 上执行此操作)。

我在此项目中尽可能使用 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;

这将返回每个组中每个用户的最后(最新)消息。问题是我的用户不是所有这些组的成员,所以我需要以某种方式加入? ChatGroupMemberships 表上并检查是否存在该用户 ID 为 @987654342 的行@

上面的查询说明如下:

我可能过于复杂了,我最初的计划是执行:

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 的最上面一行。 抱歉,这可能会造成混淆(由于我缺乏适当的术语/知识),这是一个相当长的问题。

如果需要任何澄清,我非常乐意合作。

最后,我附上表格的设计方案

聊天消息:

聊天组成员:

聊天群:

【问题讨论】:

  • 如果我理解这一点,您可以在 CTE 中添加连接,问题是您如何传递用户 ID(如果您能够将值从应用程序传递给 SQL)
  • 我可以将值传递给 sql,这没问题。你介意用那个加入发布答案吗?真的很有帮助,谢谢
  • 此时,我的建议是创建一个Minimal Reproducible Example,其中包含可消耗形式的示例数据。

标签: sql sql-server common-table-expression


【解决方案1】:
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;

【讨论】:

  • 我尝试了类似的方法,但结果如下:i.postimg.cc/3r6BPhwD/noluck.png
  • aah CTE别名不能和里面使用的表名一样,改名就好了
  • 将此标记为正确答案,它实际上回答了我的问题。谢谢你的时间亚历克斯!
【解决方案2】:

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

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 进行所需的过滤,然后像以前一样使用它。

话虽如此。如果您担心“我的用户不是所有这些组的成员”,那么我不知道您的原始查询在任何意义上是如何为您工作的。

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

g.userId = XXX

到

m.recipientId = XXX

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

【讨论】:

  • 与 Alexes 回答中的错误相同。这是在我这边吗?抱歉,以前从未遇到过这种情况。 i.postimg.cc/cJp3Fxhs/noluck2.png
  • 进行了编辑,猜测您可能正在尝试过滤收件人,而不是用户。是这样吗?
  • message 中的recipientId 是外键,引用ChatGroup 中的“id”列。由于我不知道这个rec​​ipientId,我只知道userId(当前已登录)
  • Alexes 的回答帮助了我更多,但我赞成你的努力和好的建议。感谢,我真的很重视你的帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多