插入和返回新记录或现有记录的 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 之间持续数天或数周。我更喜欢果断地执行 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)