SQL 视图显示即使在多对多关系中没有匹配的记录

SQL view to display records even no matching in many to many relation

我有 3 个表:

Role         
------
[Id],
[Name]


Scope
------
[Id],
[Name]


RoleScope
------
[RoleId],
[ScopeId],
[View],
[Add],
[Edit],
[Delete],

这是多对多的关系。

我也有这样的观点

CREATE VIEW View_RolesWithScopes
AS
SELECT
    [Role].Id AS RoleId,
    [Role].[Name] AS RoleName,
    [RoleScope].ScopeId,
    [Scope].[Name] AS ScopeName,
    [RoleScope].[View],
    [RoleScope].[Add],
    [RoleScope].[Edit],
    [RoleScope].[Delete]
FROM 
    dbo.[Role] [Role]
    LEFT JOIN dbo.RoleScope [RoleScope] ON [RoleScope].RoleId = [Role].Id 
    LEFT JOIN dbo.Scope [Scope] ON [Scope].Id = [RoleScope].ScopeId

即使 RoleScope 中没有关系,如何更新此视图以显示记录


示例:

Role
------
Id | Name
1  | Role1
2  | Role2


Scope
------
Id | Name
1  | Scope1
2  | Scope2
3  | Scope3


RoleScope
------
RoleId | ScopeId | View | Add | Edit | Delete
1      | 1       | 1    | 1   | 1    | 0
1      | 2       | 1    | 0   | 1    | 0

当前结果:

View_RolesWithScopes
------
RoleId | ScopeId | View | Add | Edit | Delete
1      | 1       | 1    | 1   | 1    | 0
1      | 2       | 1    | 0   | 1    | 0
2      | NULL    | NULL | NULL| NULL | NULL

预期结果:

View_RolesWithScopes
------
RoleId | ScopeId | View | Add | Edit | Delete
1      | 1       | 1    | 1   | 1    | 0
1      | 2       | 1    | 0   | 1    | 0
1      | 3       | 0    | 0   | 0    | 0
2      | 1       | 0    | 0   | 0    | 0
2      | 2       | 0    | 0   | 0    | 0
2      | 3       | 0    | 0   | 0    | 0

要显示所有值,您可以在 ROLES table 和 SCOPES table 之间使用 CROSS JOIN,然后使用 LEFT JOIN 到 ROLESCOPES。要将 ROLESCOPES 中的适当列归零,您可以使用 ISNULL。

drop table if exists #roles;
go
create table #roles (
  r_id              int primary key not null, 
  [name]            varchar(10) not null);

insert #roles(r_id, [name]) values
(1, 'Role1'),
(2, 'Role2');

drop table if exists #scopes;
go
create table #scopes (
  s_id              int primary key not null, 
  [name]            varchar(10) not null);

insert #scopes(s_id, [name]) values
(1, 'Scope1'),
(2, 'Scope2'),
(3, 'Scope3');

drop table if exists #rolescopes;
go
create table #rolescopes (
  r_id              int not null, 
  s_id              int not null, 
  [View]            int not null,
  [Add]             int not null,
  [Edit]            int not null,
  [Delete]          int not null);

insert #rolescopes(r_id, s_id, [View], [Add], [Edit], [Delete]) values
(1, 1, 1, 1, 1, 0),
(1, 2, 1, 0, 1, 0);

select r.r_id, s.s_id,
       isnull(rs.[View],0) [View],
       isnull(rs.[Add],0) [Add],
       isnull(rs.[Edit],0) [Edit],
       isnull(rs.[Delete],0) [Delete]
from #roles r
     cross join #scopes s
     left join #rolescopes rs on r.r_id=rs.r_id
                                 and s.s_id=rs.s_id;
r_id    s_id    View    Add Edit    Delete
1       1       1       1   1       0
1       2       1       0   1       0
1       3       0       0   0       0
2       1       0       0   0       0
2       2       0       0   0       0
2       3       0       0   0       0

使用Subquerycross join获取roleid和scopeid的笛卡尔连接,Full Join产生空值,IIF区分空值如下

SELECT RS.roleid,
       RS.scopeid,
       IIF(RSS.[view] IS NULL, 0, RSS.[view])     [View],
       IIF(RSS.[add] IS NULL, 0, RSS.[add])       [Add],
       IIF(RSS.[edit] IS NULL, 0, RSS.[edit])     [Edit],
       IIF(RSS.[delete] IS NULL, 0, RSS.[delete]) [Delete]
FROM   (SELECT r.id AS RoleId,
               s.id AS ScopeId
        FROM   roles r
               CROSS JOIN scopes s) RS
       FULL JOIN rolescopes RSS
              ON RS.roleid = RSS.roleid
                 AND RS.scopeid = RSS.scopeid  

dbfiddle

有一种方法可以像这样填充缺失的行:

SELECT * FROM RoleScope
UNION SELECT
  Role.Id, Scope.Id, 0, 0, 0, 0
FROM Role CROSS JOIN Scope
WHERE NOT EXISTS (
  SELECT 1 FROM RoleScope
  WHERE Role.id=RoleScope.RoleId AND
        Scope.id=RoleScope.ScopeId);

dbfiddle

我的查询不完全相同。

drop table if exists #roles;
go
create table #roles (
  RoleId              int primary key not null, 
  [name]            varchar(10) not null);

insert #roles(RoleId, [name]) values
(1, 'Role1'),
(2, 'Role2');
drop table if exists #scopes;
go
create table #scopes (
  ScopeId              int primary key not null, 
  [name]            varchar(10) not null);

insert #scopes(ScopeId, [name]) values
(1, 'Scope1'),
(2, 'Scope2'),
(3, 'Scope3');

drop table if exists #rolescopes;
go
create table #rolescopes (
roleScopeId              int identity(1,1) primary key not null, 
  RoleId              int not null, 
  ScopeId              int not null, 
  [View]            int not null,
  [Add]             int not null,
  [Edit]            int not null,
  [Delete]          int not null);

create nonclustered index ix_RID_SID on #rolescopes(RoleId,ScopeId) include([View],[Add],[Edit],[Delete]  )

insert #rolescopes(RoleId, ScopeId, [View], [Add], [Edit], [Delete]) values
(1, 1, 1, 1, 1, 0),
(1, 2, 1, 0, 1, 0);





    SELECT
    [Role].RoleId AS RoleId,
    [Role].[Name] AS RoleName,
    [Scope].ScopeId,
    [Scope].[Name] AS ScopeName
   into #temp 
FROM 
    #scopes [Scope] 
     join
     #roles [Role] on 1=1

     select t.* ,
     isnull([RoleScope].[View],0) [View],
    isnull([RoleScope].[Add],0) [Add],
    isnull([RoleScope].[Edit],0) [Edit],
    isnull([RoleScope].[Delete],0) [Delete]
     from  #temp t
     LEFT JOIN #rolescopes [RoleScope] ON t.ScopeId = [RoleScope].ScopeId 

     drop table #temp
  1. 在 table rolescope 上创建了索引。
  2. 我将 cross join 的结果放在 roleScope 之间,放入 temp table 然后加入 temp table和rolescope,认为performance如果有百万条记录就好了。
  3. 我忘记将数据类型从 INT 更改为 TinyINT。如果有数百万条记录,这很重要。