PostgreSQL 如何实现upsert与新旧数据自动分离

很多业务也行有这样的需求,新的数据会不断的插入,并且可能会有更新。
对于更新的数据,需要记录更新前的记录到历史表。

这个需求有点类似于审计需求,即需要对记录变更前后做审计。
我以前有写过使用hstore和触发器来满足审计需求的文档,有兴趣的同学可以参考
http://blog.163.com/digoal@126/blog/static/163877040201252575529358/
本文的目的并不是审计,而且也可能不期望使用触发器。
还有什么方法呢?
PostgreSQL 这么高大上,当然有,而且还能在一句SQL里面完成,看法宝。

创建一张当前状态表,一张历史记录表。

postgres=# create table tbl(id int primary key, price int);
CREATE TABLE
postgres=# create table tbl_history (id int not null, price int);
CREATE TABLE

插入一条不存在的记录,不会触发插入历史表的行为。
注意替代变量

id=$1 = 2
price=$2 = 7

postgres=# with old as (select * from tbl where id= $1),
postgres-# new as (insert into tbl values ($1, $2) on conflict (id) do update set price=excluded.price where tbl.price<>excluded.price returning *)
postgres-# insert into tbl_history select old.* from old,new where old.id=new.id;
INSERT 0 0

postgres=# select tableoid,ctid,* from tbl union all select tableoid,ctid,* from tbl_history ;
 tableoid | ctid  | id | price
----------+-------+----+-------
    18243 | (0,1) |  2 |     7
(1 row)

插入一条不存在的记录,不会触发插入历史表的行为。

id=$1 = 1
price=$2 = 1

postgres=# with old as (select * from tbl where id= $1),
new as (insert into tbl values ($1, $2) on conflict (id) do update set price=excluded.price where tbl.price<>excluded.price returning *)
insert into tbl_history select old.* from old,new where old.id=new.id;
INSERT 0 0
postgres=# select tableoid,ctid,* from tbl union all select tableoid,ctid,* from tbl_history ;
 tableoid | ctid  | id | price
----------+-------+----+-------
    18243 | (0,1) |  2 |     7
    18243 | (0,2) |  1 |     1
(2 rows)

插入一条已存在的记录,并且有数据的变更,触发数据插入历史表的行为。

id=$1 = 1
price=$2 = 2

postgres=# with old as (select * from tbl where id= $1),
new as (insert into tbl values ($1, $2) on conflict (id) do update set price=excluded.price where tbl.price<>excluded.price returning *)
insert into tbl_history select old.* from old,new where old.id=new.id;
INSERT 0 1
postgres=# select tableoid,ctid,* from tbl union all select tableoid,ctid,* from tbl_history ;
 tableoid | ctid  | id | price
----------+-------+----+-------
    18243 | (0,1) |  2 |     7
    18243 | (0,3) |  1 |     2
    18251 | (0,1) |  1 |     1
(3 rows)

插入一条已存在的记录,并且已存在的记录值和老值一样,不会触发将数据插入历史表的行为。

id=$1 = 1
price=$2 = 2

postgres=# with old as (select * from tbl where id= $1),
new as (insert into tbl values ($1, $2) on conflict (id) do update set price=excluded.price where tbl.price<>excluded.price returning *)
insert into tbl_history select old.* from old,new where old.id=new.id;
INSERT 0 0
postgres=# select tableoid,ctid,* from tbl union all select tableoid,ctid,* from tbl_history ;
 tableoid | ctid  | id | price
----------+-------+----+-------
    18243 | (0,1) |  2 |     7
    18243 | (0,3) |  1 |     2
    18251 | (0,1) |  1 |     1
(3 rows)

执行计划

postgres=# explain with old as (select * from tbl where id= $1),
new as (insert into tbl values ($1, $2) on conflict (id) do update set price=excluded.price where tbl.price<>excluded.price returning *)
insert into tbl_history select old.* from old,new where old.id=new.id;
                                 QUERY PLAN
----------------------------------------------------------------------------
 Insert on tbl_history  (cost=2.17..2.23 rows=1 width=8)
   CTE old
     ->  Index Scan using tbl_pkey on tbl  (cost=0.14..2.16 rows=1 width=8)
           Index Cond: (id = 1)
   CTE new
     ->  Insert on tbl tbl_1  (cost=0.00..0.01 rows=1 width=8)
           Conflict Resolution: UPDATE
           Conflict Arbiter Indexes: tbl_pkey
           Conflict Filter: (tbl_1.price <> excluded.price)
           ->  Result  (cost=0.00..0.01 rows=1 width=8)
   ->  Nested Loop  (cost=0.00..0.05 rows=1 width=8)
         Join Filter: (old.id = new.id)
         ->  CTE Scan on old  (cost=0.00..0.02 rows=1 width=8)
         ->  CTE Scan on new  (cost=0.00..0.02 rows=1 width=4)
(14 rows)

在不支持insert on conflict语法的PostgreSQL中(小于9.5的版本),SQL可以调整为:

id=$1 = 1
price=$2 = 2

with new as (update tbl set price=$2 where id=$1 and price<>$2)
  insert into tbl select $1, $2 where not exists (select 1 from tbl where id=$1);

更多upset参考

小于9.5的版本,实现本文的场景,需要这样写。

id=$1 = 1
price=$2 = 2

with
old as (select * from tbl where id=$1),
new_upd as (update tbl set price=$2 where id=$1 and price<>$2 returning *),
new_ins as (insert into tbl select $1, $2 where not exists (select 1 from tbl where id=$1) returning *)
insert into tbl_history
select old.* from old left outer join new_upd on (old.id=new_upd.id) where new_upd.* is not null;
时间: 2024-09-13 20:29:27

PostgreSQL 如何实现upsert与新旧数据自动分离的相关文章

判断字段是否被更新 新旧数据写入Audit Log表中_MsSql

Insus.NET解决这个问题,只有创建另外一个表,将存储用户决定要跟踪的表,以及这个表中需要跟踪的字段. 还要创建另外一个表[Audit],就是存储跟踪记录的表: 复制代码 代码如下: Audit SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[Audit]( [Audit_nbr] [int] IDENTITY(1,1) NOT NULL, [AuditTy

判断字段是否被更新 新旧数据写入Audit Log表中

Insus.NET解决这个问题,只有创建另外一个表,将存储用户决定要跟踪的表,以及这个表中需要跟踪的字段. 还要创建另外一个表[Audit],就是存储跟踪记录的表: 复制代码 代码如下: Audit SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[Audit]( [Audit_nbr] [int] IDENTITY(1,1) NOT NULL, [AuditTy

MS SQL新旧库数据字典比较脚本

比较|脚本|数据      /*MS SQL新旧库数据字典比较脚本*/ --注明1:新旧库必须在同一数据库服务器同一实例中,最好以Sa身份登入.--注明2:本脚本可作为系统升级改造,得到相关信息后作数据迁移之用.declare @i int set @i=4  /*注明3:1为要得到新库增加的数据字典信息;                 2为要得到旧库多出的数据字典信息;                 3为要得到新库增加的表的数据字典信息;                 4为要得到旧库多出

怎么看移动硬盘新旧

  怎么看移动硬盘新旧 首先看计算看看你的硬盘在电脑上显示的是多少字节,在用它乘与1024,看看有没有30G;二是看硬盘的标签和序列号,有没有打磨过的痕迹;三是拷贝一个大文件试试,听一听硬盘在读取数据时有没有噪音.好的硬盘没有噪音,而老的硬盘会吱吱作向. 其次我们可以用移动硬盘检测工具鉴别硬盘是否全新,推荐使用HDTunePro,测一下就知道了,里面能显示硬盘运行过多少时间,电机转速什么的正不正常.

RDS PostgreSQL\HDB PG 毫秒级海量时空数据透视 典型案例分享

标签 PostgreSQL , GIS , 时空数据 , 数据透视 , bitmapAnd , bitmapOr , multi-index , 分区 , brin , geohash cluster 背景 随着移动终端的普及,现在有越来越多的业务数据会包含空间数据,例如手机用户的FEED信息.物联网.车联网.气象传感器的数据.动物的溯源数据,一系列跟踪数据. 这些数据具备这几个维度的属性: 1.空间 2.时间 3.业务属性,例如温度.湿度.消费额.油耗.等. 数据透视是企业BI.分析师.运营非

c#-C#静态变量重新赋值 旧数据还是会占用内存么

问题描述 C#静态变量重新赋值 旧数据还是会占用内存么 如题 如果我的程序中有30个静态变量 而且频繁的对其进行赋值(几乎3秒一次) 那么之前的值(数据) 还是会占用内存么 这样挂起 会导致cpu飙高么? 解决方案 旧数据不会占用内存. 解决方案二: 看什么类型,如果是引用类型,会消耗一些内存.值类型直接覆盖. 引用类型,如果之前的对象没有其它引用,会被gc回收,但是如果你的内存比较大,未必会马上回收. 解决方案三: 如果你不断生成新的datatable,datatable又很大,不断赋值,那么

公司新旧网站更替

问题描述 公司新旧网站更替,具体如何操作呢.我是菜鸟啊--呜呜 解决方案 解决方案二:把老网站下架啊,发布新网站.域名还用以前的域名.就OK啊!解决方案三:什么意思不明白,你的意思,公司向停掉旧的网站,启用新的网站吗解决方案四:下架上架解决方案五:可能是需要你会套用吧解决方案六:嗯,停掉旧网站,启用新网站.解决方案七:不外乎就是新旧网页的不同然后拷贝资料到新的网站下解决方案八:引用3楼的回复: 下架上架 [同意]解决方案九:把旧的先留着把数据导入到新的数据库然后把新的程序启动旧的下了数据导入导出

打车软件新旧利益博弈激烈,诸多难题考验监管智慧

"手机软件叫出租车"--这样的体验在两年前还是媒体争相报道的鲜活素材,而今早已成为大中城市普通市民的日常行为. 市民们先是迎来了滴滴.快的等打车软件的迅猛兴起,后又迎来了专车服务,打车更方便了,出行选择也更多了.移动出行,以其快捷便利的优势,改变着国人的出行习惯.这一如火箭上升的新兴业态,也正挑战着城市出行的传统利益格局和交通管理方式. 打车软件.专车服务,出行新业态赢得大量消费者 高峰时段打不着车.因不顺路司机拒载.机场车站排起长龙--长期以来,打车难的问题困扰着不少城市居民,也孕育

戴锐凯:构筑新旧媒体营销平台

戴锐凯认为,在数字世界,消费者对媒体有更多的控制权,因此,企业必须了解消费者,以便在适合的地方以恰当的方式传递合适的信息. ■文/本刊记者 李颖 发自上海 随着金融海啸向实体经济蔓延,消费者的购买力下降,企业主开始谨慎地使用每一笔广告预算,这一系列连锁反应使得广告行业的前景变得扑朔迷离.世界第四大广告与传媒集团阳狮集团在2008年成立了VivaKi(阳狮锐奇),这一战略性实体整合了阳狮集团旗下的4个品牌-两家全球媒体策划与购买机构星传媒体(Starcom MediaVest Group)和实力传