elpddev
elpddev

Reputation: 4484

Limit many to many relationship by number in Sql

Three tables: users, roles and a pivot table (many to many) role_user.

user:
- id
- name

role:
- id
- name

role_user
- id 
- user_id: foreign key link to user
- role_id: foreign key link to role

If I wanted to limit the amounts of maximum roles a user can have to only 1 for example, I could put the role_id foreign link on the user as a role_1 field instead of using a pivot table of many to many.

users:
- id
- name
- role_id_1

The same goes if I wanted only two roles per user.

users:
- id
- name
- role_id_1
- role_id_2

What if I wanted to limit the amount to 1, 2 or something else using a pivot table (Not using foreign role links on the user table) ? Is there an option for that in sql ?

Something like a composite unique index option including role_id and user_id in the pivot table, but instead of a constraint on the uniqueness, a custom constraint on the limit of the user_id number of appearances.

Upvotes: 1

Views: 3034

Answers (1)

Gordon Linoff
Gordon Linoff

Reputation: 1269593

There is a way you can implement this in SQL without triggers. It is a bit complicated, but you could do it.

It starts by adding another table. Let me call it RoleNumbers. This table would consist of one row for each possible role for a user. So, you set it up with 1, 2, or however many roles you want.

Then for the junction table:

create table UserRoles (
    UserRoleId int not null auto_increment primary key,
    UserId int not null references users(user_id),
    RoleId int not null references roles(role_id),
    RoleNumber int not null references RoleNumbers(Number),
    unique (UserId, RoleId),
    unique (UserId, RoleNumber)
);

This uses my naming conventions. I have no problem with having a synthetic key on a junction table.

When you insert a new record, you would have to assign a value to RoleNumber that is not already being used. Hence, you get the limit. The most efficient way to do this is via triggers, but that is not strictly necessary. You could do an insert as:

insert into UserRoles(UserId, RoleId, RoleNumber)
    select $UserId, $RoleId, coalesce(max(RoleNumber), 0) + 1
    from UserRoles
    where UserId = $UserId;

delete would require a separate query for maintaining the numbering scheme.

Upvotes: 3

Related Questions