Member-Role is N:N relationship.
Member MemberRole Role
+----------+ +----------+ +----------+
| Id | | MemberId | | Id |
| ... | | RoleId | | Name |
+----------+ +----------+ +----------+
- There are ~15,000 members, which have a varying and possibly zero number of roles.
- There are ~50 roles.
Outside of the database, I have allow-deny lists I need to check against the database. A list might look like
+a -b +c +d
This means:
- If has role a,
- ALLOW
- Else,
- If has role b,
- DENY
- Else,
- If has role c,
- ALLOW
- Else,
- If has role d,
- ALLOW
- Else,
- DENY
- If has role d,
- If has role c,
- If has role b,
For example, someone with roles a and d would allowed, while someone with roles b and d would be disallowed.
The lists can easily be converted into a mathematical equation by starting from the end, applying a union (∪) for +
terms and a difference (-) for -
terms.
+a -b +c +d
⇒ ( ( ( ∅ ∪ d ) ∪ c ) - b ) ∪ a
Any trailing deny (-
) can be ignored, so we know the first will always be a union.
+a -b +c +d
⇒ ( ( d ∪ c ) - b ) ∪ a
From that, I can build the following query:
SELECT `MemberId` FROM `MemberRole` WHERE `RoleId` = @d
UNION SELECT `MemberId` FROM `MemberRole` WHERE `RoleId` = @c
EXCEPT SELECT `MemberId` FROM `MemberRole` WHERE `RoleId` = @b
UNION SELECT `MemberId` FROM `MemberRole` WHERE `RoleId` = @a
Since x - y = x ∩ y', we can also derive
+a -b +c +d
⇒ ( ( d ∪ c ) ∩ b' ) ∪ a
From that, I can build the following query:
SELECT `Id`
FROM `Member`
WHERE (
(
EXISTS ( SELECT * FROM `MemberRole` WHERE `MemberId` = `Member`.`Id` AND `RoleId` = @d )
OR
EXISTS ( SELECT * FROM `MemberRole` WHERE `MemberId` = `Member`.`Id` AND `RoleId` = @c )
)
AND
NOT EXISTS ( SELECT * FROM `MemberRole` WHERE `MemberId` = `Member`.`Id` AND `RoleId` = @b )
)
OR
EXISTS ( SELECT * FROM `MemberRole` WHERE `MemberId` = `Member`.`Id` AND `RoleId` = @a )
What's the best way to check if a member is allowed? (The answer will usually be yes.) Would using WITH
help?
Note that the items of the allow-deny list can be role ids (number) or role names (not a number).