Как оптимизировать запрос отношения m:n к 3 таблицам

StackOverflow https://stackoverflow.com/questions/917106

  •  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, чтобы проверить, есть ли какие-либо расхождения между наборами строк.

Ясно как грязь?:)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top