如何在 PostgreSQL 中编写 upsert 触发器?
How to write an upsert trigger in PostgreSQL?
在 PostgreSQL 9.6 及更高版本中,定义触发器函数的正确方法是什么?
每当插入因唯一性约束而失败时更新?
我知道写 insert ... on conflict ... do update set ...
语句很简单,但是我的
我的想法是,我想要一些 table 将重复插入视为更新;否则那件
逻辑必须由应用程序而不是数据库来处理。
我找到的一个表面上有效的解决方案是:
create table versions (
key text primary key,
version text );
/* ### TAINT not sure whether there may be race conditions with this upsert trigger */
create function on_before_insert_versions() returns trigger language plpgsql volatile as $$ begin
if exists ( select 1 from versions where key = new.key ) then
update versions set version = new.version where key = new.key;
return null;
end if;
return new;
end; $$;
create trigger on_before_insert_versions
before insert on versions for each row execute procedure on_before_insert_versions();
insert into versions values
( 'server', '3.0.3' ),
( 'api', '2' );
insert into versions values
( 'api', '3' );
select * from versions;
key | version
--------+---------
server | 3.0.3
api | 3
但是,触发器是否容易出现竞争条件?我试着用一个
insert ... on conflict ... do update set ...
触发器中的语句,但当然失败了
因为它自己触发触发函数,导致无限倒退。
我还尝试使用一对 alter table ... disable trigger ...
/ enable
语句,但是
cannot ALTER TABLE ... because it is being used by active queries in this session
.
错误
在唯一性约束下始终执行更新而不是插入的规范形式是什么
Postgre 中的违规行为SQL?
Update—PostgreSQL 中的更新,或者他们长期缺席,是一个热门话题,许多不那么完美的解决方案经常出现建议。
鉴于 Postgres 的维护者已经花费了如此多的时间和精力来使 insert ... on conflict .. do update
在没有竞争条件的情况下工作,接受 'seems to work' 的自制解决方案可能是不明智的(直到它不).
当我写下我的问题时,我坚持要有一个 insert
触发器在冲突时执行 update
; PostgreSQL 对此没有很好的支持,主要问题是你在 before insert
触发器中对同一个 table 执行的 insert
将导致同一个触发器叫。 @Laurenz Albe 建议如何摆脱无限循环,虽然建议的技术(巧妙!)看起来是一件值得记住的好事,但我们不知道对性能或其他副作用的可能影响。
最后,@Ilya Dyoshin 提议只从应用程序中调用一个包含必要 SQL 逻辑的函数,一针见血。我觉得这是一个 win/win 解决方案,因为
1) 它不会将 insert into x
for table x
的语义更改为 'really mean update
, sometimes';
2) 'upsert semantics' 在应用程序代码中明确说明,但没有详细说明;
3) 您可以 仍然 执行 insert
而无需隐式 'update' — 事后看来,这可能是最重要的考虑因素。
最好使用纯upsert
否则你可以引入更复杂的逻辑,并且不要 return 从触发器插入的数据(阅读文档 = 如果插入前的触发器不是 returning 值,则不执行插入)
我同意 Ilya 的观点,即在应用程序中以直接的方式执行此操作会更好。
但我本着思想实验的精神来看待它,我的解决方案利用 pg_trigger_depth()
的力量来逃避无休止的递归:
CREATE OR REPLACE FUNCTION on_before_insert_versions() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
IF pg_trigger_depth() = 1 THEN
INSERT INTO versions (key, version) VALUES (NEW.key, NEW.version)
ON CONFLICT (key)
DO UPDATE SET version = NEW.version;
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;$$;
您的解决方案绝对容易受到竞争条件的影响:两个并发的 INSERT 可能导致并发 运行 触发器,两者都无法在 versions
中找到匹配的行,因此导致 INSERT,其中之一必须失败。
在 PostgreSQL 9.6 及更高版本中,定义触发器函数的正确方法是什么? 每当插入因唯一性约束而失败时更新?
我知道写 insert ... on conflict ... do update set ...
语句很简单,但是我的
我的想法是,我想要一些 table 将重复插入视为更新;否则那件
逻辑必须由应用程序而不是数据库来处理。
我找到的一个表面上有效的解决方案是:
create table versions (
key text primary key,
version text );
/* ### TAINT not sure whether there may be race conditions with this upsert trigger */
create function on_before_insert_versions() returns trigger language plpgsql volatile as $$ begin
if exists ( select 1 from versions where key = new.key ) then
update versions set version = new.version where key = new.key;
return null;
end if;
return new;
end; $$;
create trigger on_before_insert_versions
before insert on versions for each row execute procedure on_before_insert_versions();
insert into versions values
( 'server', '3.0.3' ),
( 'api', '2' );
insert into versions values
( 'api', '3' );
select * from versions;
key | version
--------+---------
server | 3.0.3
api | 3
但是,触发器是否容易出现竞争条件?我试着用一个
insert ... on conflict ... do update set ...
触发器中的语句,但当然失败了
因为它自己触发触发函数,导致无限倒退。
我还尝试使用一对 alter table ... disable trigger ...
/ enable
语句,但是
cannot ALTER TABLE ... because it is being used by active queries in this session
.
在唯一性约束下始终执行更新而不是插入的规范形式是什么 Postgre 中的违规行为SQL?
Update—PostgreSQL 中的更新,或者他们长期缺席,是一个热门话题,许多不那么完美的解决方案经常出现建议。
鉴于 Postgres 的维护者已经花费了如此多的时间和精力来使 insert ... on conflict .. do update
在没有竞争条件的情况下工作,接受 'seems to work' 的自制解决方案可能是不明智的(直到它不).
当我写下我的问题时,我坚持要有一个 insert
触发器在冲突时执行 update
; PostgreSQL 对此没有很好的支持,主要问题是你在 before insert
触发器中对同一个 table 执行的 insert
将导致同一个触发器叫。 @Laurenz Albe 建议如何摆脱无限循环,虽然建议的技术(巧妙!)看起来是一件值得记住的好事,但我们不知道对性能或其他副作用的可能影响。
最后,@Ilya Dyoshin 提议只从应用程序中调用一个包含必要 SQL 逻辑的函数,一针见血。我觉得这是一个 win/win 解决方案,因为
1) 它不会将 insert into x
for table x
的语义更改为 'really mean update
, sometimes';
2) 'upsert semantics' 在应用程序代码中明确说明,但没有详细说明;
3) 您可以 仍然 执行 insert
而无需隐式 'update' — 事后看来,这可能是最重要的考虑因素。
最好使用纯upsert
否则你可以引入更复杂的逻辑,并且不要 return 从触发器插入的数据(阅读文档 = 如果插入前的触发器不是 returning 值,则不执行插入)
我同意 Ilya 的观点,即在应用程序中以直接的方式执行此操作会更好。
但我本着思想实验的精神来看待它,我的解决方案利用 pg_trigger_depth()
的力量来逃避无休止的递归:
CREATE OR REPLACE FUNCTION on_before_insert_versions() RETURNS trigger
LANGUAGE plpgsql AS
$$BEGIN
IF pg_trigger_depth() = 1 THEN
INSERT INTO versions (key, version) VALUES (NEW.key, NEW.version)
ON CONFLICT (key)
DO UPDATE SET version = NEW.version;
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;$$;
您的解决方案绝对容易受到竞争条件的影响:两个并发的 INSERT 可能导致并发 运行 触发器,两者都无法在 versions
中找到匹配的行,因此导致 INSERT,其中之一必须失败。