[英]Postgresql unique constraint for colums of multiple related tables
I have the following tables:我有以下表格:
server (server_id as PK)服务器(server_id 作为 PK)
server_id | server_name
----------+---------------
3251623 | ServerOfDoom
5578921 | BestServerEU
server_groups server_groups
Each server can have multiple server groups
, every group name within a server
must be unique.每个服务器可以有多个
server groups
,一个server
内的每个组名必须是唯一的。
id | server_id | group_name
----+-----------+---------------
1 | 3251623 | mods
2 | 5578921 | admins
3 | 5578921 | anothergroup
group_tags group_tags
Each group can have multiple tags
, a tag can be connected to multiple groups
.每个组可以有多个
tags
,一个标签可以连接多个groups
。 Each tag name
must be unique.每个
tag name
必须是唯一的。
id | tag_name
----+--------------
1 | mods
2 | admins
3 | ag
group_tag_relations group_tag_relations
group_id | tag_id
----------+--------
2 | 1
3 | 3
1 | 2
I need group tag names
and server group names
to be unique within one server
.我需要
group tag names
和server group names
unique within one server
是unique within one server
。 So a tag
can be connected to multiple groups
, but only to one group per server .所以一个
tag
可以连接到多个groups
,但每个服务器只能连接一个组。 Or the tag "foo"
cannot be connected to the group "bar"
of server z
because server z
already has a group "foo"
.或者
tag "foo"
不能连接到server z
的group "bar"
因为server z
已经有一个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.反之亦然,因此如果
group "foo"
具有tag "hey"
,则不应将group "hey"
添加到同一服务器。
Basically names should not appear multiple times on a server, no matter if tag or group name.基本上名称不应在服务器上多次出现,无论是标签还是组名称。
How would I implement such a Constraint?我将如何实施这样的约束?
So basically I'm trying to convert this JSON format into SQL tables:所以基本上我正在尝试将此 JSON 格式转换为 SQL 表:
{
"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. id 的 5578921 和 3251623 应该只代表 Discord 服务器的 id,所以每个 id 都代表我的机器人所在的一台服务器。 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.
Json 文件是我的 bot 对每个服务器的所有信息,但它不像数据库那样可靠和可扩展,所以我想转换它。
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).间的一种一对多的关系,
server
和server_groups
之间的许多一对多的关系server_groups
和group_tags
(所以不是存储重复的标签我可以将它们分配到不同的组)。
I just want to make sure there are no duplicate names on one server and asking how to do so for my current tables.我只想确保一台服务器上没有重复的名称,并询问如何为我当前的表执行此操作。
As mentioned before, a stored procedure or function with triggers is probably the way to go.如前所述,带有触发器的存储过程或函数可能是可行的方法。
The code could look something like this:代码可能如下所示:
CREATE FUNCTION public."CHECK_TAG_AND_GROUP_NAME_UNIQUE_PER_SERVER"()
RETURNS trigger
LANGUAGE 'plpgsql'
NOT LEAKPROOF
AS $BODY$
DECLARE
countServerGroupsWithNamePerServer integer;
countGroupTagsWithNamePerServer integer;
BEGIN
-- Count occurrences of name in server_groups table
SELECT COUNT(*)
FROM server_groups
INTO countServerGroupsWithNamePerServer
WHERE "name" = NEW.name
AND "server_id" = NEW.server_id;
-- Check if one exists. If it does, throw error
IF countServerGroupsWithNamePerServer > 0 THEN
RAISE 'Name already exists as a group server name %', NEW.name;
END IF;
-- Count occurrences of name in group_tags table
SELECT COUNT(*)
FROM group_tags
INTO countGroupTagsWithNamePerServer
WHERE "name" = NEW.name
AND "server_id" = NEW.server_id;
-- Check if one exists. If it does, throw error
IF countGroupTagsWithNamePerServer > 0 THEN
RAISE 'Name already exists as a group_tag name %', NEW.name;
END IF;
-- If no error is thrown, insert the new record
RETURN NEW;
END;
$BODY$;
and then you attach the function as a BEFORE INSERT
trigger to each of the two tables group_tags
and server_groups
:然后将该函数作为
BEFORE INSERT
触发器附加到两个表group_tags
和server_groups
每一个:
CREATE TRIGGER "BEFORE_INSERT_CHECK_TAG_NAME_UNIQUE_PER_SERVER"
BEFORE INSERT
ON public.group_tags
FOR EACH ROW
EXECUTE PROCEDURE public."CHECK_TAG_AND_GROUP_NAME_UNIQUE_PER_SERVER"();
CREATE TRIGGER "BEFORE_INSERT_CHECK_TAG_NAME_UNIQUE_PER_SERVER"
BEFORE INSERT
ON public.server_groups
FOR EACH ROW
EXECUTE PROCEDURE public."CHECK_TAG_AND_GROUP_NAME_UNIQUE_PER_SERVER"();
Please notice for this example I also added a foreign key column server_id
to the group_tags
table.请注意,对于这个示例,我还在
group_tags
表中添加了一个外键列server_id
。 Otherwise we are not sure to which server the tag belongs.否则我们不确定标签属于哪个服务器。 This is just a rough guide though, please feel free to change it up as much as you want.
不过,这只是一个粗略的指南,请随时根据需要进行更改。
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更新
server_groups
表的触发器,检查名称是否已在服务器上使用
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为
server_groups
表插入触发器,检查名称是否已在服务器上使用
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)为
group_tag_relation
表插入触发器,检查标签是否已经在服务器上使用(因为标签不会更新,所以不需要更新触发器)
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();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.