问题
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