编辑:
在尝试解释该查询的过程中,我意识到它并不总是能正常工作。所以,我回去想办法测试这个。我仍然被模式设置所困扰——也就是说,它意味着新用户不能添加到现有线程中,并且一组特定的用户只能在一个线程中交谈——但纠正一下很好查询。
WITH Selected_Users(id) as (VALUES (@id1), (@id2), --etc--),
Threads(id) as (SELECT DISTINCT threadFk
FROM ThreadMembers as a
JOIN Selected_Users as b
ON b.id = a.userFk)
SELECT a.id
FROM Threads as a
WHERE NOT EXISTS (SELECT '1'
FROM ThreadMembers as b
LEFT JOIN Selected_Users as c
ON c.id = b.userFk
WHERE c.id IS NULL
AND b.threadFk = a.id)
AND NOT EXISTS (SELECT '1'
FROM Selected_Users as b
LEFT JOIN ThreadMembers as c
ON c.userFk = b.id
AND c.threadFk = a.id
WHERE c.userFk IS NULL)
该语句可能必须是动态的,以构建选定用户的列表,除非 SQL Server 有办法提供一个列表作为主变量(我知道 DB2 可以,至少在 iSeries 中是这样)。我没有完美的数据集来测试它,但是针对数百万行表(只有多对一关系),它几乎立即返回 - 我正在获得仅索引访问(提示提示) .
解释:
WITH Selected_Users(id) as (VALUES (@id1), (@id2), --etc--),
此 CTE 正在构建用户列表,以便可以将其作为表格引用。这使它最容易处理,尽管可以在任何地方简单地用 IN 语句替换它(但需要多个引用)。
Threads(id) as (SELECT DISTINCT threadFk
FROM ThreadMembers as a
JOIN Selected_Users as b
ON b.id = a.userFk)
此 CTE 获取用户参与的(不同的)线程列表。大多数情况下,这只是将列表分解为对 threadFk 的单个引用。
SELECT a.id
FROM Threads as a
...获取选定的线程集...
WHERE NOT EXISTS (SELECT '1'
FROM ThreadMembers as b
LEFT JOIN Selected_Users as c
ON c.id = b.userFk
WHERE c.id IS NULL
AND b.threadFk = a.id)
如果所选用户列表中没有任何人“丢失” - 也就是说,它会消除具有较大用户列表子集的用户列表的线程。它还消除了从选择中列出了一些用户的线程,但也消除了一些未列出的线程,这意味着用户的计数会匹配,但实际用户不会匹配(这是我的第一个版本失败了)。
编辑:
我意识到,虽然现有语句处理了提供的用户列表是为给定线程列出的用户子集的情况,但我没有处理所选用户列表包含子集,即给定线程的用户列表。
AND NOT EXISTS (SELECT '1'
FROM Selected_Users as b
LEFT JOIN ThreadMembers as c
ON c.userFk = b.id
AND c.threadFk = a.id
WHERE c.userFk IS NULL)
这个条款解决了这个问题。它确保在排除特定线程的用户后,选择列表中没有任何剩余用户。
这个声明现在让我有点烦——我可能有更好的方法来做到这一点......
编辑:
Muwahaha,有一个COUNT(*)的版本,应该也更快:
WITH Selected_Users(id) as (VALUES (@id1), (@id2), --etc--),
SELECT a.threadFk
FROM ThreadMembers as a
JOIN Selected_Users as b
ON b.id = a.userFk
GROUP BY a.threadFk
HAVING COUNT(*) = (SELECT COUNT(*) FROM Selected_Users)
AND COUNT(*) = (SELECT COUNT(*) from ThreadMembers as c
WHERE c.threadFk = a.threadFk)
解释:
SELECT a.threadFk
FROM ThreadMembers as a
JOIN Selected_Users as b
ON b.id = a.userFk
这是加入以获取列出的成员所属的所有线程。这相当于上面的Threads CTE。实际上,您也可以在上述查询中删除该 CTE。
GROUP BY a.threadFk
毕竟我们只想要给定线程的一个实例。此外(至少在 DB2 中),该语句的其余部分除非存在,否则无效。
HAVING COUNT(*) = (SELECT COUNT(*) FROM Selected_Users)
验证,对于给定的线程,所有选定的用户都存在。或者,所有选定的用户都必须出现在给定的线程中。
AND COUNT(*) = (SELECT COUNT(*) from ThreadMembers as c
WHERE c.threadFk = a.threadFk)
验证,对于给定的线程,没有未选择的用户。或者,不能有任何用户“被排除在外”
您应该为此获得仅索引访问权限(我似乎是)。结果行的COUNT(*)(对于GROUP BY)应该只执行一次,并重复使用。 HAVING 子句在发生GROUP BY 之后评估(如果我没记错的话),因此从原始表中进行计数的子选择应该只发生一次 em> 每个threadFk。