Sql выбирает записи с соответствующими подмножествами
Есть два набора сотрудников: менеджеры и ворчание.
Для каждого менеджера есть таблица manager_meetings, которая содержит список встреч, на которых присутствовал каждый менеджер. Аналогичная таблица grunt_meetings содержит список встреч, на которых присутствовал каждый хрюканье.
Итак:
manager_meetings grunt_meetings
managerID meetingID gruntID meetingID
1 a 4 a
1 b 4 b
1 c 4 c
2 a 4 d
2 b 5 a
3 c 5 b
3 d 5 c
3 e 6 a
6 c
7 b
7 a
Владельцу это не нравится, когда менеджер и ворчание знают точно такую же информацию. У него болит голова. Он хочет идентифицировать эту ситуацию, поэтому он может понизить репутацию менеджера до ворчания или рекламировать менеджера, или взять их в гольф. Владелец любит играть в гольф.
Задача состоит в том, чтобы перечислить каждую комбинацию менеджера и хрюкать, где оба присутствовали на одних и тех же собраниях. Если менеджер посещал больше встречи, чем ворчание, не совпадало. Если хрюканье посещало больше встреч, чем менеджер, не было ни одного матча.
Ожидаемые результаты:
ManagerID GruntID
2 7
1 5
... потому что менеджер 2 и grunt 7 оба присутствовали (a, b), в то время как менеджер 1 и ворчать 5 присутствовали (a, b, c).
Я могу решить это неуклюже, путем подбора подмножества собраний в подзапросе в XML и сравнения каждого хрустящего XML-списка с каждым XML-менеджером. Но это ужасно, а также я должен объяснить владельцу, что такое XML. И мне не нравится играть в гольф.
Есть ли лучший способ сделать "WHERE {subset1} = {subset2}"
? Похоже, я пропустил какой-то умный вид.
SQL Fiddle
Ответы
Ответ 1
Вот версия, которая работает:
select m.mId, g.gId, count(*) --select m.mid, g.gid, mm.meetingid, gm.meetingid as gmm
from manager m cross join
grunt g left outer join
(select mm.*, count(*) over (partition by mm.mid) as cnt
from manager_meeting mm
) mm
on mm.mid = m.mId full outer join
(select gm.*, count(*) over (partition by gm.gid) as cnt
from grunt_meeting gm
) gm
on gm.gid = g.gid and gm.meetingid = mm.meetingid
group by m.mId, g.gId, mm.cnt, gm.cnt
having count(*) = mm.cnt and mm.cnt = gm.cnt;
Метод сравнения строк короче, возможно, легче понять и, вероятно, быстрее.
EDIT:
Для вашего конкретного случая получения точных совпадений запрос можно упростить:
select mm.mId, gm.gId
from (select mm.*, count(*) over (partition by mm.mid) as cnt
from manager_meeting mm
) mm join
(select gm.*, count(*) over (partition by gm.gid) as cnt
from grunt_meeting gm
) gm
on gm.meetingid = mm.meetingid and
mm.cnt = gm.cnt
group by mm.mId, gm.gId
having count(*) = max(mm.cnt);
Это может быть более конкурентоспособным со строковой версией, как с точки зрения производительности, так и с ясностью.
Он подсчитывает количество совпадений между хрюканьем и менеджером. Затем он проверяет, что это все собрания для каждого.
Ответ 2
Альтернативная версия - но требует другой таблицы. В принципе, мы даем каждой встрече отличную силу из двух, поскольку она "ценит", а затем суммирует каждую стоимость встречи менеджера и значение каждого хрюкающего собрания. Если они одинаковы, у нас есть матч.
Должно быть возможно сделать таблицу meeting_values
TVF, но это немного проще.
SQL Fiddle
Дополнительная таблица:
CREATE TABLE meeting_values (value INT, meetingID CHAR(1));
INSERT INTO meeting_values VALUES
(1,'a'),(2,'b'),(4,'c'),(8,'d'),(16,'e');
И запрос:
SELECT managemeets.mID, gruntmeets.gID
FROM
( SELECT gm.gID, sum(value) AS meeting_totals
FROM grunt_meeting gm
INNER JOIN
meeting_values mv ON gm.meetingID = mv.meetingID
GROUP BY gm.gID
) gruntmeets
INNER JOIN
( SELECT mm.mID, sum(value) AS meeting_totals
FROM manager_meeting mm
INNER JOIN
meeting_values mv ON mm.meetingID = mv.meetingID
GROUP BY mm.mID
) managemeets ON gruntmeets.meeting_totals = managemeets.meeting_totals
Ответ 3
Попытка отомстить поражение Аарона - решение используя EXCEPT:
SELECT
m.mID,
g.gID
FROM
manager AS m
INNER JOIN
grunt AS g
ON NOT EXISTS (
SELECT meetingID
FROM manager_meeting
WHERE mID = m.mID
EXCEPT
SELECT meetingID
FROM grunt_meeting
WHERE gID = g.gID
)
AND NOT EXISTS (
SELECT meetingID
FROM grunt_meeting
WHERE gID = g.gID
EXCEPT
SELECT meetingID
FROM manager_meeting
WHERE mID = m.mID
);
В принципе, вычтите изрядный набор собраний из набора встреч менеджеров, затем наоборот. Если ни один результат не содержит строк, хрюканье и менеджер присутствовали на одном и том же собрании.
Обратите внимание, что этот запрос будет соответствовать менеджерам и ворчаниям, которые никогда не посещали ни одну встречу.