РЕДАКТИРОВАТЬ:
В ходе попытки объяснить запрос я понял, что он не всегда будет работать правильно.Итак, я вернулся и выяснил, как это проверить.Мне все еще мешает настройка схемы, а именно, это означает, что новые пользователи не могут быть добавлены в существующий поток, и что определенный набор пользователей сможет общаться только в одном потоке, но это было хорошо исправитьзапрос.
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
.