包含 insert into 子句的合并语句在 PostgreSQL 中失败

Coalesce sentence containing an insert into clause fails in PostgreSQL

这是我的琐事testtable,

create table test (
  id          int not null generated always as identity,
  first_name. varchar,

  primary key (id),
  unique(first_name)
);

作为 insert-into-on-conflict 句子的替代方案,我试图使用 coalesce 惰性来尽可能执行 selectinsert,仅当 select 找不到一行。

coalesce 文档中描述了惰性。参见 https://www.postgresql.org/docs/current/functions-conditional.html

Like a CASE expression, COALESCE only evaluates the arguments that are needed to determine the result; that is, arguments to the right of the first non-null argument are not evaluated. This SQL-standard function provides capabilities similar to NVL and IFNULL, which are used in some other database systems.

我还想取回该行的 id 值,是否已插入。

我开始于:

select coalesce (
  (select id from test where first_name='carlos'),
  (insert into test(first_name) values('carlos') returning id)
);

但发现错误 syntax error at or near "into"。 在另一个 DBFiddle 上看到它 https://www.db-fiddle.com/f/t7TVkoLTtWU17iaTAbEhDe/0

然后我尝试了:

select coalesce (
  (select id from test where first_name='carlos'),
  (with r as (
    insert into test(first_name) values('carlos') returning id
   ) select id from r
  )
);

这里我遇到了一个我不理解的 WITH clause containing a data-modifying statement must be at the top level 错误,因为 insertwith.

中的第一个也是唯一一个句子

我正在使用 DBFiddle 和 PostgreSQL 13 对此进行测试。可以在以下位置找到源代码 https://www.db-fiddle.com/f/hp8T1iQ8eS4wozDCBhBXDw/5

不同方法:链接 CTE:


CREATE TABLE test
        ( id          INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY
        , first_name VARCHAR UNIQUE
        );

WITH sel AS (
        SELECT id FROM test WHERE first_name = 'carlos'
        )
, ins AS (
        INSERT INTO test(first_name) 
        SELECT 'carlos'
        WHERE NOT EXISTS (SELECT 1 FROM test WHERE first_name = 'carlos')
        RETURNING id
        )
, omg AS (
        SELECT id FROM sel
        UNION ALL 
        SELECT id FROM ins
        )
SELECT id
FROM omg
        ;

这是对@wildplasser 接受的答案的改进。它避免比较 first_name 两次并使用 coalesce 而不是 union all。有点像 selsert 一句话。

with sel as (
  select id from test where first_name = 'carlos'
)
, ins as (
  insert into test(first_name) 
  select 'carlos'
  where (select id from sel) is null
  returning id
)
select coalesce (
  (select id from sel),
  (select id from ins)
);

https://www.db-fiddle.com/f/goRh4TyAebTkEZFHk6WbtK/6

看来 insert into 子句的返回值在本质上不等同于 select 子句的标量查询。所以我尝试将 insert into 封装到一个 SQL 函数中并且它起作用了。

create or replace function insert_first_name(
  _first_name varchar
) returns int
language sql as $$
  insert into test (first_name) 
  values (_first_name) 
  returning id;
$$;

select coalesce (
  (select id from test where first_name='carlos'),
  (select insert_first_name('carlos'))
);

https://www.db-fiddle.com/f/73rVXgqGfrG4VmjrAk6Z3i/2

上查看