I have the following tables:
server (server_id as PK)
server_id | server_name ----------+--------------- 3251623 | ServerOfDoom 5578921 | BestServerEU
server_groups
Each server can have multiple server groups
, every group name within a server
must be unique.
id | server_id | group_name ----+-----------+--------------- 1 | 3251623 | mods 2 | 5578921 | admins 3 | 5578921 | anothergroup
group_tags
Each group can have multiple tags
, a tag can be connected to multiple groups
. Each tag name
must be unique.
id | tag_name ----+-------------- 1 | mods 2 | admins 3 | ag
group_tag_relations
group_id | tag_id ----------+-------- 2 | 1 3 | 3 1 | 2
Problem:
I need group tag names
and server group names
to be unique within one server
. So a tag
can be connected to multiple groups
, but only to one group per server. Or the tag "foo"
cannot be connected to the group "bar"
of server z
because server z
already has a group "foo"
. The same goes for the other way around, so if the group "foo"
had the tag "hey"
you shouldn’t be able to add a group "hey"
to the same server.
Basically names should not appear multiple times on a server, no matter if tag or group name.
How would I implement such a Constraint?
EDIT
So basically I’m trying to convert this JSON format into SQL tables:
{ "5578921": { "Server Name": "Server1", ... "Groups": { "Game1": { "Tags": [ "g1", "gameone" ], ... }, "Game2": { "Tags": [ "g2", "gametwo" ], ... } } }, "3251623": ... }
The id’s 5578921 and 3251623 should just represent Discord server id’s, so every id is for one server my bot is on. The Json file is all the information of my bot for each server, but it’s not as reliable and scalable as a database, so I wanted to convert it.
So the upper tables are what I came up with:
A One-To-Many relation between server
and server_groups
and a Many-To-Many relation between server_groups
and group_tags
(so instead of storing duplicate tags I can just assign them to different groups).
I just want to make sure there are no duplicate names on one server and asking how to do so for my current tables.
Advertisement
Answer
After hours of suffering I finally got what I wanted:
Get all Tags of a server
CREATE OR REPLACE FUNCTION get_server_tags(serverid BIGINT) RETURNS TABLE(group_name VARCHAR(100), tag_name VARCHAR(100), group_id BIGINT, tag_id BIGINT) AS $$ SELECT group_name, tag_name, group_id, tag_id FROM group_tag_relations JOIN server_groups ON server_groups.server_id = serverid AND server_groups.id = group_tag_relations.group_id JOIN group_tags ON group_tags.id = group_tag_relations.tag_id $$ language sql stable;
Get all Groups of a server
CREATE OR REPLACE FUNCTION get_server_group(serverid BIGINT, groupname VARCHAR(100)) RETURNS TABLE(group_name VARCHAR(100), group_id BIGINT) AS $$ SELECT group_name, id FROM server_groups WHERE server_id = serverid AND lower(group_name) = lower(groupname); $$ language sql stable;
Get a Group by Name (Calling both Functions above)
CREATE OR REPLACE FUNCTION get_group_by_name(serverid BIGINT, groupname VARCHAR(100)) RETURNS TABLE(group_name VARCHAR(100), group_id BIGINT) AS $$ BEGIN RETURN QUERY SELECT get_server_group.group_name, get_server_group.group_id FROM get_server_group(serverid, groupname); IF NOT found THEN RETURN QUERY SELECT get_server_tags.group_name, get_server_tags.group_id FROM get_server_tags(serverid) WHERE lower(tag_name) = lower(groupname); END IF; END; $$ language plpgsql stable;
Update Trigger for server_groups
table, checking wether the name is already taken on a server
CREATE OR REPLACE FUNCTION group_name_update() RETURNS TRIGGER AS $$ BEGIN PERFORM get_group_by_name(OLD.server_id, NEW.group_name); IF lower(OLD.group_name) = lower(NEW.group_name) THEN RETURN NEW; ELSIF found THEN RETURN OLD; ELSE RETURN NEW; END IF; END; $$ language plpgsql volatile; CREATE TRIGGER group_name_update_trigger BEFORE UPDATE ON server_groups FOR EACH ROW EXECUTE PROCEDURE group_name_update();
Insert Trigger for server_groups
table, checking wether the name is already taken on a server
CREATE OR REPLACE FUNCTION group_name_insert() RETURNS TRIGGER AS $$ BEGIN PERFORM get_group_by_name(NEW.server_id, NEW.group_name); IF found THEN RETURN OLD; ELSE RETURN NEW; END IF; END; $$ language plpgsql volatile; CREATE TRIGGER group_name_insert_trigger BEFORE INSERT ON server_groups FOR EACH ROW EXECUTE PROCEDURE group_name_insert();
Insert Trigger for group_tag_relation
table, checking wether the tag is already taken on a server (as the tags wont get updated there’s no need for an update trigger)
CREATE OR REPLACE FUNCTION group_tag_relation_insert() RETURNS TRIGGER AS $$ BEGIN PERFORM get_group_by_name((SELECT server_id FROM server_groups WHERE id = NEW.group_id), (SELECT tag_name FROM group_tags WHERE id = tag_id)); IF found THEN RETURN OLD; ELSE RETURN NEW; END IF; END; $$ language plpgsql volatile; CREATE TRIGGER group_tag_relation_insert_trigger BEFORE INSERT ON group_tag_relations FOR EACH ROW EXECUTE PROCEDURE group_tag_relation_insert();