SQL CTE scope in query

▼魔方 西西 提交于 2021-01-29 12:34:51

问题


I have tables ChatMessages, ChatGroups and ChatGroupMemberships. A user can be in 0..N groups and in group can be 1..N chat messages. That first message is created once group is initied and it is sort of "alive" ping.

I'm optimizing the way I'm reconstructing the list of a user's conversations. That list is pretty standard and you may know it from any social site:

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

What I did so far was that I simply queried a list of ChatGroupMemberships where userId = X (x being logged in user) and then for each entry in this collection I've selected latest message in that group and then ordered entire list on server (insted of doing that on DB).

I'm creating queries with NHibernate where possible in this project so the function for the above follows:

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

Now I'm pretty sure that this is a pretty ugly solution I did there and as users created more and more chat groups reconstruction time of that conversations list started to took more and more time (I'm doing that via AJAX but still). Now a user is commonly a member of about 50 groups.

Thus a need to optimize this arose and what am I doing now is that I'd like to split loading of that list into smaller batches. As users scroll the list loads entries on fly.

The function I'm working on looks like:

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

} 

I've spent a good few hours learning about CTE's and come up with the following:

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;

This returns last (newest) message by every user in every group. The catch is that my user in not a member of every of these groups so I need to somehow join? this on the ChatGroupMemberships table and check whether there is a row with that user's ID as userId

An illustration of the query above follows:

I might be as well overcomplicating this, my original plan was to execute:

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

Yields:

But here I'd need only the top-most row for each groupId which I tried to do with the query above. Sorry this might be confusing (due to my lack of proper terms / knowledge) and it is a fairly long question.

Should any clarification be needed I'd be more than happy to collaborate.

Lastly, I'm attaching design schemes of the tables

chat messages:

chat group memberships:

chat groups:


回答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;



回答2:


I can't see your pictures, but if your plan was simply to make a most-recent-row version of your 'original plan', then it would be something like:

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

The main point being, that you don't try to make chatGroupMemberships out of chatMessages. You simply do your needed filtering on chatMessages and then use it as you did before.

That being said. If your concern is that 'my user is not a member of every of these groups', I don't know how your original query worked for you in any sense.

Based on your c# code, .Add(Restrictions.Eq("RecipientId", groupId)), I'm wondering if you are perhaps needing to change

g.userId = XXX

to

m.recipientId = XXX

And in any case, why does g.groupId match to m.recipientId. That seems like an error, or if not, very odd.



来源:https://stackoverflow.com/questions/59955002/sql-cte-scope-in-query

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!