Как сгруппировать строки путем перестановки составного ключа из двух столбцов - PullRequest
0 голосов
/ 14 января 2019

Не уверен, что формулировка вопроса так ясна, как должна быть (это лучшее, что я мог придумать), но вот пример, чтобы прояснить ситуацию. У меня есть представление Chats, которое должно обобщить историю разговоров между двумя людьми. Представление состоит из следующих столбцов: Sender, Recipient, Timestamp, LatestMessage и UnreadMessageCount.

Все столбцы представления Chats получены из таблицы Direct_Messages, в которой хранятся сведения об отдельных сообщениях чата, которыми обмениваются пользователи системы. Вот его столбцы: ID, Sender, Recipient, Body, Timestamp, TimeRead (равно нулю, если сообщение не было прочитано получателем). Столбцы Timestamp и LatestMessage представления имеют значения самого последнего прямого сообщения между двумя участниками (самое последнее - Timestamp FWIW).

Проблема действительно связана с тем фактом, что должна существовать только одна перестановка составных столбцов Sender, Recipient в представлении Chats , т. Е. В последнем обмене между двумя участниками , Например, если Гэри отправил сообщение «Привет» Барри, то Барри ответил «Привет» - единственная запись в Chats между этими двумя парнями должна иметь Sender как «Барри», Recipient как 'Gary', Timestamp как отметка времени ответа Барри, LatestMessage как 'Привет' и UnreadMessageCount как количество сообщений, которые Recipient не прочитал.

Я пытался использовать GROUP BY "Sender", "Recipient" OR "Recipient", "Sender", но он просто возвращает два столбца: один сгруппирован по Барри, Гэри; и другой сгруппированный по Гари, Барри

Вот мой код:

SELECT Sender AS Sender,
       Recipient AS Recipient,
       Timestamp AS Timestamp,
       Body AS LatestMessage,
       (SUM(CASE WHEN TimeRead IS NULL THEN 1 ELSE 0 END) ) AS UnreadMessageCount
FROM Direct_Messages
GROUP BY Sender, Recipient OR Recipient, Sender
ORDER BY Timestamp DESC

РЕДАКТИРОВАТЬ: Вот пример данных в таблице Direct_Messages и соответствующий вывод в представлении Chats

С Direct_Messages

ID          Sender  Recipient   Body    Timestamp                   TimeRead
148567984   Gary    Barry       Hi      2018-12-12 23:53:39.487     2018-12-12 23:55:45
1668701120  Barry   Gary        Hello   2018-12-12 23:54:49.326     NULL

Результат в Chats:

Sender  Recipient   Timestamp                 LatestMessage UnreadMessageCount
Gary    Barry       2018-12-12 23:53:39.487   Hi            0
Barry   Gary        2018-12-12 23:54:49.326   Hello         1

Ответы [ 3 ]

0 голосов
/ 14 января 2019

Вы можете получить большую часть того, что хотите, используя MIN() и MAX() с несколькими аргументами. С несколькими аргументами это скалярные функции, которые работают как LEAST() и GREATEST() в других базах данных:

SELECT MIN(Sender, Recipient) AS u1,
       MAX(Sender, Recipient) AS u2,
       MAX(Timestamp) AS Timestamp,
       -- Body AS LatestMessage,
       (COUNT(*) - COUNT(TimeRead)) as UnreadMessageCount
FROM Direct_Messages_cooked
GROUP BY u1, u2
ORDER BY MAX(Timestamp) DESC

Задача - получить новейший метод. Вы можете получить это с условным агрегированием и дополнительным JOIN:

SELECT MIN(dmc.Sender, dmc.Recipient) AS u1,
       MAX(dmc.Sender, dmc.Recipient) AS u2,
       MAX(dmc.Timestamp) AS Timestamp,
       MAX(CASE WHEN dmc.Timestamp = dmc2.Timestamp THEN Body END) AS LatestMessage,
       (COUNT(*) - COUNT(dmc.TimeRead)) as UnreadMessageCount
FROM Direct_Messages_cooked dmc JOIN
     (SELECT MIN(Sender, Recipient) AS u1,
             MAX(Sender, Recipient) AS u2,
             MAX(Timestamp) AS Timestamp
      FROM Direct_Messages_cooked
      GROUP BY u1, u2
     ) dmc2
     ON dmc2.u1 = MIN(dmc.Sender, dmc.Recipient) AND
        dmc2.u2 = MAX(dmc.Sender, dmc.Recipient)
GROUP BY u1, u2
ORDER BY dmc2.Timestamp DESC
0 голосов
/ 16 января 2019

Опираясь на проницательные ответы @ Гордона Линоффа и @dani herrera, мне удалось подправить и найти краткое решение моей конкретной проблемы, хотя в более широком контексте моего первоначального вопроса ответ @ Гордона, судя по моим наблюдениям, чтобы решить проблему более полно. Вот что мне удалось придумать:

SELECT Sender AS Sender,
       Recipient AS Recipient,
       Timestamp AS Timestamp,
       Body AS LatestMessage,
       (COUNT( * ) - COUNT(TimeRead) ) AS UnreadMessageCount
  FROM Direct_Messages
 GROUP BY (
              SELECT MAX(Sender, Recipient) 
          ),
          (
              SELECT MIN(Sender, Recipient) 
          )
 ORDER BY Timestamp DESC
0 голосов
/ 14 января 2019

Вы можете предварительно соединить ваши данные, чтобы сообщения всегда были в одном направлении от каждой комбинации пользователей.

Пример, если ваши данные:

Sender Recipient
A ---> B
B ---> A

Вы меняете его на:

U1     U2
B ---> A (changed)
B ---> A

Как это:

SELECT (case when Sender > Recipient then Sender else Recipient end) AS u1,
       (case when Sender > Recipient then Recipient else Sender end) AS u2,
       Timestamp AS Timestamp,
       Body AS LatestMessage,
       (SUM(CASE WHEN TimeRead IS NULL THEN 1 ELSE 0 END) ) AS UnreadMessageCount
FROM Direct_Messages_cooked
GROUP BY 
     (case when Sender > Recipient then Sender else Recipient end), 
     (case when Sender > Recipient then Recipient else Sender end) 
ORDER BY Timestamp DESC

Примечание: будьте осторожны с производительностью (думаю, это не важно, потому что вы помечены вопросом как sqlite)

Вы можете использовать CTE для предварительного анализа ваших данных и получить более читаемый запрос :

with Direct_Messages_coocked as
(
    select
      (case when Sender > Recipient then Sender else Recipient end) AS U1,
      (case when Sender > Recipient then Recipient else Sender end) AS U2,
      *
    from Direct_Messages
)
SELECT U1 AS U1,
       U2 AS U2,
       Timestamp AS Timestamp,
       Body AS LatestMessage,
       (SUM(CASE WHEN TimeRead IS NULL THEN 1 ELSE 0 END) ) AS UnreadMessageCount
FROM Direct_Messages_coocked
GROUP BY U1, U2
ORDER BY Timestamp DESC
...