简体   繁体   English

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

[英]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

Problem:问题:

I need group tag names and server group names to be unique within one server .我需要group tag namesserver group names unique within one serverunique 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 zgroup "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?我将如何实施这样的约束?

EDIT编辑

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).一种一对多的关系, serverserver_groups之间的许多一对多的关系server_groupsgroup_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_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"();

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 serverserver_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.

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