繁体   English   中英

多个相关表的列的 Postgresql 唯一约束

[英]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 namesserver group names unique within one serverunique within one server 所以一个tag可以连接到多个groups ,但每个服务器只能连接一个组 或者tag "foo"不能连接到server zgroup "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 对每个服务器的所有信息,但它不像数据库那样可靠和可扩展,所以我想转换它。

所以上面的表格是我想出的:

一种一对多的关系, serverserver_groups之间的许多一对多的关系server_groupsgroup_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_tagsserver_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM