使用此模式:
CREATE TABLE tag (
tag_id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
tag_slug text UNIQUE NOT NULL,
tag_name text NOT NULL
);
我目前正在使用JavaScript生成一个批量UPSERT命令,如下所示(它使用knex.raw):
knex.raw(
`
INSERT INTO tag (tag_slug, tag_name)
VALUES ${tags.map(() => `(?, ?)`).join(", ")}
ON CONFLICT (tag_slug)
DO UPDATE SET tag_name = excluded.tag_name
RETURNING *
`,
tags.map((t) => [t.slug, t.text]).flat()
)
但我想将其转换为一个名为upsert_tags
的存储过程,可以像这样调用(请随意将函数签名调整为同样或更符合人体工程学的东西):
call upsert_tags(
[
('first-tag', 'First Tag'),
('second-tag', 'Second Tag')
]
);
我该怎么做?
这是我迄今为止最好的尝试,但它肯定不起作用!
CREATE PROCEDURE upsert_tags(tags array)
LANGUAGE SQL
AS $$
INSERT INTO tag (tag_slug, tag_name)
VALUES (unnest(tags))
ON CONFLICT (tag_slug)
DO UPDATE SET tag_name = excluded.tag_name
RETURNING *
$$
返回值只需要包含tag_id
,因为我使用它将数据输入到直通表中,以记录many:many关系。
1条答案
按热度按时间gfttwv5a1#
FUNCTION
对比PROCEDURE
RETURNING *
表示您希望返回一组行-准确地说是SETOF tag
。The manual:过程不返回函数值;因此
CREATE PROCEDURE
缺少RETURNS
子句。但是,过程可以通过输出参数将数据返回给调用方。输出参数不会削减它。
PROCEDURE
是一个错误的选择。使用FUNCTION
代替。参见:实现
有许多可能的方式如何格式化输入数据。
有很多种方法可以正确地处理冲突。
返回数据的方法有很多种。
我们几乎可以做任何事情。我们甚至可以使它的大部分是动态的和/或通用的,以处理不同的表/列。(考虑将来可能对表进行的更改...)请参阅:
最好的解决方案取决于你需要什么 * 确切 *。演示两个实现。
实现一:传递两个独立的Postgres数组
对于两列,传递两个单独的数组可能比较方便。Postgres有一个专用的
unnest()
变体来并行解嵌套数组。参见:所以:
电话:
返回
SETOF public.tag
将返回与原始行相同的完整结果行。它引入了对表的行类型的依赖性,这有利有弊……关于将数组传递给函数:
实现二:传递对象的JSON数组
您提到了JavaScript,所以传递一个JSON对象数组可能会很方便,然后我们用
json_populate_recordset()
分解它。(还有其他选项,如json_to_recordset()
...)电话:
fiddle
并发?表演?
如果可以从多个事务并发地调用函数(或者以任何方式在同一表上并发地竞争写入),则存在复杂的竞争条件。
这两种解决方案(包括您的原始解决方案)都会覆盖冲突的行,即使
tag_name
没有更改,这也会增加不做任何有用操作的成本。如果它发生了很多,这很重要。我们可以跳过这一步,但您可能仍然希望获得与输入匹配的完整输出行集?对于这些问题,请参见:
*如何在PostgreSQL中使用RETURNING with ON CONFLICT?
如果您还想知道是否插入或更新了每一行,请参阅:
最终实现
基于您在评论中添加的解决方案。对于您的特定情况,这种实现是有意义的。
fiddle
我在结果行中添加了
tag_slug
以允许反向链接。所有这些都假设您的输入中**没有重复项 *。否则你需要做更多。相关内容:
由于输入中没有重复,因此普通的
JOIN
在最终的SELECT
中的性能优于IN
。IN
也会尝试删除右侧的重复项...最后,关于对直通表后续写入:您可以将其集成到同一函数中以优化性能。相关内容: