完全匹配两个多对多表的记录集

时间:2011-07-17 17:48:44

标签: tsql sql-server-2008-r2

我有用户职位许可

关系是:

  • 用户可能拥有许多许可
  • 职位可能需要许多许可

因此,我可以轻松获得每个职位的许可证要求以及每个用户的有效许可证。

但我想知道匹配两个的最佳方法是什么?逻辑上,用户至少需要某个位置所需的许可证。可能有更多,但剩下的不相关。

我希望得到用户和符合条件的排名结果。

PersonID PositionID
1        1          -> user 1 is eligible to work on position 1
1        2          -> user 1 is eligible to work on position 2
2        1          -> user 2 is eligible to work on position 1
3        2          -> user 3 is eligible to work on position 2
4        ...

正如您所看到的,我需要为所有用户提供结果,而不是每个用户只需一个结果,这会使事情变得更加容易。


这里实际上有5个表:

create table Person ( PersonID, ...)
create table Position (PositionID, ...)
create table License (LicenseID, ...)

和关系

create table PersonLicense (PersonID, LicenseID, ...)
create table PositionLicense (PositionID, LicenseID, ...)

所以基本上我需要找到某个人获得许可的职位。当然这里有一个更复杂的问题,因为还有其他因素,但主要目标是相同的:

如何将一个关系表的多个记录与另一个关系表的多个记录进行匹配。这也可以描述为每组记录inner join而不是每个记录为它通常在TSQL中完成。

我正在考虑TSQL语言结构:

  • rowsets但我以前从未使用过它们,也不知道如何使用它们
  • intersect语句可能虽然这些语句可能只适用于整个集合而不是组

3 个答案:

答案 0 :(得分:4)

最终解决方案(供将来参考)

与此同时,当你的开发人员回答我的问题时,这是我提出的并使用CTE和分区,当然可以在SQL Server 2008 R2上使用。我之前从未使用过结果分区,所以我不得不学习一些新的东西(这完全是一个加号)。这是代码:

with CTEPositionLicense as (
    select
        PositionID,
        LicenseID,
        checksum_agg(LicenseID) over (partition by PositionID) as RequiredHash
    from PositionLicense
)
select per.PersonID, pos.PositionID
from CTEPositionLicense pos
    join PersonLicense per
    on (per.LicenseID = pos.LicenseID)
group by pos.PositionID, pos.RequiredHash, per.PersonID
having pos.RequiredHash = checksum_agg(per.LicenseID)
order by per.PersonID, pos.PositionID;

所以我对这三种技术进行了比较,我将其命名为:

  1. Cross join(Andriy M)
  2. Table variable(由Petar Ivanov撰写)
  3. Checksum - 这一个(Robert Koritnik,我)
  4. 我已经按人数和位置订购了结果,所以我也将其添加到其他两个,以便返回相同的结果。

    产生的估计执行计划

    1. 校验和:7%
    2. 表变量:2%(表创建)+ 9%(执行)= 11%
    3. 交叉加入:82%
    4. 我还将表变量版本更改为CTE版本(而不是表变量使用了CTE),最后删除了order by并比较了它们的估计执行计划。仅供参考CTE版本43%,而原始版本有53%(10%+ 43%)。

答案 1 :(得分:2)

有效写这个的一种方法是在LicenceId上将PositionLicences与PersonLicences连接起来。然后计算按位置和人员分组的非空值,并与所有位置许可证的计数进行比较 - 如果等于该人员符合条件:

DECLARE @tmp TABLE(PositionId INT, LicenseCount INT)

INSERT INTO @tmp
SELECT  PositionId as PositionId
        COUNT(1) as LicenseCount
FROM PositionLicense
GROUP BY PositionId

SELECT  per.PersonID, pos.PositionId
FROM    PositionLicense as pos
INNER JOIN PersonLicense as per ON (pos.LicenseId = per.LicenseId)
GROUP BY t.PositionID, t.PersonId
HAVING COUNT(1) = (
    SELECT LicenceCount FROM @tmp WHERE PositionId = t.PositionID
)

答案 2 :(得分:1)

我会像这样解决问题:

  1. PersonLicense获取所有(不同)用户。

  2. PositionLicense交叉加入。

  3. 使用PersonLicensePersonID左键加入包含LicenseID的结果集。

  4. PersonIDPositionID对结果进行分组。

  5. 过滤掉(PersonID, PositionID)对中PositionLicense中的许可数与PersonLicense中的许可数不匹配的SELECT u.PersonID, pl.PositionID FROM (SELECT DISTINCT PersonID FROM PersonLicense) u CROSS JOIN PositionLicense pl LEFT JOIN PersonLicense ul ON u.PersonID = ul.PersonID AND pl.LicenseID = ul.LicenseID GROUP BY u.PersonID, pl.PositionID HAVING COUNT(pl.LicenseID) = COUNT(ul.LicenseID) 对。

  6. 这是我的实施:

    {{1}}