本篇介绍 SQL:2016(ISO/IEC 9075:2016)标准中定义的数据操作语言(DML),包括 INSERT、UPDATE、DELETE 以及 MERGE,以及六种主流数据库中的实现及差异:Oracle、MySQL、Microsoft SQL Server、PostgreSQL、Db2、SQLite。
插入数据
SQL 标准中提供了 3 种数据插入的方法:
INSERT INTO … VALUES;
INSERT INTO … SELECT;
INSERT INTO … DEFAULT;
首先,第一种形式的语法如下:
INSERT INTO t(column1, column2, ...)
VALUES (value1, value2, ...);
其中,VALUES 子句中的值与列出的字段一一对应,即数量相同并且类型兼容。
举个例子:
INSERT INTO employees(employee_id, first_name, last_name, email,
phone_number, hire_date, job_id, salary,
commission_pct, manager_id, department_id)
VALUES ( 207, 'Tony', 'Dong', 'TonyDong',
'515.123.8000', '2010-01-15', 'IT_PROG', 8800,
NULL, 102, 60);
对于 Oracle,使用 DATE ‘2010-01-15’ 定义一个日期常量
以上语句为 employees 表插入了一条新的员工信息。如果值列表与表中的字段顺序完全一致,可以省略字段列表。以上示例也可以简写成:
INSERT INTO employees
VALUES ( 207, 'Tony', 'Dong', 'TonyDong',
'515.123.8000', '2010-01-15', 'IT_PROG', 8800,
NULL, 102, 60);
数据库在插入数据之前会执行各种完整性检查,对于违反约束的数据,将会提示错误,而不会生成新的数据。以下 2 个语句分别违反了外键约束和非空约束:
-- 违反外键约束,job_id 不存在
INSERT INTO employees(employee_id, last_name, email, hire_date, job_id)
VALUES ( 208, 'Nobody', 'Nobody', '2018-06-06', 'NOT_EXIST');
-- 违反非空约束,last_name 不能为空
INSERT INTO employees(employee_id)
VALUES ( 209 );
MySQL 只有 InnoDB 引擎支持外键约束
SQLite 可以设置是否启用外键约束扫描二维码关注公众号,回复: 4840853 查看本文章
除了 Oracle 之外,其他五种数据库支持通过 VALUES
子句指定多条记录值,同时插入多行数据:
-- Except for Oracle
INSERT INTO employees(employee_id, first_name, last_name, email,
phone_number, hire_date, job_id, salary,
commission_pct, manager_id, department_id)
VALUES ( 209, 'David', 'Lee', 'DavidLee',
'600.545.3428', '2018-08-08', 'SA_REP', 7000,
0.2, 149, 80),
( 210, 'Otto', 'Smith', 'OttoSmith',
'102.667.5439', '2018-08-30', 'AC_ACCOUNT', 7600,
NULL, 205, 110);
第二种插入数据的语法通过 SELECT
语句查询出一个结果集,然后将该结果集插入表中。我们先创建一个新表 emp_archive:
CREATE TABLE emp_archive
( employee_id INTEGER NOT NULL
, first_name CHARACTER VARYING(20)
, last_name CHARACTER VARYING(25) NOT NULL
, email CHARACTER VARYING(25) NOT NULL
, phone_number CHARACTER VARYING(20)
, hire_date DATE NOT NULL
, job_id CHARACTER VARYING(10) NOT NULL
, salary NUMERIC(8,2)
, commission_pct NUMERIC(2,2)
, manager_id INTEGER
, department_id INTEGER
) ;
插入一个查询结果集:
INSERT INTO emp_archive(employee_id, first_name, last_name, email,
phone_number, hire_date, job_id, salary,
commission_pct, manager_id, department_id)
SELECT employee_id, first_name, last_name, email,
phone_number, hire_date, job_id, salary,
commission_pct, manager_id, department_id
FROM employees
WHERE salary > 10000
ORDER BY hire_date;
其中的 SELECT
语句查询出所有薪水大于 10000 的员工,然后将这些数据插入到表 emp_archive 中。
SELECT *
FROM emp_archive;
employee_id |first_name |last_name |email |phone_number |hire_date |job_id |salary |commission_pct |manager_id |department_id |
------------|-----------|----------|---------|-------------------|-----------|--------|-------|---------------|-----------|--------------|
102 |Lex |De Haan |LDEHAAN |515.123.4569 |2001-01-13 |AD_VP |17000 | |100 |90 |
205 |Shelley |Higgins |SHIGGINS |515.123.8080 |2002-06-07 |AC_MGR |12008 | |101 |110 |
108 |Nancy |Greenberg |NGREENBE |515.124.4569 |2002-08-17 |FI_MGR |12008 | |101 |100 |
114 |Den |Raphaely |DRAPHEAL |515.127.4561 |2002-12-07 |PU_MAN |11000 | |100 |30 |
100 |Steven |King |SKING |515.123.4567 |2003-06-17 |AD_PRES |24000 | | |90 |
201 |Michael |Hartstein |MHARTSTE |515.123.5555 |2004-02-17 |MK_MAN |13000 | |100 |20 |
174 |Ellen |Abel |EABEL |011.44.1644.429267 |2004-05-11 |SA_REP |11000 |0.3 |149 |80 |
145 |John |Russell |JRUSSEL |011.44.1344.429268 |2004-10-01 |SA_MAN |14000 |0.4 |100 |80 |
146 |Karen |Partners |KPARTNER |011.44.1344.467268 |2005-01-05 |SA_MAN |13500 |0.3 |100 |80 |
147 |Alberto |Errazuriz |AERRAZUR |011.44.1344.429278 |2005-03-10 |SA_MAN |12000 |0.3 |100 |80 |
168 |Lisa |Ozer |LOZER |011.44.1343.929268 |2005-03-11 |SA_REP |11500 |0.25 |148 |80 |
101 |Neena |Kochhar |NKOCHHAR |515.123.4568 |2005-09-21 |AD_VP |17000 | |100 |90 |
162 |Clara |Vishney |CVISHNEY |011.44.1346.129268 |2005-11-11 |SA_REP |10500 |0.25 |147 |80 |
148 |Gerald |Cambrault |GCAMBRAU |011.44.1344.619268 |2007-10-15 |SA_MAN |11000 |0.3 |100 |80 |
149 |Eleni |Zlotkey |EZLOTKEY |011.44.1344.429018 |2008-01-29 |SA_MAN |10500 |0.2 |100 |80 |
第三种方式全部使用默认值插入数据,使用场景有限。
CREATE TABLE test_insert
( id INTEGER NOT NULL DEFAULT 0,
name VARCHAR(10)
);
-- SQL Server, PostgreSQL and SQLite
INSERT INTO test_insert
DEFAULT VALUES;
-- Except for SQLite
INSERT INTO t(column1, column2,...)
VALUES (DEFAULT, DEFAULT, ...);
DROP TABLE test_insert;
更新数据
SQL 标准提供了 UPDATE
语句,用于更新表中的数据。基本语法如下:
UPDATE t
SET column1 = expr1,
column2 = expr2,
...
[WHERE condition];
其中,t 是要更新数据的表名;SET
子句指定了要更新的列和更新后的值,多个字段使用逗号进行分隔;满足 WHERE
条件的数据行才会被更新,如果没有指定条件,将会更新表中所有的行。
以下示例更新 emp_archive 表中 employee_id 等于 205 的记录,修改了其中的电话号码和薪水值:
SELECT employee_id, phone_number, salary
-- 205 |515.123.8080 |12008 |
FROM emp_archive
WHERE employee_id = 205;
UPDATE emp_archive
SET phone_number = '515.123.9009',
salary = 13000
WHERE employee_id = 205;
SELECT employee_id, phone_number, salary
FROM emp_archive
WHERE employee_id = 205;
employee_id |phone_number |salary |
------------|-------------|-------|
205 |515.123.9009 |13000 |
和插入数据一样,更新数据时会执行约束校验,确保不会产生违反约束的数据。以下语句违反了外键约束,因为不存在部门编号为 0 的部门:
-- 违反外键约束的更新操作
UPDATE employees
SET department_id = 0
WHERE employee_id = 207;
某些数据库还允许将多个字段组合在一起进行赋值(元组更新):
-- PostgreSQL, Db2 and SQLite
UPDATE emp_archive
SET (phone_number, commission_pct, department_id)
= ('011.043.2333', 0.2, 80)
WHERE job_id = 'SA_MAN';
-- Oracle
UPDATE emp_archive
SET (phone_number, commission_pct, department_id)
= (SELECT '011.043.2333', 0.2, 80 FROM dual)
WHERE job_id = 'SA_MAN';
MySQL、SQL Server 不支持元组更新
除了直接指定值之外,也可以通过一个关联子查询获取更新后的值:
UPDATE emp_archive
SET phone_number = (SELECT e.phone_number
FROM employees e
WHERE e.employee_id = emp_archive.employee_id),
commission_pct = 0.2,
department_id = 80
WHERE job_id = 'SA_MAN';
以上语句在 SET
中使用了一个关联子查询,将 employees 表中对应员工的电话号码更新到 emp_archive 表。
同样也可以使用子查询同时给多个字段赋值:
-- For Oracle, PostgreSQL, Db2 and SQLite
UPDATE emp_archive
SET (phone_number, commission_pct, department_id)
= (SELECT e.phone_number, e.commission_pct, e.department_id
FROM employees e
WHERE e.employee_id = emp_archive.employee_id)
WHERE job_id = 'SA_MAN';
删除数据
要删除表中的数据,使用 DELETE
语句。
DELETE FROM t
[WHERE conditions];
只有满足 WHERE
条件的数据才会被删除;如果省略 WHERE
条件,将会删除表中所有的数据。
以下语句删除 emp_archive 中 employee_id 等于 100 的数据:
-- SELECT *
DELETE
FROM emp_archive
WHERE employee_id = 100;
Oracle 可以省略 FROM 关键字
在删除数据之前,可以使用相应的 SELECT
语句查看将要删除的数据。
使用 DELETE
语句删除数据时,需要注意的一点就是,如果删除存在外键关联的父表中的数据,可能会违反外键约束:
-- SELECT *
DELETE
FROM departments
WHERE department_id = 60;
以上语句执行失败,显示违反外键约束。这是因为它的子表 employees 中存在部门编号为 60 的记录,如果删除了 departments 表中的记录,employees 中的这些数据就会属于一个不存在的部门。
可以先删除子表中对应的记录,然后删除父表中的数据;或者设置外键约束的 ON DELETE 选项级联删除或设置为 NULL。
合并数据
在 SQL:2003 标准中,引入了一个新的数据操作命令:MERGE
。它可以同时完成 INSERT
和 UPDATE
的操作,甚至 DELETE
的功能。
目前只有 Oracle、SQL Server 和 Db2 支持标准的 MERGE
语句,简单形式的语法如下:
-- Oracle, SQL Server and Db2
MEGRE INTO target_table [AS t_alias]
USING source_table [AS s_alias]
ON (condition)
WHEN MATCHED THEN
UPDATE SET column1 = expr_1,
column2 = expr_2,
...
WHEN NOT MATCHED THEN
INSERT (column1, column2, ...)
VALUES (expr_1, expr_2, ...);
其中,target_table 是合并的目标表;USING
指定了数据的来源,可以是一个表或者查询结果集;ON 指定了合并操作的判断条件,对于数据源中的每一行,如果在目标表中存在满足条件的记录,执行 UPDATE
操作更新目标表中对应的记录;如果不存在匹配的记录,执行 INSERT
在目标表中插入一条新记录。
MySQL、PostgreSQL、SQLite 使用非 SQL 标准的专有语法
先创建一个用于合并数据的目标表 emp_merge:
CREATE TABLE emp_merge
( employee_id INTEGER NOT NULL
, first_name CHARACTER VARYING(20)
, last_name CHARACTER VARYING(25) NOT NULL
, email CHARACTER VARYING(25) NOT NULL
, CONSTRAINT emp_merge_pk
PRIMARY KEY (employee_id)
, CONSTRAINT emp_merge_email
UNIQUE (email) );
使用 MERGE
语句将 employees 表中的部分数据(department_id = 60)合并到 emp_merge 表:
-- Oracle, SQL Server and Db2
MERGE INTO emp_merge t
USING (SELECT employee_id, first_name, last_name, email
FROM employees
WHERE department_id = 60) s
ON (t.employee_id = s.employee_id)
WHEN MATCHED THEN
UPDATE
SET t.first_name = s.first_name,
t.last_name = s.last_name,
t.email = s.email
WHEN NOT MATCHED THEN
INSERT (employee_id, first_name, last_name, email)
VALUES (s.employee_id, s.first_name, s.last_name, s.email);
第一次运行时,由于 emp_merge 表中没有任何数据,因此执行的都是 WHEN NOT MATCHED THEN
分支,即插入新的数据。
SELECT *
FROM emp_merge;
EMPLOYEE_ID |FIRST_NAME |LAST_NAME |EMAIL |
------------|-----------|----------|---------|
103 |Alexander |Hunold |AHUNOLD |
104 |Bruce |Ernst |BERNST |
106 |Valli |Pataballa |VPATABAL |
105 |David |Austin |DAUSTIN |
107 |Diana |Lorentz |DLORENTZ |
可以尝试修改 emp_merge 中的某些数据,然后再次运行上面的 MERGE
语句,查看 WHEN MATCHED THEN
分支的更新结果。
MERGE
语句也可以只包含更新或者插入操作:
-- Oracle, SQL Server and Db2
MERGE INTO emp_merge t
USING (SELECT employee_id, first_name, last_name, email
FROM employees
WHERE department_id = 60) s
ON (t.employee_id = s.employee_id)
WHEN NOT MATCHED THEN
INSERT (employee_id, first_name, last_name, email)
VALUES (s.employee_id, s.first_name, s.last_name, s.email);
以上语句只在没有匹配数据时插入新的数据,而存在匹配时的忽略任何操作。
MERGE
还支持 DELETE
功能,用于存在匹配数据时删除目标表中的记录:
-- Oracle
MERGE INTO emp_merge t
USING (SELECT employee_id, first_name, last_name, email
FROM employees
WHERE department_id = 60) s
ON (t.employee_id = s.employee_id)
WHEN MATCHED THEN
UPDATE
SET t.first_name = s.first_name,
t.last_name = s.last_name,
t.email = s.email
DELETE WHERE t.last_name = 'Austin'
WHEN NOT MATCHED THEN
INSERT (employee_id, first_name, last_name, email)
VALUES (s.employee_id, s.first_name, s.last_name, s.email);
-- SQL Server and Db2
MERGE INTO emp_merge t
USING (SELECT employee_id, first_name, last_name, email
FROM employees
WHERE department_id = 60) s
ON (t.employee_id = s.employee_id)
WHEN MATCHED AND t.last_name = 'Austin' THEN
DELETE
WHEN MATCHED THEN
UPDATE
SET t.first_name = s.first_name,
t.last_name = s.last_name,
t.email = s.email
WHEN NOT MATCHED THEN
INSERT (employee_id, first_name, last_name, email)
VALUES (s.employee_id, s.first_name, s.last_name, s.email);
以上语句的 MATCHED
分支增加了一个删除操作,对于目标表中的匹配数据,如果 first_name 等于 ‘Austin’,将会被删除。
MySQL、PostgreSQL 和 SQLite 使用专有的语法格式(UPSERT),不支持 DELETE
操作:
-- MySQL
INSERT INTO emp_merge(employee_id, first_name, last_name, email)
SELECT employee_id, first_name, last_name, email
FROM employees s
WHERE department_id = 60
ON DUPLICATE KEY UPDATE
first_name = s.first_name,
last_name = s.last_name,
email = s.email;
-- PostgreSQL and SQLite
INSERT INTO emp_merge(employee_id, first_name, last_name, email)
SELECT employee_id, first_name, last_name, email
FROM employees
WHERE department_id = 60
ON CONFLICT(employee_id) DO UPDATE
SET first_name = EXCLUDED.first_name,
last_name = EXCLUDED.last_name,
email = EXCLUDED.email;
数据库 | 插入 | 更新 | 删除 | 合并 | 描述 |
---|---|---|---|---|---|
Oracle | OK | OK | OK | OK | 支持 INSERT ALL 多表插入 DELETE FROM 可以简写成 DELETE |
MySQL | OK | OK | OK | OK | INSERT INTO 可以简写成 INSERT ;支持 INSERT INTO SET 语法 支持多表更新 支持多表删除 使用非标准的 INSERT ON DUPLICATE 语法 |
SQL Server | OK | OK | OK | OK | INSERT INTO 可以简写成 INSERT |
PostgreSQL | OK | OK | OK | OK | 使用非标准的 INSERT ON CONFLICT 语法 |
Db2 | OK | OK | OK | OK | |
SQLite | OK | OK | OK | OK | 使用非标准的 INSERT ON CONFLICT 语法 |
欢迎留言讨论!