SQL Область видимости CTE в запросе - PullRequest
0 голосов
/ 28 января 2020

У меня есть таблицы ChatMessages, ChatGroups и ChatGroupMemberships. Пользователь может быть в 0..N группах и в группе может быть 1..N сообщений чата. Это первое сообщение создается после того, как группа инициирована, и это своего рода «живой» пинг.

Я оптимизирую способ восстановления списка разговоров пользователя. Этот список довольно стандартен, и вы можете знать его по любому социальному сайту:

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

Возвращает последнее (новейшее) сообщение для каждого пользователя в каждой группе. Подвох в том, что мой пользователь не входит в каждую из этих групп, поэтому мне нужно каким-то образом присоединиться? в таблице ChatGroupMemberships и проверить, есть ли строка с идентификатором этого пользователя как userId

Ниже приведен пример запроса:

enter image description here

Возможно, я слишком усложнил это, мой первоначальный план состоял в следующем:

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

Выход:

img2

Но здесь мне понадобится только самая верхняя строка для каждого идентификатора группы, который я пробовал делать с запросом выше. Извините, это может сбить с толку (из-за отсутствия у меня правильных терминов / знаний), и это довольно длинный вопрос.

Если понадобится какое-либо разъяснение, я был бы более чем рад сотрудничеству.

Наконец, я прилагаю схемы оформления таблиц

сообщения чата:
chat messages

членство в группах чата:
enter image description here

группы чата:
enter image description here

Ответы [ 2 ]

1 голос
/ 28 января 2020

Я не вижу ваши фотографии, но если бы вы планировали сделать самую последнюю версию вашего «первоначального плана», то это было бы что-то вроде:

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

главное, что вы не пытаетесь сделать chatGroupMemberships из chatMessages. Вы просто выполняете необходимую фильтрацию на chatMessages, а затем используете ее, как делали раньше.

Как говорится. Если вы обеспокоены тем, что «мой пользователь не является членом каждой из этих групп», я не знаю, как ваш исходный запрос работал для вас в каком-либо смысле.

Исходя из вашего c# кода, .Add(Restrictions.Eq("RecipientId", groupId)), мне интересно, возможно, вам нужно изменить

g.userId = XXX

на

m.recipientId = XXX

И в в любом случае, почему g.groupId соответствует m.recipientId. Это похоже на ошибку или, если нет, очень странно.

1 голос
/ 28 января 2020
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;
...