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