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))。类似的问题,但没有好的答案: