SQL Select 来自 table 其中来自第二个 table 的连接值是来自第三个 table 的值的子集
SQL Select from table where joined values from a second table are a subset of values from a third table
我在 MS SQL 服务器中有以下 tables:Tasks、Users、Tags、TaskTags(将任务映射到标签)和 UserTags(将用户映射到标签) .
给定一个用户 U,我想找到所有任务 T,其中 T 的每个标签也是 U 的标签(例如,如果任务的标签是用户标签的子集)。
这是一个带有一些示例数据的 table 脚本(它可以是 运行 at http://sqlfiddle.com/ with MS SQL Server 17):
CREATE TABLE [dbo].[Tasks](
[TaskId] [int] NOT NULL PRIMARY KEY,
[TaskName] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[Users](
[UserId] [int] NOT NULL PRIMARY KEY,
[UserName] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[Tags](
[TagId] [int] NOT NULL PRIMARY KEY,
[TagName] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[TaskTags](
[TaskId] [int] NOT NULL,
[TagId] [int] NOT NULL
)
CREATE TABLE [dbo].[UserTags](
[UserId] [int] NOT NULL,
[TagId] [int] NOT NULL
)
INSERT INTO Tasks VALUES (1,'Task for all SWEs');
INSERT INTO Tasks VALUES (2,'Task for USA SWEs');
INSERT INTO Tasks VALUES (3,'Task for all PMs');
INSERT INTO Tasks VALUES (4,'Task for Europe PMs');
INSERT INTO Users VALUES (1,'Europe SWE');
INSERT INTO Users VALUES (2,'USA SWE');
INSERT INTO Users VALUES (3,'Europe PM');
INSERT INTO Users VALUES (4,'USA PM');
INSERT INTO Tags VALUES (1,'swe');
INSERT INTO Tags VALUES (2,'pm');
INSERT INTO Tags VALUES (3,'usa');
INSERT INTO Tags VALUES (4,'europe');
INSERT INTO TaskTags VALUES (1,1);
INSERT INTO TaskTags VALUES (2,1);
INSERT INTO TaskTags VALUES (2,3);
INSERT INTO TaskTags VALUES (3,2);
INSERT INTO TaskTags VALUES (4,2);
INSERT INTO TaskTags VALUES (4,4);
INSERT INTO UserTags VALUES (1,1);
INSERT INTO UserTags VALUES (1,4);
INSERT INTO UserTags VALUES (2,1);
INSERT INTO UserTags VALUES (2,3);
INSERT INTO UserTags VALUES (3,2);
INSERT INTO UserTags VALUES (3,4);
INSERT INTO UserTags VALUES (4,2);
INSERT INTO UserTags VALUES (4,3);
当给出任务 T 时,我能够找出这个问题的逆。例如。给定任务 T,return 所有用户 U,其中 T 的标签是 U 的子集。这是该查询:
WITH thisTaskTags AS (
SELECT DISTINCT TaskTags.TagId
FROM TaskTags
WHERE TaskTags.TaskId = @taskId
)
SELECT UserTags.UserId
FROM UserTags JOIN thisTaskTags
ON UserTags.TagId = thisTaskTags.TagId CROSS JOIN
(SELECT COUNT(*) AS keycnt FROM thisTaskTags) k
GROUP BY UserTags.UserId
HAVING COUNT(thisTaskTags.TagId) = MAX(k.keycnt)
当@taskId = 1 时,UserIds 1 和 2 被 returned,而当 @taskId = 2 时,只有 UserId 2 被 returned(正确行为)。
然而,当我试图将其转换为 return 给定用户应该拥有的所有任务时,我 运行 遇到了麻烦。我试过这个查询:
WITH thisUserTags AS (
SELECT DISTINCT UserTags.TagId
FROM UserTags
WHERE UserTags.UserId = @userId
)
SELECT TaskTags.TaskId
FROM TaskTags JOIN thisUserTags
ON thisUserTags.TagId = TaskTags.TagId CROSS JOIN
(SELECT COUNT(*) AS keycnt FROM thisUserTags) k
GROUP BY TaskTags.TaskId
HAVING COUNT(thisUserTags.TagId) = MAX(k.keycnt);
然而,这只有 returns 个任务,其中所有任务标签都匹配所有用户任务,例如如果你有标签:[a,b,c] 它只会得到带有标签的任务:[a,b,c] 而不是 [a]、[b]、[b,c] 等
对于具体示例,如果您设置@userId = 1,则不会 return 编辑任何任务 ID,当正确的输出将获得 1 行时,任务 ID = 1。当 @userId = 2 时,只有 taskID 2 是 returned,而 taskIDs 1 和 2 都应该是 returned(即,如果一个任务只有“swe”标签,所有“swe”用户都应该得到它,但是如果任务同时具有“swe”和“usa”,只有同时具有这两个标签的用户才能获得它。
我也试过这个查询:
SELECT DISTINCT Tasks.TaskId FROM Tasks
INNER JOIN TaskTags ON TaskTags.TaskId = Tasks.TaskId
WHERE TaskTags.TagId IN (SELECT TagId from UserTags where UserId = @userId)
GROUP BY Tasks.TaskId
但问题是 return 任何具有任何共同标签的任务,所以带有标签的 U 会得到带有标签的 T:[b,d]即使你没有标签 d.
再举个具体的例子,如果@userId = 1,taskIDs 1,2,4 被 returned,而只有 taskIds 1 和 2 应该被 returned(任务 ID 4 应该仅分配给同时具有“europe”和“pm”标签的用户,由于常见的“europe”标签,此处错误地分配给具有“europe”和“swe”标签的用户。
有人可以在这里阐明一下吗?
您可能正在寻找类似以下内容...
declare @userId int = ...;
select Tasks.TaskId
from Tasks
where 0 = (
select count(1)
from (
select TagId from TaskTags where TaskTags.TaskId=Tasks.TaskId
except
select TagId from UserTags where UserTags.UserId=@userId
) TaskSpecificTags
);
不清楚您是否还想 return 带有 0 个标签的任务,因此您可能还需要测试该条件。
这是一道经典的Relational Division With Remainder题。
你只需要正确构图:
- 你想要所有
Tasks
...
- ... 其
TaskTags
将所有 UserTags
的集合划分为给定的 User
UserTags
可以有余数,TaskTags
不能有余数所以前者是被除数,后者是除数
一个典型的解决方案(有很多)是将被除数左连接到除数,将其分组,然后确保匹配的被除数与除数的个数相同。换句话说,所有除数都匹配。
因为你似乎只想要 Tasks
而不是他们的 TaskTags
,你可以在 EXISTS
子查询中完成所有这些:
DECLARE @userId int = 1;
SELECT *
FROM Tasks t
WHERE EXISTS (SELECT 1
FROM TaskTags tt
LEFT JOIN UserTags ut ON ut.TagId = tt.TagId
AND ut.UserId = @userId
WHERE tt.TaskId = t.TaskId
HAVING COUNT(*) = COUNT(ut.UserId)
);
我在 MS SQL 服务器中有以下 tables:Tasks、Users、Tags、TaskTags(将任务映射到标签)和 UserTags(将用户映射到标签) .
给定一个用户 U,我想找到所有任务 T,其中 T 的每个标签也是 U 的标签(例如,如果任务的标签是用户标签的子集)。
这是一个带有一些示例数据的 table 脚本(它可以是 运行 at http://sqlfiddle.com/ with MS SQL Server 17):
CREATE TABLE [dbo].[Tasks](
[TaskId] [int] NOT NULL PRIMARY KEY,
[TaskName] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[Users](
[UserId] [int] NOT NULL PRIMARY KEY,
[UserName] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[Tags](
[TagId] [int] NOT NULL PRIMARY KEY,
[TagName] [nvarchar](MAX) NOT NULL
)
CREATE TABLE [dbo].[TaskTags](
[TaskId] [int] NOT NULL,
[TagId] [int] NOT NULL
)
CREATE TABLE [dbo].[UserTags](
[UserId] [int] NOT NULL,
[TagId] [int] NOT NULL
)
INSERT INTO Tasks VALUES (1,'Task for all SWEs');
INSERT INTO Tasks VALUES (2,'Task for USA SWEs');
INSERT INTO Tasks VALUES (3,'Task for all PMs');
INSERT INTO Tasks VALUES (4,'Task for Europe PMs');
INSERT INTO Users VALUES (1,'Europe SWE');
INSERT INTO Users VALUES (2,'USA SWE');
INSERT INTO Users VALUES (3,'Europe PM');
INSERT INTO Users VALUES (4,'USA PM');
INSERT INTO Tags VALUES (1,'swe');
INSERT INTO Tags VALUES (2,'pm');
INSERT INTO Tags VALUES (3,'usa');
INSERT INTO Tags VALUES (4,'europe');
INSERT INTO TaskTags VALUES (1,1);
INSERT INTO TaskTags VALUES (2,1);
INSERT INTO TaskTags VALUES (2,3);
INSERT INTO TaskTags VALUES (3,2);
INSERT INTO TaskTags VALUES (4,2);
INSERT INTO TaskTags VALUES (4,4);
INSERT INTO UserTags VALUES (1,1);
INSERT INTO UserTags VALUES (1,4);
INSERT INTO UserTags VALUES (2,1);
INSERT INTO UserTags VALUES (2,3);
INSERT INTO UserTags VALUES (3,2);
INSERT INTO UserTags VALUES (3,4);
INSERT INTO UserTags VALUES (4,2);
INSERT INTO UserTags VALUES (4,3);
当给出任务 T 时,我能够找出这个问题的逆。例如。给定任务 T,return 所有用户 U,其中 T 的标签是 U 的子集。这是该查询:
WITH thisTaskTags AS (
SELECT DISTINCT TaskTags.TagId
FROM TaskTags
WHERE TaskTags.TaskId = @taskId
)
SELECT UserTags.UserId
FROM UserTags JOIN thisTaskTags
ON UserTags.TagId = thisTaskTags.TagId CROSS JOIN
(SELECT COUNT(*) AS keycnt FROM thisTaskTags) k
GROUP BY UserTags.UserId
HAVING COUNT(thisTaskTags.TagId) = MAX(k.keycnt)
当@taskId = 1 时,UserIds 1 和 2 被 returned,而当 @taskId = 2 时,只有 UserId 2 被 returned(正确行为)。
然而,当我试图将其转换为 return 给定用户应该拥有的所有任务时,我 运行 遇到了麻烦。我试过这个查询:
WITH thisUserTags AS (
SELECT DISTINCT UserTags.TagId
FROM UserTags
WHERE UserTags.UserId = @userId
)
SELECT TaskTags.TaskId
FROM TaskTags JOIN thisUserTags
ON thisUserTags.TagId = TaskTags.TagId CROSS JOIN
(SELECT COUNT(*) AS keycnt FROM thisUserTags) k
GROUP BY TaskTags.TaskId
HAVING COUNT(thisUserTags.TagId) = MAX(k.keycnt);
然而,这只有 returns 个任务,其中所有任务标签都匹配所有用户任务,例如如果你有标签:[a,b,c] 它只会得到带有标签的任务:[a,b,c] 而不是 [a]、[b]、[b,c] 等
对于具体示例,如果您设置@userId = 1,则不会 return 编辑任何任务 ID,当正确的输出将获得 1 行时,任务 ID = 1。当 @userId = 2 时,只有 taskID 2 是 returned,而 taskIDs 1 和 2 都应该是 returned(即,如果一个任务只有“swe”标签,所有“swe”用户都应该得到它,但是如果任务同时具有“swe”和“usa”,只有同时具有这两个标签的用户才能获得它。
我也试过这个查询:
SELECT DISTINCT Tasks.TaskId FROM Tasks
INNER JOIN TaskTags ON TaskTags.TaskId = Tasks.TaskId
WHERE TaskTags.TagId IN (SELECT TagId from UserTags where UserId = @userId)
GROUP BY Tasks.TaskId
但问题是 return 任何具有任何共同标签的任务,所以带有标签的 U 会得到带有标签的 T:[b,d]即使你没有标签 d.
再举个具体的例子,如果@userId = 1,taskIDs 1,2,4 被 returned,而只有 taskIds 1 和 2 应该被 returned(任务 ID 4 应该仅分配给同时具有“europe”和“pm”标签的用户,由于常见的“europe”标签,此处错误地分配给具有“europe”和“swe”标签的用户。
有人可以在这里阐明一下吗?
您可能正在寻找类似以下内容...
declare @userId int = ...;
select Tasks.TaskId
from Tasks
where 0 = (
select count(1)
from (
select TagId from TaskTags where TaskTags.TaskId=Tasks.TaskId
except
select TagId from UserTags where UserTags.UserId=@userId
) TaskSpecificTags
);
不清楚您是否还想 return 带有 0 个标签的任务,因此您可能还需要测试该条件。
这是一道经典的Relational Division With Remainder题。
你只需要正确构图:
- 你想要所有
Tasks
... - ... 其
TaskTags
将所有UserTags
的集合划分为给定的User
UserTags
可以有余数,TaskTags
不能有余数所以前者是被除数,后者是除数
一个典型的解决方案(有很多)是将被除数左连接到除数,将其分组,然后确保匹配的被除数与除数的个数相同。换句话说,所有除数都匹配。
因为你似乎只想要 Tasks
而不是他们的 TaskTags
,你可以在 EXISTS
子查询中完成所有这些:
DECLARE @userId int = 1;
SELECT *
FROM Tasks t
WHERE EXISTS (SELECT 1
FROM TaskTags tt
LEFT JOIN UserTags ut ON ut.TagId = tt.TagId
AND ut.UserId = @userId
WHERE tt.TaskId = t.TaskId
HAVING COUNT(*) = COUNT(ut.UserId)
);