我有一个帖子表、一个主题标签表以及一个将帖子链接到主题标签的表,如下所示:
CREATE TABLE posts(
id SERIAL PRIMARY KEY,
post_data VARCHAR(128) NOT NULL
);
CREATE TABLE hashtags (
id SERIAL PRIMARY KEY,
value VARCHAR(128) NOT NULL
);
CREATE TABLE post_hashtags(
id SERIAL PRIMARY KEY,
post_id INTEGER NOT NULL REFERENCES posts(id),
hashtag_id INTEGER NOT NULL REFERENCES hashtag(id)
);
INSERT INTO posts(post_data) VALUES ('post1');
INSERT INTO hashtags (value) VALUES ('hashtag1'), ('hashtag2'), ('hashtag3');
-- At this point I might want to add links between post1 and existing hashtags as well as possibly new ones
每当用户使用多个哈希标签发表帖子时,我想:
posts
中创建一个新行并获取IDhashtags
中为不存在的主题标签创建新行并获取其 ID post_hashtags
中使用帖子和主题标签 ID 创建一行现在我能够在服务器端处理它,但显然这是很糟糕的性能;插入帖子并获取 ID;对于每个主题标签,插入
hashtags
,如果不存在,获取ID;然后插入post_hashtags
。我猜想可以简化对数据库的大量调用,但我目前缺乏 SQL 技能
您可以使用数据修改 CTE 通过单个查询安全高效地完成此操作:
WITH input(post_data, tags) AS ( -- provide single data row with array of tags
VALUES ('post2', '{hashtag1, hashtag2, hashtag4}'::text[]) -- single post!
)
, tag_set AS ( -- unnest tags - may be empty/missing (?)
SELECT unnest(i.tags) AS value
FROM input i
)
, ins_p AS (
INSERT INTO posts (post_data)
SELECT i.post_data
FROM input i
RETURNING id AS post_id
)
, ins_h AS (
INSERT INTO hashtags (value)
SELECT t.value
FROM tag_set t
WHERE NOT EXISTS (SELECT FROM hashtags h WHERE h.value = t.value) -- optional to avoid burning lots of serial IDs
ON CONFLICT (value) DO NOTHING
RETURNING id AS hashtag_id
)
INSERT INTO post_hashtags
( post_id, hashtag_id)
SELECT p.post_id, t.hashtag_id
FROM ins_p p
CROSS JOIN ( -- only if actual tags were entered
TABLE ins_h -- new tags
UNION ALL
SELECT h.id AS hastag_id -- pre-existing tags
FROM tag_set t
JOIN hashtags h USING (value)
) t
RETURNING *;
即使在并发写入负载很重的情况下,这也是安全的。
仍然存在两种可能的极端情况:
并发事务可能会创建相同的新主题标签,但随后回滚,导致表
hashtags
中缺少该查询的条目,从而引发异常。
预先存在的主题标签可能会被在此处查找和插入到
post_hashtags
之间的并发事务删除。再次提出例外。极不可能,但有可能。
如果出现这两个不太可能出现的问题之一,您可以重新运行此查询。
你们在同一个查询中涵盖了这两个方面。我懒得走那么远。请参阅此处的说明和详细说明: