Как оптимизировать запрос отношения m:n к 3 таблицам
-
06-09-2019 - |
Вопрос
это моя проблема с sql - есть 3 таблицы:
Списки имен ListHasNames Id Name Id Desc ListsId NamesId =-------- ------------ ---------------- 1 Paul 1 Football 1 1 2 Joe 2 Basketball 1 2 3 Jenny 3 Ping Pong 2 1 4 Tina 4 Breakfast Club 2 3 5 Midnight Club 3 2 3 3 4 1 4 2 4 3 5 1 5 2 5 3 5 4
Это означает, что Пол (Id=1) и Джо (Id=2) входят в футбольную команду (Lists.Id=1), Пол и Дженни — в баскетбольную команду и т. д.
Теперь мне нужен оператор SQL, который возвращает Lists.Id определенной комбинации имен:В каких списках Пол, Джо и Дженни являются единственными членами этого списка?Отвечайте только Lists.Id=4 (Клуб для завтраков) — но не 5 (Клуб Midnight), потому что Тина тоже есть в этом списке.
Я пробовал это с INNER JOINS и SUB QERIES:
SELECT Q1.Lists_id FROM ( SELECT Lists_Id FROM names as T1, listhasnames as T2 WHERE (T1.Name='Paul') and (T1.Id=T2.Names_ID) and ( ( SELECT count(*) FROM listhasnames as Z1 where (Z1.lists_id = T2.lists_Id) ) = 3) ) AS Q1 INNER JOIN ( SELECT Lists_Id FROM names as T1, listhasnames as T2 WHERE (T1.Name='Joe') and (T1.Id=T2.Names_ID) and ( (SELECT count(*) FROM listhasnames as Z1 WHERE (Z1.Lists_id = T2.lists_id) ) = 3) ) AS Q2 ON (Q1.Lists_id=Q2.Lists_id) INNER JOIN ( SELECT Lists_Id FROM names as T1, listhasnames as T2 WHERE (T1.Name='Jenny') and (T1.Id=T2.Names_ID) and ( (SELECT count(*) FROM listhasnames as Z1 WHERE (Z1.Lists_id = T2.lists_id) ) = 3) ) AS Q3 ON (Q1.Lists_id=Q3.Lists_id)
Выглядит немного сложно, да?Как это оптимизировать?Мне нужен только тот Lists.Id, в котором есть конкретные имена (и только эти имена и больше никто).Может быть, с SELECT IN?
С уважением, Деннис
Решение
SELECT ListsId
FROM ListHasNames a
WHERE NamesId in (1, 2, 3)
AND NOT EXISTS
(SELECT * from ListHasNames b
WHERE b.ListsId = a.ListsId
AND b.NamesId not in (1, 2, 3))
GROUP BY ListsId
HAVING COUNT(*) = 3;
Редактировать:Исправлено благодаря комментарию Криса Гоу;подвыбор необходим для исключения списков, в которых есть другие люди.Редактировать 2 Имя таблицы исправлено благодаря комментарию Денниса.
Другие советы
Используя решение Карла Манастера в качестве отправной точки, я пришел к следующему:
SELECT listsid
FROM listhasnames
GROUP BY listsid HAVING COUNT(*) = 3
INTERSECT
SELECT x.listsid
FROM listhasnames x, names n
WHERE n.name IN('Paul', 'Joe', 'Jenny')
AND n.id = x.namesid
Обновлено:
select a.ListsId from
(
--lists with three names only
select lhn.ListsId, count(*) as count
from ListHasNames lhn
inner join Names n on lhn.NamesId = n.Id
group by lhn.ListsId
having count(*) = 3
) a
where a.ListsId in (select ListsId from ListHasNames lhn where NamesId = (select NamesId from names where Name = 'Paul'))
and a.ListsId in (select ListsId from ListHasNames lhn where NamesId = (select NamesId from names where Name = 'Joe'))
and a.ListsId in (select ListsId from ListHasNames lhn where NamesId = (select NamesId from names where Name = 'Jenny'))
Недавно я решал проблему, которая может подойти и для вашего случая.Это может быть излишним.
Я использовал подход, заключающийся в создании списка ассоциаций-кандидатов, которые могут быть правильным решением, а затем с помощью курсора или таблицы очередей для просмотра наиболее вероятных правильных решений для полной проверки.
В моем случае это было реализовано следующим образом:
select
ParentId
count(*) as ChildCount
checksum_agg(checksum(child.*) as ChildAggCrc
from parent join child on parent.parentId = child.parentId
Затем вы можете сравнить количество и совокупную контрольную сумму с данными поиска (т. е.ваши 3 имени, которые нужно проверить).Если ни одна строка не соответствует, у вас гарантированно не будет совпадений.Если какая-либо строка совпадает, вы можете затем выполнить объединение этого конкретного ParentId, чтобы проверить, есть ли какие-либо расхождения между наборами строк.
Ясно как грязь?:)