sql - 插入和返回新记录或现有记录的 ID,在 Postgres 12.5 中需要更好的策略

标签 sql postgresql select inner-join sql-insert

我正在尝试在 Postgres 12.5 中做一些事情,但我要么遗漏了一个细节,要么完全追求一个有缺陷的策略。我有一个简单的查找表,其中包含一些较长的字符串和扩展的详细信息。当数据流入主表时,我想将这些字符串转换为一个简单的 int 作为键。我想我可以将查找保存在一个表中,然后使用 INSERT....ON CONFLICT DO NOTHING...RETURNING 来获得这些结果:

  • 如果查找表中不存在该行,则创建它并返回新的id。 <-- 这行得通。
  • 如果该行确实存在,则不要覆盖它并返回新的 id。 <-- 未返回 id

第二种情况更为常见。很有可能,系统会在新值到达查找表之间运行数天或数周。我更喜欢果断地执行 INSERT 以避免并发问题或唯一冲突。然而,RETURNING 并没有像我希望的那样工作。我检查了文档,清楚地记录了我所看到的行为。而且,考虑一下,很有意义。因此,继续前进,编写一个函数来测试结果并发出 SELECT id FROM pgconfig_target WHERE... 以恢复现有的 id 是否更有意义,还是其他一些更直接的策略?

作为背景,主表是一个 push_audit_log 总结了来 self 们客户的 insert 调用,包括时间、记录计数和其他详细信息。这些记录将会很多。这就是为什么我宁愿将字符串转移到另一个表。

这是查找表设置的简短版本:

--------------------------------------
-- 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 重新表述逻辑:首先插入(或什么都不做),然后再次从表中选择,使用输入数据进行过滤。

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)

关于sql - 插入和返回新记录或现有记录的 ID,在 Postgres 12.5 中需要更好的策略,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64920254/

相关文章:

mysql - 运行 10,000 个查询每个更新一行更快,还是运行 100 个查询每个更新 100 行更快?

oracle - 如何从 Oracle 11g 中的对象类型列中进行 SELECT?

php - MySQL选择产品-同类产品

sql - 如何快速将条目与长字符串的开头匹配?

c# - 使用 Windows 身份验证连接到 SQL Server

c# - 使用 Critieria API 在 NHibernate 中选择子查询

php - 在 SQL 中插入多行

postgresql - 当用户提供任意 SQL 标识符时如何防止注入(inject)?

sql - 无法在 PostgreSQL 中查询表详细信息

php - 使用多个值更新数据库中的选择标签