给出:“用户和组”以及“多对多GroupUsers”表。
每对用户都必须有自己的配对组,前提是可以有更多或更少用户的组。
如何检查和创建丢失的配对组?
create table dbo.Users (
Id int not null,
Name nvarchar(50) not null,
constraint PK_Users primary key clustered (Id)
);
create table dbo.Groups (
Id int not null,
Name nvarchar(50) not null,
constraint PK_Groups primary key clustered (Id)
);
create table dbo.GroupUsers (
GroupId int not null ,
UserId int not null ,
constraint PK_GroupUsers primary key clustered (GroupId, UserId),
constraint FK_GroupUsers_GroupId foreign key (GroupId) references dbo.Groups (Id),
constraint FK_GroupUsers_UserId foreign key (UserId) references dbo.Users (Id)
);
insert into dbo.Users values (1, 'Anna'), (2, 'Berta'), (3, 'Carlie'), (4, 'Dana'), (5, 'Emil');
insert into dbo.Groups values (1, 'Anna-Berta'), (2, 'Anna-Carlie'), (3, 'Anna-Berta-Carlie');
insert into dbo.GroupUsers values
(1,1), (1, 2), -- 'Anna-Berta' group
(2,1), (2, 3), -- 'Anna-Carlie' group
(3,1), (3, 2), (3, 3); -- 'Anna-Berta-Carlie' group
如何为用户Anna查找和创建丢失的“配对组”?
更新2019-12-15 :
因此(Link to SQL Fiddle)是迄今为止我最喜欢的解决方案,它如何查找丢失的配对组及其用户(感谢answer的@Kari F.)
declare @UserId int = 1;
with cte as
(
select
VirtualGroupId = row_number() over(order by p.Id desc) * -1,
GroupName = concat( u.Name, '_', p.Name),
CurrentUserId = u.Id,
OtherUserId = p.Id
from
dbo.Users u inner join dbo.Users p on u.Id = @UserId and p.Id <> @UserId
where
concat('_', u.Id, '_', p.Id) not in
(
select
(
select concat('_', gu.UserId)
from dbo.GroupUsers gu
where gu.GroupId = g.Id
order by case when gu.UserId = @UserId then 0 else 1 end
for xml path ('')
)
from
dbo.Groups g
)
)
select VirtualGroupId, GroupName, UserId = CurrentUserId from cte
union all
select VirtualGroupId, GroupName, UserId = OtherUserId from cte
order by VirtualGroupId, UserId
答案 0 :(得分:1)
我尝试使用循环方法解决问题,这是因为必须提供groups表中的id,并且它不是一个Identity列,否则实现会变得更简单。
我还使用了keyuser变量来处理以下事实:必须配对的是“ Anna”,而不是其他用户。这样可以避免简单地对用户ID值进行硬编码。
create table #Users (
Id int not null,
Name nvarchar(50) not null,
constraint PK_Users primary key clustered (Id)
);
create table #Groups (
Id int not null,
Name nvarchar(50) not null,
constraint PK_Groups primary key clustered (Id)
);
create table #GroupUsers (
GroupId int not null ,
UserId int not null ,
constraint PK_GroupUsers primary key clustered (GroupId, UserId),
constraint FK_GroupUsers_GroupId foreign key (GroupId) references #Groups (Id),
constraint FK_GroupUsers_UserId foreign key (UserId) references #Users (Id)
);
insert into #Users values (1, 'Anna'), (2, 'Berta'), (3, 'Carlie'), (4, 'Dana'), (5, 'Emil');
insert into #Groups values (1, 'Anna-Berta'), (2, 'Anna-Carlie'), (3, 'Anna-Berta-Carlie');
insert into #GroupUsers values
(1,1), (1, 2), -- 'Anna-Berta' group
(2,1), (2, 3), -- 'Anna-Carlie' group
(3,1), (3, 2), (3, 3); -- 'Anna-Berta-Carlie' group
declare @sql nvarchar(max) = '';
declare @nextgroupid int = 0;
declare @keyuser int = 0;
select @keyuser = ID from #Users where [Name]='Anna';
while exists (select 1 from #Users u inner join #Users u2 on u.Id=1 where u2.Id not in (select userid from #GroupUsers))
begin
select @nextgroupid = MAX(id)+1 from #Groups;
set @sql = 'insert #groups (id, [name]) select top 1 ' + CAST(@nextgroupid as nvarchar(3)) + ', u.[name] + ''-'' + u2.[name] as missingusergroup from #Users u inner join #Users u2 on u.Id= ' + CAST(@keyuser as nvarchar(3)) + ' where u2.Id not in (select userid from #GroupUsers) order by u2.id;';
exec(@sql);
set @sql = 'insert #groupusers (groupid, userid) select top 1 ' + CAST(@nextgroupid as nvarchar(3)) + ', u2.id from #Users u inner join #Users u2 on u.Id= ' + CAST(@keyuser as nvarchar(3)) + ' where u2.Id not in (select userid from #GroupUsers) union select top 1 '+ CAST(@nextgroupid as nvarchar(3)) +', u.id from #Users u where u.id= ' + CAST(@keyuser as nvarchar(3)) + ' order by u2.id;';
exec(@sql);
end
select * from #GroupUsers;
select * from #Groups;
drop table #Groups, #GroupUsers, #Users;
答案 1 :(得分:1)
这个简单的解决方案呢?
select u.Id, p.Id
from dbo.Users u
cross join dbo.Users p
where u.Id = 1 and p.Id <> 1
and u.Name + '-' + p.Name not in (select g.Name from dbo.Groups g);
http://sqlfiddle.com/#!18/3c30f/16
第二种解决方案,考虑到您的评论
with required_groups as (
select u.Id as userId, p.Id pairId
from dbo.Users u
cross join dbo.Users p
where u.Id = 1 and p.Id <> 1
),
existing_groups as (
select annas_groups.UserId as userId, pairs_groups.UserId as pairId
from (
select *
from GroupUsers gu
where gu.UserId = 1
and gu.GroupId in (
select x.GroupId from GroupUsers x
group by x.GroupId
having count(*) = 2
)
) annas_groups
inner join (
select *
from GroupUsers gu
where
gu.UserId <> 1
and gu.GroupId in (
select x.GroupId from GroupUsers x
group by x.GroupId
having count(*) = 2
)
) pairs_groups on annas_groups.GroupId = pairs_groups.GroupId
)
select *
from required_groups rg
where not exists (
select 1
from existing_groups eg
where eg.UserId = rg.UserId
and eg.PairId = rg.PairId
)
http://sqlfiddle.com/#!18/3c30f/34
我认为它有点复杂,但仍然可读。