PostgreSQL INSERT ON CONFLICT不存在則插入,存在則更新


近期有一個需求,向一張數據庫表插入數據,如果是新數據則執行插入動作,如果插入的字段和已有字段重復,則更新該行對應的部分字段

1. 創建測試表

create table meta_data (
        id serial,
        user_id varchar(128) DEFAULT NULL,
        file_name varchar(1024) DEFAULT NULL,
        file_path varchar(1024) DEFAULT NULL,
        update_time TIMESTAMP DEFAULT NULL,
        UNIQUE (user_id,file_name)
    );


postgres=# \d meta_data
                                      Table "public.meta_data"
   Column    |            Type             |                       Modifiers                        
-------------+-----------------------------+--------------------------------------------------------
 id          | integer                     | not null default nextval('meta_data_id_seq'::regclass)
 user_id     | character varying(128)      | default NULL::character varying
 file_name   | character varying(1024)     | default NULL::character varying
 file_path   | character varying(1024)     | default NULL::character varying
 update_time | timestamp without time zone | 
Indexes:
    "meta_data_user_id_file_name_key" UNIQUE CONSTRAINT, btree (user_id, file_name)

2. 插入兩條測試數據

INSERT INTO meta_data (
               user_id,
               file_name,
               file_path,
               UPDATE_TIME )
     VALUES ( 'user_id01',
              'file_name01',
              '/usr/local/file_name01',
              now())
ON CONFLICT (user_id, file_name) DO UPDATE SET file_path = EXCLUDED.file_path, UPDATE_TIME = EXCLUDED.UPDATE_TIME;    

INSERT INTO meta_data (
               user_id,
               file_name,
               file_path,
               UPDATE_TIME )
     VALUES ( 'user_id02',
              'file_name02',
              '/usr/local/file_name02',
              now())
ON CONFLICT (user_id, file_name) DO UPDATE SET file_path = EXCLUDED.file_path, UPDATE_TIME = EXCLUDED.UPDATE_TIME;
postgres=# select * from meta_data;
 id |  user_id  |  file_name  |       file_path        |        update_time         
----+-----------+-------------+------------------------+----------------------------
  1 | user_id01 | file_name01 | /usr/local/file_name01 | 2019-09-23 17:14:52.39878
  2 | user_id02 | file_name02 | /usr/local/file_name02 | 2019-09-23 17:14:53.118192
(2 rows)

 3. 插入第三條測試數據,注意插入的字段user_id和file_name和第二條語句對應的字段是重復的

INSERT INTO meta_data (
               user_id,
               file_name,
               file_path,
               UPDATE_TIME )
     VALUES ( 'user_id02',
              'file_name02',
              '/usr/local/file_name03',
              now())
ON CONFLICT (user_id, file_name) DO UPDATE SET file_path = EXCLUDED.file_path, UPDATE_TIME = EXCLUDED.UPDATE_TIME;
postgres=# select * from meta_data;
 id |  user_id  |  file_name  |       file_path        |        update_time         
----+-----------+-------------+------------------------+----------------------------
  1 | user_id01 | file_name01 | /usr/local/file_name01 | 2019-09-23 17:14:52.39878
  2 | user_id02 | file_name02 | /usr/local/file_name03 | 2019-09-23 17:16:52.457696
(2 rows)

 可以看到新插入的第三條語句其實是更新了已存在的第二條記錄

4.如何區分該條語句到底是執行了insert和update操作。

通過xmax字段的值是否為0,可以判斷,如果是UPDATE,XMAX里面會填充更新事務號。注意直接用UPDATE語句更新的話,XMAX會寫入0,因為是新版本,而老版本上XMAX會填入更新事務號。

我們重建表結構重新插入前面兩條數據測試。

postgres=# select ctid,xmin,xmax,* from meta_data;
 ctid  | xmin | xmax | id |  user_id  |  file_name  |       file_path        |        update_time         
-------+------+------+----+-----------+-------------+------------------------+----------------------------
 (0,1) | 3241 |    0 |  1 | user_id01 | file_name01 | /usr/local/file_name01 | 2019-09-23 17:31:27.360539
 (0,2) | 3242 |    0 |  2 | user_id02 | file_name02 | /usr/local/file_name02 | 2019-09-23 17:31:28.10752
(2 rows)

 

 

再次插入第三條重復數據

INSERT INTO meta_data (
               user_id,
               file_name,
               file_path,
               UPDATE_TIME )
     VALUES ( 'user_id02',
              'file_name02',
              '/usr/local/file_name03',
              now())
ON CONFLICT (user_id, file_name) DO UPDATE SET file_path = EXCLUDED.file_path, UPDATE_TIME = EXCLUDED.UPDATE_TIME;

postgres=# select ctid,xmin,xmax,* from meta_data;
 ctid  | xmin | xmax | id |  user_id  |  file_name  |       file_path        |        update_time         
-------+------+------+----+-----------+-------------+------------------------+----------------------------
 (0,1) | 3241 |    0 |  1 | user_id01 | file_name01 | /usr/local/file_name01 | 2019-09-23 17:31:27.360539
 (0,3) | 3243 | 3243 |  2 | user_id02 | file_name02 | /usr/local/file_name03 | 2019-09-23 17:33:53.459403
(2 rows)

 

ctid表示行號, xmin表示INSERT該記錄的事務號,xmax表示刪除該記錄(update實際上是刪除老版本新增新版本,所以老版本上xmax有值)的事務號。

手動執行update

postgres=# update meta_data set file_path='/usr/local/file_name02' where user_id='user_id02';
UPDATE 1
postgres=# select ctid,xmin,xmax,* from meta_data;
 ctid  | xmin | xmax | id |  user_id  |  file_name  |       file_path        |        update_time         
-------+------+------+----+-----------+-------------+------------------------+----------------------------
 (0,1) | 3241 |    0 |  1 | user_id01 | file_name01 | /usr/local/file_name01 | 2019-09-23 17:31:27.360539
 (0,4) | 3244 |    0 |  2 | user_id02 | file_name02 | /usr/local/file_name02 | 2019-09-23 17:33:53.459403
(2 rows)

 

結論

1、insert into on conflict do update,返回xmax等於0表示insert,不等於0表示update,

2、直接update,並提交,提交的記錄上xmax為0。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM