插入和返回新记录或现有记录的 ID,在 Postgres 12.5 中需要更好的策略
INSERTING and RETURNING the ID of a new or existing record, need a better strategy in Postgres 12.5
我正在尝试在 Postgres 12.5 中做一些事情,但我要么遗漏了一个细节,要么完全追求一个有缺陷的策略。我有一个简单的查找 table,其中包含一些更长的字符串和扩展的详细信息。当数据流入主 table 时,我想将这些字符串转换为键的简单 int。我认为我可以将查找保存在 table 中,然后使用 INSERT....ON CONFLICT DO NOTHING...RETURNING
获得这些结果:
- 如果查找中不存在该行 table,请创建它并 return 新的
id
。 <-- 这行得通。
- 如果该行确实存在,请不要覆盖它并 return 新的
id
。 <-- id
未 returned。
第二种情况更为常见。有可能,系统将 运行 在新值命中查找 table 之间持续数天或数周。我更喜欢果断地执行 INSERT
以避免并发问题或唯一冲突。然而,RETURNING
并没有像我希望的那样工作。我检查了文档,清楚地记录了我所看到的行为。而且,考虑一下,很有意义。因此,展望未来,编写一个可以测试结果并发出 SELECT id FROM pgconfig_target WHERE...
以恢复现有 id
的函数是否更有意义,还是其他一些更直接的策略?
对于背景,主要 table 是一个 push_audit_log
总结来自我们客户的 insert
电话,包括时间、记录计数和其他细节。将有 很多 这些记录。这就是为什么我宁愿将字符串转移到另一个 table.
这是查找 table 设置的简短版本:
--------------------------------------
-- Define table
------------------------------------
BEGIN;
DROP TABLE IF EXISTS dba.pgconfig_target CASCADE;
CREATE TABLE IF NOT EXISTS dba.pgconfig_target (
id int2 GENERATED ALWAYS AS IDENTITY,
schema_name citext NOT NULL DEFAULT NULL,
target_name citext NOT NULL DEFAULT NULL,
target_path citext NOT NULL DEFAULT NULL
);
ALTER TABLE dba.pgconfig_target
OWNER TO user_change_structure;
COMMIT;
------------------------------------
-- Build indexes
------------------------------------
CREATE UNIQUE INDEX pgconfig_target_unique_ix_btree
ON dba.pgconfig_target (schema_name, target_name, target_path);
这是我正在试验的INSERT
:
INSERT INTO pgconfig_target (schema_name, target_name, target_path)
VALUES ('hello','world','passthrough.hello.world')
ON CONFLICT (schema_name,target_name,target_path) do nothing
RETURNING id;
感谢您的建议。
#稍后#
我想使用像列出的(曾经有用的)GMB 这样的建议,但似乎无法让它在第一个 运行 上工作。我很好奇为什么,因为我不理解有关命令或执行的某些内容。
我尝试了一点 scratch 函数,它起作用了,但如果可能的话,纯 SQL 语句解决方案似乎更好一些。这是函数:
CREATE OR REPLACE FUNCTION ascendco.pgconfig_target_add_if_missing (
s_name citext,
t_name citext,
t_path citext
)
RETURNS integer AS
$BODY$
DECLARE
id_out integer = 0;
BEGIN
-- Insert the value, if it's missing.
INSERT INTO pgconfig_target (schema_name, target_name, target_path)
VALUES (s_name, t_name, t_path)
ON CONFLICT DO NOTHING;
-- The ID should be there, either historically or becase it was just added.
SELECT id
FROM pgconfig_target
WHERE schema_name = s_name
AND target_name = t_name
AND target_path = t_path
INTO id_out;
return id_out;
END
$BODY$
LANGUAGE plpgsql;
这样的示例调用有效:
select * from pgconfig_target_add_if_missing ('hello', 'world', 'checking.it.out')
我正在使用 select *
,因为我没有整理出使 id
成为明确结果的语法。
这就是 on conflict
的工作原理。当没有插入行时,不返回任何内容。
解决方法是使用 CTE 重新表述逻辑:首先插入(或什么也不做),然后从 table 再次 select,使用输入数据进行过滤。
with
data as (
select *
from (values
('hello','world','passthrough.hello.world')
) v(schema_name, target_name, target_path)
),
ins as (
insert into pgconfig_target (schema_name, target_name, target_path)
select *
from data
on conflict (schema_name, target_name, target_path) do nothing
)
select c.id
from pgconfig_target c
inner join data d using (schema_name, target_name, target_path)
我正在尝试在 Postgres 12.5 中做一些事情,但我要么遗漏了一个细节,要么完全追求一个有缺陷的策略。我有一个简单的查找 table,其中包含一些更长的字符串和扩展的详细信息。当数据流入主 table 时,我想将这些字符串转换为键的简单 int。我认为我可以将查找保存在 table 中,然后使用 INSERT....ON CONFLICT DO NOTHING...RETURNING
获得这些结果:
- 如果查找中不存在该行 table,请创建它并 return 新的
id
。 <-- 这行得通。 - 如果该行确实存在,请不要覆盖它并 return 新的
id
。 <--id
未 returned。
第二种情况更为常见。有可能,系统将 运行 在新值命中查找 table 之间持续数天或数周。我更喜欢果断地执行 INSERT
以避免并发问题或唯一冲突。然而,RETURNING
并没有像我希望的那样工作。我检查了文档,清楚地记录了我所看到的行为。而且,考虑一下,很有意义。因此,展望未来,编写一个可以测试结果并发出 SELECT id FROM pgconfig_target WHERE...
以恢复现有 id
的函数是否更有意义,还是其他一些更直接的策略?
对于背景,主要 table 是一个 push_audit_log
总结来自我们客户的 insert
电话,包括时间、记录计数和其他细节。将有 很多 这些记录。这就是为什么我宁愿将字符串转移到另一个 table.
这是查找 table 设置的简短版本:
--------------------------------------
-- Define table
------------------------------------
BEGIN;
DROP TABLE IF EXISTS dba.pgconfig_target CASCADE;
CREATE TABLE IF NOT EXISTS dba.pgconfig_target (
id int2 GENERATED ALWAYS AS IDENTITY,
schema_name citext NOT NULL DEFAULT NULL,
target_name citext NOT NULL DEFAULT NULL,
target_path citext NOT NULL DEFAULT NULL
);
ALTER TABLE dba.pgconfig_target
OWNER TO user_change_structure;
COMMIT;
------------------------------------
-- Build indexes
------------------------------------
CREATE UNIQUE INDEX pgconfig_target_unique_ix_btree
ON dba.pgconfig_target (schema_name, target_name, target_path);
这是我正在试验的INSERT
:
INSERT INTO pgconfig_target (schema_name, target_name, target_path)
VALUES ('hello','world','passthrough.hello.world')
ON CONFLICT (schema_name,target_name,target_path) do nothing
RETURNING id;
感谢您的建议。
#稍后# 我想使用像列出的(曾经有用的)GMB 这样的建议,但似乎无法让它在第一个 运行 上工作。我很好奇为什么,因为我不理解有关命令或执行的某些内容。
我尝试了一点 scratch 函数,它起作用了,但如果可能的话,纯 SQL 语句解决方案似乎更好一些。这是函数:
CREATE OR REPLACE FUNCTION ascendco.pgconfig_target_add_if_missing (
s_name citext,
t_name citext,
t_path citext
)
RETURNS integer AS
$BODY$
DECLARE
id_out integer = 0;
BEGIN
-- Insert the value, if it's missing.
INSERT INTO pgconfig_target (schema_name, target_name, target_path)
VALUES (s_name, t_name, t_path)
ON CONFLICT DO NOTHING;
-- The ID should be there, either historically or becase it was just added.
SELECT id
FROM pgconfig_target
WHERE schema_name = s_name
AND target_name = t_name
AND target_path = t_path
INTO id_out;
return id_out;
END
$BODY$
LANGUAGE plpgsql;
这样的示例调用有效:
select * from pgconfig_target_add_if_missing ('hello', 'world', 'checking.it.out')
我正在使用 select *
,因为我没有整理出使 id
成为明确结果的语法。
这就是 on conflict
的工作原理。当没有插入行时,不返回任何内容。
解决方法是使用 CTE 重新表述逻辑:首先插入(或什么也不做),然后从 table 再次 select,使用输入数据进行过滤。
with
data as (
select *
from (values
('hello','world','passthrough.hello.world')
) v(schema_name, target_name, target_path)
),
ins as (
insert into pgconfig_target (schema_name, target_name, target_path)
select *
from data
on conflict (schema_name, target_name, target_path) do nothing
)
select c.id
from pgconfig_target c
inner join data d using (schema_name, target_name, target_path)