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

Viewed 148

I have 3 tables:

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


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


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

This is the relation many to many.

I have also the view which shows me that relation

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

How to update this view to display record even there is no relation between in RoleScope


Example:

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

Current result:

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

Expected result:

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
4 Answers

To make all of the values appear you could use CROSS JOIN between the ROLES table and the SCOPES table followed by LEFT JOIN to ROLESCOPES. To have the appropriate columns from ROLESCOPES zero'ed out you could use 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

Use Subquery and cross join to get Cartesian join of roleid and scopeid,Full Join to prodcue null values and IIF to distinguishing null values as follows

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

There is a way to fill in the missing rows like this:

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

My query is not exactly same.

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. Created index on table rolescope.
  2. I put result of cross join between role and Scope into temp table then join the temp table and rolescope,thinking that performance will be good if there are milion of records.
  3. I forgot to change dataType from INT to TinyINT .It matter if there are millions of records.
Related