如何对同时具有主键和 UNIQUE 列的表执行 upsert

Posted

技术标签:

【中文标题】如何对同时具有主键和 UNIQUE 列的表执行 upsert【英文标题】:How to perform an upsert on a table with both a primary key and UNIQUE column 【发布时间】:2020-02-13 07:56:27 【问题描述】:

SQLite 新手并试图了解 upsert 功能。

我有一个包含以下 DDL 的表:

CREATE TABLE contacts (
    contact_id INTEGER PRIMARY KEY,
    first_name TEXT NOT NULL,
    last_name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    phone TEXT NOT NULL UNIQUE
);

假设我插入一条记录:

INSERT INTO contacts (contact_id, first_name, last_name, email, phone)
VALUES (1, 'John', 'Jones', 'jjones@gmail.com', '888-867-5309');

我如何做一个考虑到 UNIQUE 约束 (email) 和 PK 约束 (contact_id) 的 upsert,以便它处理这两种情况,因为我不知道哪个约束会失败。

我试过这样做:

INSERT INTO contacts (contact_id, first_name, last_name, email, phone)
VALUES (1, 'John', 'Jones', 'john.jones@gmail.com', '888-867-5309')
ON CONFLICT (contact_id, email) DO UPDATE
SET first_name='John', last_name='Jones', email='john.jones@gmail.com', phone='888-867-5309'
WHERE contact_id=1;

但我得到了错误:

sqlite3.OperationalError: ON CONFLICT 子句不匹配任何 PRIMARY KEY 或 UNIQUE 约束

单独做就可以了。

INSERT INTO contacts (contact_id, first_name, last_name, email, phone)
VALUES (1, 'John', 'Jones', 'john.jones@gmail.com', '888-867-5309')
ON CONFLICT (contact_id) DO UPDATE
SET first_name='John', last_name='Jones', email='john.jones@gmail.com', phone='888-867-5309'
WHERE contact_id=1;
INSERT INTO contacts (contact_id, first_name, last_name, email, phone)
VALUES (1, 'John', 'Jones', 'john.jones@gmail.com', '888-867-5309')
ON CONFLICT (email) DO UPDATE
SET first_name='John', last_name='Jones', email='john.jones@gmail.com', phone='888-867-5309'
WHERE email='john.jones@gmail.com';

我知道我收到错误是因为列的组合不满足单个约束,它包含两个。但是我该如何兼顾两者呢?

【问题讨论】:

也许INSERT OR REPLACE 会做你想做的事。 【参考方案1】:

既然你定义了:

contact_id INTEGER PRIMARY KEY

contact_idAUTOINCREMENT 并且您必须在插入新行时显式设置此列的值(尽管如果没有冲突,SQLite 不会抱怨)。 所以你只需要:

INSERT INTO contacts (first_name, last_name, email, phone)
VALUES ('John', 'Jones', 'john.jones@gmail.com', '888-867-5309')
ON CONFLICT (email) DO UPDATE
SET first_name='John', last_name='Jones', email='john.jones@gmail.com', phone='888-867-5309'; 

但是,你也定义了:

phone TEXT NOT NULL UNIQUE

所以您的表中有 2 个UNIQUE 约束。 对于这种情况,如果您希望 SQLite 处理来自两列的冲突,您可以使用 (INSERT OR) REPLACE:

REPLACE INTO contacts (first_name, last_name, email, phone)
VALUES('Johny', 'Jones', 'john.jones@gmail.com', '888-867-5309')

您必须知道,如果没有冲突,REPLACE 会插入新行(在您的情况下是 emailphone 列),但如果存在冲突,则删除冲突的行(因为将有 2 个冲突行,一个用于 email,另一个用于 phone)并插入新行。

【讨论】:

以上是关于如何对同时具有主键和 UNIQUE 列的表执行 upsert的主要内容,如果未能解决你的问题,请参考以下文章

没有主键和空列的表上的实体框架反向 poco

如何匹配主键和外键的值?

为啥 Oracle 不允许您使用相同的列同时创建主键和唯一约束?

创建具有主键和2个外键的表

如何使用主键和分区列创建雪花表?示例 DDL?

navicat内的主键和外键