3

I have a fairly simple database consisting of three (relevant) tables: users, permissions, and user permissions. The basic premise is simple: when a user gets created, all the records in the permissions table are automatically added to the user_permissions table with a default value. This is all working fine.

However, as I'm currently in development, I continue to add new permissions, which of course existing users won't have since those new permissions didn't exist in the permissions table when they were created. So, I had the brilliant idea to create a little stored procedure to automatically update the user_permissions table with all the permissions not currently existing in the user_permissions table.

So, in short, what I want to do is something like (pseudocode)

For each user without x permission in user_permissions, insert into user_permissions user_id and permission_id

I wasn't quite sure how to do this from an SQL POV. I played with joins and "not exists" but haven't really gotten anywhere.

You can play with my schema here: http://sqlfiddle.com/#!3/b0761/3 Thanks in advance for the help!

EDIT: Schema:

CREATE TABLE users (
  user_id      int IDENTITY(1, 1) NOT NULL,
  user_name    varchar(255),
  PRIMARY KEY (user_id));

CREATE TABLE permissions (
  permission_id          int IDENTITY(1, 1) NOT NULL, 
  permission_name        varchar(255) NOT NULL, 
  PRIMARY KEY (permission_id));

CREATE TABLE user_permissions (
  user_id       int NOT NULL, 
  permission_id int NOT NULL, 
  value         tinyint DEFAULT 0 NOT NULL,  
  PRIMARY KEY (user_id, 
  permission_id));

ALTER TABLE user_permissions ADD CONSTRAINT FK_user_pe338140 
FOREIGN KEY (permission_id)
REFERENCES permissions (permission_id);

ALTER TABLE user_permissions ADD CONSTRAINT FK_user_pe405324 
FOREIGN KEY (user_id) REFERENCES users (user_id);

INSERT INTO users(user_name) values('test_username');
INSERT INTO users(user_name) values('test_username2');

INSERT INTO permissions(permission_name) VALUES('permission_1')
INSERT INTO permissions(permission_name) VALUES('permission_2')

INSERT INTO user_permissions(user_id, permission_id, value)
VALUES(1, 1, 1)

INSERT INTO user_permissions(user_id, permission_id, value)
VALUES(2, 1, 1)

EDIT: Query so far

SELECT a.user_id, b.permission_id, 1 as 'value'
FROM USER_PERMISSIONS a right outer join 
PERMISSIONS b on a.permission_id = b.permission_id
Mansfield
  • 14,445
  • 18
  • 76
  • 112
  • possible duplicate of [Only inserting a row if it's not already there](http://stackoverflow.com/questions/3407857/only-inserting-a-row-if-its-not-already-there) – GSerg Jul 12 '12 at 18:14
  • That question doesn't answer my question of how to insert these records for every user. – Mansfield Jul 12 '12 at 18:15

4 Answers4

2
insert into user_permissions (user_id, permission_id)
select
  u.user_id,
  p.permission_id
from
  users u
  cross join permissions p
where
  not exists (select 0 from user_permissions with (updlock, holdlock)
              where user_id = u.user_id and permission_id = p.permission_id)

Reference: Only inserting a row if it's not already there

Community
  • 1
  • 1
GSerg
  • 76,472
  • 17
  • 159
  • 346
1
INSERT dbo.user_permissions([user_id], permission_id, [value])
SELECT u.[user_id], p.permission_id, 1
FROM dbo.user_permissions AS u 
CROSS JOIN dbo.[permissions] AS p
WHERE NOT EXISTS (SELECT 1 FROM dbo.user_permissions 
  WHERE [user_id] = u.[user_id]
  AND permission_id = p.permission_id
)
GROUP BY [user_id], p.permission_id;

As an aside, you should avoid names that tend to require delimiters, e.g. user_id and permissions are keywords/reserved words.

Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
  • I used to do this pattern to until I realized that I can express it this way: `FROM TableA A INNER JOIN TableC C ON EXISTS (SELECT * FROM TableB B WHERE A.AID = B.AID AND B.CID = C.CID)`. I like it better because it avoid the CROSS JOIN that is just there as a placeholder to get the table names in so you can EXISTS them, plus it moves the EXISTS out of the WHERE clause, ANSI-style. – ErikE Jul 13 '12 at 06:17
1

Mansfield, If I understand you correctly, you want to seed the user_permissions table with a value when you add a new permission.

I'll also assume that you want it to default to 0. After inserting the new permission, running this code will seed the user_permissions table for all users with a default value of 0 for any permissions currently not in use.

--insert into permissions(permission_name) values('newperm');
--select * from permissions
insert into user_permissions(user_id, permission_id, value)
select
  u.user_id, p.permission_id, 0
from
             users u
  cross join permissions p
where
      p.permission_id not in(select permission_id from user_permissions)
;
--select * from user_permissions;
Brandon
  • 695
  • 10
  • 29
  • What if a new permission has been added only for one user? (Also the default was 1 according to the sample code in the question.) – Aaron Bertrand Jul 12 '12 at 19:03
  • "So, I had the brilliant idea to create a little stored procedure to automatically update the user_permissions table with all the permissions not currently existing in the user_permissions table." So, he says that an assumption was that it was not in use. :) – Brandon Jul 12 '12 at 19:52
  • Sure, but why not write the query to insulate you from the case where things aren't executed exactly as expected? I've administered a system exactly like this, and of course we had developers testing things for their user only. – Aaron Bertrand Jul 12 '12 at 19:53
0

The below query will give you the missing userpermission rows to be inserted:

select a.USER_ID,b.permission_id from users a,permissions b,user_permissions c
   where c.user_id <>a.user_id and c.permission_id <> b.permission_id
Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
Esen
  • 973
  • 1
  • 21
  • 47
  • Ugh, old-style cartesian joins, really? – Aaron Bertrand Jul 12 '12 at 18:20
  • I hate to do this, but this is one of the way to achieve what the author is expecting. – Esen Jul 12 '12 at 18:22
  • I just try the sql in the sqlfiddle and it works. Am I missing something. Did you try this query? It does return two row – Esen Jul 12 '12 at 18:28
  • @Mansfield you know you can un-do your up-vote by clicking the up-arrow again. – Aaron Bertrand Jul 12 '12 at 18:30
  • I tried it on my actual data and got 744 rows for 9 users missing one permission...so something is obviously not quite right. – Mansfield Jul 12 '12 at 18:30
  • @AaronBertrand yes, but it's been five minutes...bit of a silly rule but it gives me an error saying I can't change my vote unless the post is edited. – Mansfield Jul 12 '12 at 18:31
  • @AaronBertrand here is the fiddle http://sqlfiddle.com/#!3/b0761/5/0. I am totally confused why you say this is giving wrong result – Esen Jul 12 '12 at 18:33
  • 1
    @Esen this should demonstrate: http://sqlfiddle.com/#!3/39a3b/1 (I added some more data) – Mansfield Jul 12 '12 at 18:35
  • 1
    Try http://sqlfiddle.com/#!3/eda3c/1 - if I *add* data to the table, your query should return *fewer* rows, not *more* rows. – Aaron Bertrand Jul 12 '12 at 18:36
  • Got it thanks for your patience to help me understand. What should I do. Delete my answer? or edit it inorder to downvote? – Esen Jul 12 '12 at 18:39
  • I see you edited and did the downvote already. Then I leave my answer as it is. – Esen Jul 12 '12 at 18:40
  • I made a minor edit so @Mansfield can change their vote if they want. You can always improve your answer if you can get the right join criteria working (and I will correct my vote). Anyway my point about the old-style joins is that you can express the same thing using `CROSS JOIN` which makes it explicitly clear that you intended to do it. – Aaron Bertrand Jul 12 '12 at 18:40
  • 1
    I'll also be glad to upvote again if you get it working :) always happy to help people making the effort to help me out! – Mansfield Jul 12 '12 at 18:43