2014-01-27 5 views
11

Есть два набора сотрудников: менеджеры и ворчание.
Для каждого менеджера есть таблица manager_meethings, в которой содержится список встреч, на которых присутствовал каждый менеджер. Аналогичная таблица grunt_meetings содержит список встреч, на которых присутствовал каждый хрюканье.sql select records with matching subsets

Итак:

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 и хрюкать 7 и присутствовал (а, б), в то время как менеджер 1 и хрюкать 5 оба посетили (а, б, в).

Я могу решить это неуклюже, путем подбора подмножества встреч в подзапросе в XML и сравнения каждого XML-списка grunt с XML каждого менеджера. Но это ужасно, и я должен объяснить владельцу, что такое XML. И мне не нравится играть в гольф.

Есть ли лучший способ сделать "WHERE {subset1} = {subset2}"? Похоже, я пропустил какой-то умный вид.

SQL Fiddle

+5

Ну, +1 для SQL Fiddle, интересная история назад, попытка решить ее самостоятельно и сложный вопрос. Если бы все вопросы были такими тщательными! –

+2

О, но -1 для [использования 'char' без длины] (http://sqlblog.com/blogs/aaron_bertrand/archive/2009/10/09/bad-habits-to-kick-declaring-varchar-without- length.aspx). :-) –

+0

Без длинного объявления char в моем производственном коде, я обещаю! Взял пару ярлыков, скрывая код. – SteveSmithSQL

ответ

8

Вот версия, которая работает:

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); 

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

Подсчитывает количество совпадений между хрюканьем и менеджером. Затем он проверяет, что это все собрания для каждого.

+0

+1 хороший ответ. Я пошел по касательной, пытаясь сделать что-то умное с КРОМЕ/ИНТЕРЕСОМ или КРЕСТОМ ПРИМЕНЯЕМ и быстро почувствовал себя побежденным. –

+0

@AaronBertrand. , , Я смог немного улучшить его. –

3

Альтернативная версия - но для этого требуется другая таблица. В принципе, мы даем каждой встрече определенную силу из двух, так как это «ценность», а затем суммируем стоимость собрания каждого менеджера и стоимость собрания каждого хрюка. Там, где они те же, у нас есть матч.

Должно быть возможно сделать таблицу 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 
+0

Умный подход, хотя мы быстро выйдем из двух степеней. Хотя я бы хотел рассказать людям: «У нас больше нет встреч, у нас уже было 63». – SteveSmithSQL

3

Попытка мстя Aaron's defeat - это solution using 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 
); 

В принципе, вычтите собрание собраний из собрания менеджера, затем наоборот. Если ни один результат не содержит строк, хрюканье и менеджер присутствовали на одном и том же собрании.

Обратите внимание, что этот запрос будет соответствовать менеджерам и хрюканьем, которые никогда не присутствовали на одном собрании.

+0

Замечательный. Я тоже пошел по этому пути INTERSECT/EXCEPT, но, как и Аарон, я сдался после того, как несколько вложенных версий «A не содержит B или B содержит A, но не C». – SteveSmithSQL