sql选择具有匹配子集的记录

时间:2014-01-27 22:27:01

标签: sql sql-server tsql

有两组员工:经理和咕噜声 对于每个经理,都有一个表manager_meetings,其中包含每个经理参加的会议列表。类似的表grunt_meetings包含每个grunt参加的会议的列表。

所以:

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

当经理和咕噜声知道完全相同的信息时,所有者不喜欢它。这让他头痛。他想确定这种情况,因此他可以将经理降级为咕噜声,或者将咕噜声推向经理,或者让他们两人打高尔夫球。主人喜欢打高尔夫球。

任务是列出管理员和grunt的每个组合,两者都参加完全相同的会议。如果经理参加的会议多于咕噜声,那就没有比赛。如果咕噜声参加了比经理更多的会议,没有比赛。

这里的预期结果是:

ManagerID            GruntID
2                    7
1                    5 

...因为经理2和grunt 7都参加了(a,b),而经理1和grunt 5都参加了(a,b,c)。

我可以通过将子查询中的会议子集转换为XML,并将每个grunt的XML列表与每个管理器的XML进行比较,以笨重的方式解决它。但这太可怕了,我还要向所有者解释XML是什么。而且我不喜欢打高尔夫球。

有没有更好的方法"WHERE {subset1} = {subset2}"?感觉我错过了一些聪明的加入。

SQL Fiddle

3 个答案:

答案 0 :(得分: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;

字符串比较方法更短,可能更容易理解,而且可能更快。

编辑:

对于获得完全匹配的特定情况,可以简化查询:

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

在性能和清晰度方面,这可能与字符串版本更具竞争力。

它计算grunt和manager之间的匹配数。然后它会检查这是每个会议的所有会议。

答案 1 :(得分:4)

尝试报复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
);

基本上,从经理的一系列会议中减去一个grunt的会议集,然后相反。如果两个结果都不包含行,那么grunt和经理就会参加同一组会议。

请注意,此查询将匹配从未参加过单个会议的经理和咕噜声。

答案 2 :(得分: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