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

Tsql 完全匹配两个多对多表的记录集,tsql,sql-server-2008-r2,Tsql,Sql Server 2008 R2,我拥有用户、职位和许可证 关系是: 用户可能有许多许可证 职位可能需要很多许可证 因此,我可以轻松获得每个职位的许可证要求以及每个用户的有效许可证 但我想知道什么是最好的方式来匹配这两盘?按照逻辑,用户至少需要某些职位所需的许可证。可能有更多,但剩余的不相关 我希望获得用户和合格职位的结果。 PersonID PositionID 1 1 -> user 1 is eligible to work on position 1 1 2

我拥有用户职位许可证

关系是:

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

但我想知道什么是最好的方式来匹配这两盘?按照逻辑,用户至少需要某些职位所需的许可证。可能有更多,但剩余的不相关

我希望获得用户和合格职位的结果。

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, ...)
所以基本上我需要找到一个特定的人被授权工作的职位。当然这里有一个更复杂的问题,因为还有其他因素,但主要目标是相同的:

如何将一个关系表的多条记录与另一个关系表的多条记录进行匹配。这也可以描述为每一组记录的
内部联接,而不是像TSQL中通常所做的每一条记录

我想到了TSQL语言结构:

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

有效编写此文件的一种方法是在LicenseID上将职位许可证与个人许可证合并。然后计算按职位和人员分组的非空值,并与所有职位许可证的计数进行比较-如果等于该人员的资格:

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
)

我会这样处理这个问题:

  • PersonLicense
    获取所有(不同的)用户

  • 使用
    PositionLicense
    交叉连接它们

  • 使用
    PersonID
    LicenseID
    将结果集与
    PersonLicense
    左键联接

  • PersonID
    PositionID
    对结果进行分组

  • 过滤掉那些
    (PersonID,PositionID)
    对,其中
    位置许可证
    中的许可证数量与
    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)
    
    最终解决方案(供将来参考) 同时,当您的开发伙伴回答我的问题时,这是我提出的,并使用了CTE和分区,当然可以在SQLServer2008R2上使用。我以前从未使用过结果分区,所以我必须学习一些新的东西(这是一个加号)。代码如下:

    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;
    
    因此,我对这三种技术进行了比较,我将其命名为:

  • (安德烈M)
  • (佩塔尔·伊万诺夫)
  • 校验和-这一个在这里(由罗伯特·科里特尼克,我)
  • 我已经对每个人和每个职位的结果进行了排序,所以我还将相同的结果添加到其他两个,以使返回的结果相同

    由此产生的估计执行计划
  • 校验和:7%
  • 表变量:2%(表创建)+9%(执行)=11%
  • 交叉连接:82%

  • 我还将表变量版本更改为CTE版本(使用了CTE而不是表变量),并在最后删除了
    order by,并比较了它们的估计执行计划。仅供参考,CTE版本为43%,而原始版本为53%(10%+43%)。

    问题/注意/观察:似乎没有必要包装子查询。我已经完全删除了外包装查询,结果也是一样的。使用外部查询所做的一切就是从子查询中获取结果并将其分组。无论如何,分组可以很容易地在子查询上完成。你为什么决定把它包起来?但是查询优化器足够聪明,所以它为这两个对象生成相同的执行计划。啊,这一点很好!我拥有它的原因是原始版本稍微复杂一些,它需要分组(我没有使用have)。我编辑了asnwer。我喜欢你的带有checksum_agg的解决方案,我使用XML路径来获取记录集的签名,这需要大量的CPU来处理大量的数据集。当然,避免交叉连接对于大量数据集也非常重要。我建议您使用n个字段添加此问题的更通用解决方案-在这种情况下,您将使用校验和_agg(校验和(f1,f2,…,fn))。类似的问题,但没有好的答案: