Reputation: 33459
I have 3 tables - users
, teams
and team_members
. The latter is a many-to-many map from team(id)
to user(id)
(foreign keys to teams
and users
respectively). Is there any integrity check I can add to my database that can assert that while teams with no members is possible but users with no teams is impossible? To clarify, I want to enforce at the database layer that all users must belong to atleast 1 team (while no such requirement that all teams must have 1 user). An answer that works in MYSQL or Postgres is acceptable.
Upvotes: 2
Views: 646
Reputation: 324511
(Answer assumes PostgreSQL; details on triggers and locking will vary if you use another RDBMS, but most RDBMSes should be able to support the same underlying operations.)
While it's possible to add a foreign key from users
to teams
, doing that requires duplicating knowledge - you'd basically be creating an extra m:1 relationship in addition to the exisitng m:n relationship. That's undesirable.
If you don't need much concurrency, the best option here is probably to use deferred constraint triggers and table locking. Add:
a deferred constraint trigger ON INSERT OR UPDATE ... FOR EACH ROW
trigger on users
that does a LOCK TABLE users, team_members IN EXCLUSIVE MODE
and then checks that the created user, if it hasn't since been deleted, has at least one team using a join through team_members
. Your application will want to LOCK TABLE users IN EXCLUSIVE MODE
before writing to it as well to prevent deadlocks. Note that EXCLUSIVE MODE
does not prevent SELECT
.
a deferred constraint trigger ON UPDATE OR DELETE ... FOR EACH ROW
on team_members
that does the same thing in reverse, making sure that if you delete a team membership then the user who was a member still has other team memberships. It must also lock both users
and team_members
.
Of course, team_members
also needs FK constraints to users
and teams
but that should just be assumed for a m:n join table.
If you don't mind having to carefully do things in specific orders, e.g. always add a new membership before deleting an old one, you can use a normal trigger instead of a deferred constraint trigger. That'll give you errors immediately after you do something wrong, instead of at COMMIT time, but will make certain orderings of statements that would otherwise be valid into error conditions.
If you do need good concurrency, you're probably stuffed.
Upvotes: 3