SQL - схема сообщения - необходимо найти существующую ветку сообщений с учетом набора пользователей - PullRequest
0 голосов
/ 28 марта 2012

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

Существует 2 сценария отправки сообщения:

Отправить в поток: При просмотре потока сообщение отправляется непосредственно в этот поток, поэтому идентификатор потока известен. (не проблема)

Отправить получателям: Пользователь создает новое сообщение и указывает набор получателей с нуля. Я только хочу создать новую тему, если она еще не существует между этими пользователями, вот где я застрял. Мне нужен запрос, который найдет существующий threadID с учетом набора пользователей. Таблица ThreadMembers отображает пользователей в потоки. Это вообще возможно? Или мне нужно изменить мои таблицы?

Мои таблицы:

Тема:
ID потока (id)
lastSent (отметка времени)

ThreadMembers:
threadFK (внешний ключ для потока)
userFK (внешний ключ для пользователя)

Сообщения:
threadFK (внешний ключ для потока)
senderFK (внешний ключ для пользователя)
msgID (id)
msgDate (метка времени)
msgText (текст)

Большое спасибо!

Ответы [ 4 ]

1 голос
/ 28 марта 2012

Я не рекомендую это слегка, но я думаю, что вам лучше немного денормализовать, добавив столбец к Thread, который содержит отсортированный через запятую список внешних ключей к User. И индексировать этот столбец. Тогда вашему приложению просто нужно отсортировать идентификаторы пользователя отправителя + всех получателей, присоединить отсортированный список к запятой и найти запись Thread.

С & ndash; по определению & mdash; список пользователей в потоке никогда не меняется, вам просто нужно правильно заполнить эти элементы при вставке, и вам не нужно беспокоиться о согласованности последующих обновлений.

(Для ясности: то, что вы описываете, определенно возможно при правильно нормализованной схеме. Но это будет некрасиво, и я думаю, что это будет работать плохо.)

1 голос
/ 28 марта 2012

РЕДАКТИРОВАТЬ:

В ходе попытки объяснить запрос я понял, что он не всегда будет работать правильно.Итак, я вернулся и выяснил, как это проверить.Мне все еще мешает настройка схемы, а именно, это означает, что новые пользователи не могут быть добавлены в существующий поток, и что определенный набор пользователей сможет общаться только в одном потоке, но это было хорошо исправитьзапрос.

WITH Selected_Users(id) as (VALUES (@id1), (@id2), --etc--),
     Threads(id) as (SELECT DISTINCT threadFk
                     FROM ThreadMembers as a
                     JOIN Selected_Users as b
                     ON b.id = a.userFk)
SELECT a.id
FROM Threads as a
WHERE NOT EXISTS (SELECT '1'
                  FROM ThreadMembers as b
                  LEFT JOIN Selected_Users as c
                  ON c.id = b.userFk
                  WHERE c.id IS NULL
                  AND b.threadFk = a.id)
AND NOT EXISTS (SELECT '1'
                FROM Selected_Users as b
                LEFT JOIN ThreadMembers as c
                ON c.userFk = b.id
                AND c.threadFk = a.id
                WHERE c.userFk IS NULL) 

Оператор, скорее всего, должен быть динамическим, чтобы построить список выбранных пользователей, если только у SQL Server нет способа предоставить список в качестве переменной хоста (я знаю, что DB2 делает вминимум из iSeries).У меня нет идеального набора данных для проверки этого, но для многомиллионной таблицы строк (с отношением «многие-один») он возвращается почти мгновенно - я получаю доступ только для индекса (подсказка).

Пояснения:

WITH Selected_Users(id) as (VALUES (@id1), (@id2), --etc--),

Этот CTE формирует список пользователей, чтобы на него можно было ссылаться как на таблицу.Это облегчает работу, хотя можно было бы просто заменить его на оператор IN везде (хотя требуется несколько ссылок).

     Threads(id) as (SELECT DISTINCT threadFk
                     FROM ThreadMembers as a
                     JOIN Selected_Users as b
                     ON b.id = a.userFk)

Этот CTE получает список (различных)потоки, в которых участвуют пользователи. В основном, это просто разделить список вниз на отдельные ссылки на threadFk.

SELECT a.id
FROM Threads as a

... Получить выбранный набор потоков ...

WHERE NOT EXISTS (SELECT '1'
                  FROM ThreadMembers as b
                  LEFT JOIN Selected_Users as c
                  ON c.id = b.userFk
                  WHERE c.id IS NULL
                  AND b.threadFk = a.id)

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

РЕДАКТИРОВАТЬ:

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

AND NOT EXISTS (SELECT '1'
                FROM Selected_Users as b
                LEFT JOIN ThreadMembers as c
                ON c.userFk = b.id
                AND c.threadFk = a.id
                WHERE c.userFk IS NULL) 

Этот пункт исправляет это.Он гарантирует, что в списке выбора нет оставшихся пользователей, после исключения пользователей для определенного потока.

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

РЕДАКТИРОВАТЬ:

Muwahaha, там это COUNT(*) версия, которая также должна быть быстрее:

WITH Selected_Users(id) as (VALUES (@id1), (@id2), --etc--),
SELECT a.threadFk
FROM ThreadMembers as a
JOIN Selected_Users as b
ON b.id = a.userFk
GROUP BY a.threadFk
HAVING COUNT(*) = (SELECT COUNT(*) FROM Selected_Users)
AND COUNT(*) = (SELECT COUNT(*) from ThreadMembers as c
                WHERE c.threadFk = a.threadFk)

Пояснения:

SELECT a.threadFk
FROM ThreadMembers as a
JOIN Selected_Users as b
ON b.id = a.userFk

Это объединение для получения всех потоков, в которые входят перечисленные члены.Это внутренний эквивалент Threads CTE выше.На самом деле, вы также можете удалить этот CTE в приведенном выше запросе.

GROUP BY a.threadFk

Мы хотим только один экземпляр данного потока.Также (по крайней мере, в DB2) остальная часть оператора недействительна, если она не присутствует.

HAVING COUNT(*) = (SELECT COUNT(*) FROM Selected_Users)

Убедитесь, что для данного потока присутствуют все выбранные пользователи.Или все выбранные пользователи должны присутствовать в данной теме.

AND COUNT(*) = (SELECT COUNT(*) from ThreadMembers as c
                WHERE c.threadFk = a.threadFk)

Убедитесь, что для данной темы нет выбранных пользователей.Или не должно быть никаких «пропущенных» пользователей

Вы должны получить для этого доступ только по индексу (мне кажется).COUNT(*) строк результатов (для GROUP BY) должны выполняться только один раз и использоваться повторно.Предложение HAVING оценивается после , происходит GROUP BY (если я правильно помню), поэтому повторный выбор для подсчета из исходной таблицы должен выполняться только один раз за threadFk.

0 голосов
/ 29 марта 2012

Вот пример ответа (ответ № 1) с использованием MS SQL Server 2008. Предполагается, что таблица: MessageThreadUsers (threadFK - int, userFK - varchar) определено (ваши типы ключей могут отличаться):

DELETE FROM MessageThreadUsers
GO

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (1, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (1, 'user2')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (2, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (2, 'user2')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (2, 'user3')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (3, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (3, 'user2')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (3, 'user3')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (3, 'user4')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user2')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user3')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user4')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (4, 'user5')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user1')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user2')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user3')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user4')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user5')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (5, 'user6')

INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (6, 'user6')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (6, 'user3')
INSERT INTO MessageThreadUsers (threadFK, userFK) VALUES (6, 'user1')

GO

WITH Selected_Users (id) AS (
    SELECT 'user3' UNION
    SELECT 'user1' UNION
    SELECT 'user6'
)
SELECT a.threadFk
FROM MessageThreadUsers as a
JOIN Selected_Users as b
ON b.id = a.userFk
GROUP BY a.threadFk
HAVING COUNT(*) = (SELECT COUNT(*) FROM Selected_Users)
AND COUNT(*) = (SELECT COUNT(*) from MessageThreadUsers as c
                WHERE c.threadFk = a.threadFk)
0 голосов
/ 28 марта 2012

Правильно ли сказать, что вас интересует, существует ли какой-либо поток, который: 1) имеет то же число в элементах потока при группировке по threadFK, что и количество членов интересующей вас группы, 2) и ссылку каждый участник? Если так, я думаю, что решение последует оттуда (так что это предложенный ответ). Точная механика будет зависеть от того, какую базу данных вы используете, сервер oracle, postgres или sql, вероятно, будет проще, чем другие бренды. Как вы хотите вызвать вещь, как хранимую процедуру, которая берет таблицу пользователей, список имен пользователей и возвращает, что, ключ, если есть совпадение, или NULL?

...