簡體   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