I don't see anything cyclic here. There are people and posts and two independent relationships between these entities. I would see likes as the implementation of one of these relationships.
- A person can write many posts, a post is written by one person:
1:n
- A person can like many posts, a post can be liked by many people:
n:m
The n:m relationship can be implemented with another relation: likes
.
Basic implementation
The basic implementation could look like this in PostgreSQL:
CREATE TABLE person (
person_id serial PRIMARY KEY
, person text NOT NULL
);
CREATE TABLE post (
post_id serial PRIMARY KEY
, author_id int NOT NULL -- cannot be anonymous
REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE -- 1:n relationship
, post text NOT NULL
);
CREATE TABLE likes ( -- n:m relationship
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE
, post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE
, PRIMARY KEY (post_id, person_id)
);
A post must have an author (NOT NULL
), while the existence of any likes is optional. For existing likes, however, post
and person
must both be referenced - enforced by the PRIMARY KEY
that makes both columns NOT NULL
implicitly. So anonymous likes are impossible.
About the n:m implementation:
Prevent self-like
You wrote:
(created person cannot like his own post).
That's not enforced in the above implementation, yet. You could use a trigger.
Or one of these faster / more reliable solutions:
Rock-solid for a cost
If it needs to be rock-solid, extend the FK from likes
to post
to include author_id
redundantly. Then you can rule out incest with a simple CHECK
constraint:
CREATE TABLE likes (
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE
, post_id int
, author_id int NOT NULL
, CONSTRAINT likes_pkey PRIMARY KEY (post_id, person_id)
, CONSTRAINT likes_post_fkey FOREIGN KEY (author_id, post_id)
REFERENCES post(author_id, post_id) ON UPDATE CASCADE ON DELETE CASCADE
, CONSTRAINT no_self_like CHECK (person_id <> author_id)
);
This requires an otherwise redundant UNIQUE
constraint in post
:
ALTER TABLE post ADD CONSTRAINT post_for_fk_uni UNIQUE (author_id, post_id);
I put author_id
first to supply a useful index while being at it.
Related:
Cheaper with a CHECK
constraint
Building on the "Basic implementation" above.
CHECK
constraints are meant to be immutable. Referencing another table is never immutable, we are abusing the concept a bit here. Declare the constraint NOT VALID
to properly reflect that. See:
A CHECK
constraint seems reasonable in this particular case, as the author of a post seems like an attribute that never changes. Disallow updates to that field to be sure.
We fake an IMMUTABLE
function:
CREATE OR REPLACE FUNCTION f_author_id_of_post(_post_id int)
RETURNS int
LANGUAGE sql IMMUTABLE AS
'SELECT p.author_id FROM public.post p WHERE p.post_id = $1';
Replace 'public' with the actual schema of your tables.
Use this function in a CHECK
constraint:
ALTER TABLE likes ADD CONSTRAINT no_self_like_chk
CHECK (f_author_id_of_post(post_id) <> person_id) NOT VALID;