[英]Postgresql unique constraint for colums of multiple related tables
我有以下表格:
服务器(server_id 作为 PK)
server_id | server_name
----------+---------------
3251623 | ServerOfDoom
5578921 | BestServerEU
server_groups
每个服务器可以有多个server groups
,一个server
内的每个组名必须是唯一的。
id | server_id | group_name
----+-----------+---------------
1 | 3251623 | mods
2 | 5578921 | admins
3 | 5578921 | anothergroup
group_tags
每个组可以有多个tags
,一个标签可以连接多个groups
。 每个tag name
必须是唯一的。
id | tag_name
----+--------------
1 | mods
2 | admins
3 | ag
group_tag_relations
group_id | tag_id
----------+--------
2 | 1
3 | 3
1 | 2
我需要group tag names
和server group names
unique within one server
是unique within one server
。 所以一个tag
可以连接到多个groups
,但每个服务器只能连接一个组。 或者tag "foo"
不能连接到server z
的group "bar"
因为server z
已经有一个group "foo"
。 反之亦然,因此如果group "foo"
具有tag "hey"
,则不应将group "hey"
添加到同一服务器。
基本上名称不应在服务器上多次出现,无论是标签还是组名称。
我将如何实施这样的约束?
所以基本上我正在尝试将此 JSON 格式转换为 SQL 表:
{
"5578921": {
"Server Name": "Server1",
...
"Groups": {
"Game1": {
"Tags": [
"g1",
"gameone"
], ...
},
"Game2": {
"Tags": [
"g2",
"gametwo"
], ...
}
}
},
"3251623": ...
}
id 的 5578921 和 3251623 应该只代表 Discord 服务器的 id,所以每个 id 都代表我的机器人所在的一台服务器。 Json 文件是我的 bot 对每个服务器的所有信息,但它不像数据库那样可靠和可扩展,所以我想转换它。
所以上面的表格是我想出的:
间的一种一对多的关系, server
和server_groups
之间的许多一对多的关系server_groups
和group_tags
(所以不是存储重复的标签我可以将它们分配到不同的组)。
我只想确保一台服务器上没有重复的名称,并询问如何为我当前的表执行此操作。
如前所述,带有触发器的存储过程或函数可能是可行的方法。
代码可能如下所示:
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$;
然后将该函数作为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"();
请注意,对于这个示例,我还在group_tags
表中添加了一个外键列server_id
。 否则我们不确定标签属于哪个服务器。 不过,这只是一个粗略的指南,请随时根据需要进行更改。
经过几个小时的痛苦,我终于得到了我想要的:
获取服务器的所有标签
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;
获取服务器的所有组
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;
按名称获取组(调用上面的两个函数)
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;
更新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();
为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();
为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.