对于任何一个使用数据库的人来说,Upsert都是非常有用的,但是 "upsert "这个词甚至可能没有出现在你的DBMS的文档中!那么什么是upsert?
那么,什么是upsert,为什么在你的文档中没有提到它?
什么是SQL中的upsert?
术语upsert是一个混合词--是 "更新 "和 "插入 "的组合。在关系型数据库中,upsert是一个数据库操作,如果一个指定的值在表中已经存在,它将更新现有的行,如果指定的值不存在,则插入一个新的行。
例如,设想我们有一个数据库,有一个表employees ,一个id 列作为主键。
当改变这个表中的雇员信息时,我们可以使用一个upsert。从逻辑上看,这将是这样的。
- 如果雇员的ID在表中存在,就用新的信息更新该行。
- 如果该雇员的ID不存在于表中,则将其作为新行添加。
不同的RDBMS处理上载的语法是不同的--我们稍后会讨论这个问题--但是使用CockroachDB的UPSERT语法,下面是几个SQL语句的例子,以及每个语句运行后的employees 。
例子#1
UPSERT INTO employees (id, name, email) VALUES (2, ‘Dennis’, ‘dennisp@weyland.corp’);
结果。
在这个例子中,2 的主键值已经存在于表中,所以UPSERT 操作用name 和email 的新值更新了该行。
例子 #2
UPSERT INTO employees (id, name, email) VALUES (3, ‘Ash’, ‘ash@hyperdyne.corp’);
结果。
在这个例子中,3 的主键值在表中已经不存在了,所以UPSERT 操作在表中插入了一条带有相关值的新行。
然而,这只是一个简单的例子。事实上,在许多RDBMS中,UPSERT 甚至不作为一个命令存在!这就是为什么如果你搜索你选择的数据库的文档,你可能找不到 "upsert "的条目。
然而,我们可以在大多数流行的数据库中执行upsert,所以让我们看看如何在MySQL和PostgreSQL中执行upsert,然后再回到CockroachDB讨论一些细节。
我们将继续使用我们的样本employees 表来演示这些工作。
在MySQL中Upsert
UPSERT 命令在MySQL中并不存在,但是upsert仍然可以实现。在当前版本的MySQL中,实现upsert的最佳方法是 INSERT … ON DUPLICATE KEY UPDATE.让我们更详细地看一下这个命令。
正如命令本身所暗示的,INSERT … ON DUPLICATE KEY UPDATE 将向表插入一条新的行,除非它检测到主键列中有重复的值,在这种情况下,它将用新的信息更新现有的行。
因此,如果我们在例子employees 表上运行下面的命令...
INSERT INTO employees (id, name, email) VALUES (2, ‘Dennis’, ‘dennisp@weyland.corp’) ON DUPLICATE KEY UPDATE;
...我们会得到与上面例子1中相同的结果。MySQL检测到值2 已经存在于主键列id 中,所以它用新的信息更新该行。
同样地,如果我们运行同样的命令,输入值(4, ‘Dallas’, ‘dallas@weyland.corp’) ,它将在employees 中插入一个带有这些值的新行,因为值4 在示例表中不存在。
PostgreSQL中的Upsert
PostgreSQL也没有专门的UPSERT 命令,但是可以使用INSERT ON CONFLICT 来完成upsert。这个命令比INSERT … ON DUPLICATE KEY ,但它也允许我们有更多的控制。
让我们先看一下Postgres中INSERT ON CONFLICT 语句的基本结构。
INSERT INTO table (col1, col2, col3)
VALUES (val1, val2, val3)
ON CONFLICT conflict_target conflict_action;
正如我们在上面的命令中看到的,PostgreSQL允许我们指定两件事。
- conflict_target,也就是说,它应该在哪里检测冲突。
- conflict_action,也就是说,如果检测到冲突,应该如何处理这个命令。
这使我们能够在如何应用我们的上调时更有针对性。
在当前版本的PostgreSQL中 INSERT中,我们可以通过指定冲突目标(在本例中是id ,主键列)和在检测到冲突时我们要做的事情(在本例中是更新现有行)来实现基本的upsert。
INSERT INTO employees (id, name, email)
VALUES (2, ‘Dennis’, ‘dennisp@weyland.corp’)
ON CONFLICT (id) DO UPDATE;
运行这个命令会产生与本文开头的例子1中相同的结果。id PostgreSQL检测到了一个冲突--我们试图插入一条值为2 的记录,但是id 的记录已经存在于employees 中--所以它使用新的值对该记录运行UPDATE 。
如果我们用不产生冲突的值来运行这个命令(例如:(5, ‘Kane’, ‘kane@weyland.corp’) ,它将用这些值在employees 中插入一条新行。
CockroachDB中的UPSERT
CockroachDB确实有一个UPSERT ,和PostgreSQL一样,UPSERT也可以通过INSERT ON CONFLICT 实现。
虽然这两个命令可以实现类似的结果,但是它们并不完全相同。让我们来看看它们有什么不同,以及什么时候我们可能想使用它们。
UPSERT VS.INSERT ON CONFLICT
CockroachDB中的UPSERT 命令根据主键列的唯一性来执行upsert,它将根据被添加的值是否唯一来执行UPDATE 或INSERT 。
这使得使用UPSERT 比INSERT ON CONFLICT 更加直接,因为我们不需要指定冲突目标或操作。例如,对我们的例子employees 表运行下面的语句...
UPSERT INTO employees (id, name, email) VALUES (6, ‘Lambert’, ‘lambert@weyland.corp`);
...将产生下面的表格。
因为6 的值在employees 中并不存在,CockroachDB将这些值作为新的行插入到表中。
同样的,如果我们运行下面的语句...
UPSERT INTO employees (id, name, email) VALUES (1, ‘Ripley’, ‘ripley@weyland.corp`);
...我们会得到下面的表格。
因为1 已经存在于id ,主键列,CockroachDB用新的信息更新该行。
然而,在CockroachDB中,我们也可以灵活地使用INSERT ON CONFLICT ,这在某些情况下可能是有用的。例如,我们可以使用INSERT ON CONFLICT ,在我们想避免与主键无关的冲突的情况下,处理上载。例如,我们可以指定一个外键列作为冲突目标。
UPSERT 和INSERT ON CONFLICT 之间有时也会有性能上的差异,不过这取决于你工作负载的具体情况。更多信息请参考CockroachDBUPSERT 文档。