Как оптимизировать запрос отношения m: n для 3 таблиц - PullRequest
0 голосов
/ 27 мая 2009

это моя проблема sql - есть 3 таблицы:

<b>Names         Lists                ListHasNames</b>
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 (Клуб для полуночи), потому что Тина тоже в этом списке.

Я пробовал это с ВНУТРЕННИМИ СОЕДИНЕНИЯМИ И ПОДПИСКАМИ:

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?

С уважением, Dennis

Ответы [ 4 ]

2 голосов
/ 28 мая 2009

Используя решение Карла Манастера в качестве отправной точки, я придумал:

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
2 голосов
/ 27 мая 2009
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 Исправлено имя таблицы благодаря комментарию Денниса

1 голос
/ 27 мая 2009

Обновлен:

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'))
0 голосов
/ 27 мая 2009

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

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

В моем случае это было реализовано следующим образом:

select
ParentId
count(*) as ChildCount
checksum_agg(checksum(child.*) as ChildAggCrc
from parent join child on parent.parentId = child.parentId

Затем вы можете сравнить количество и контрольную сумму с вашими поисковыми данными (т. Е. Ваши 3 имени для проверки). Если ни одна строка не совпадает, у вас гарантированно не будет совпадений. Если какая-либо строка совпадает, вы можете пройти и выполнить соединение этого конкретного ParentId, чтобы проверить, есть ли какие-либо расхождения между наборами строк.

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

...