SQL 从入门到出门 第14章 数据操作

版权声明:本站不全为博主原创文章,欢迎转载,转载记得标明出处。^-^ https://blog.csdn.net/horses/article/details/82967268

本篇介绍 SQL:2016(ISO/IEC 9075:2016)标准中定义的数据操作语言(DML),包括 INSERT、UPDATE、DELETE 以及 MERGE,以及六种主流数据库中的实现及差异:Oracle、MySQL、Microsoft SQL Server、PostgreSQL、Db2、SQLite。

插入数据

SQL 标准中提供了 3 种数据插入的方法:

  1. INSERT INTO … VALUES;
  2. INSERT INTO … SELECT;
  3. 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 。它可以同时完成 INSERTUPDATE 的操作,甚至 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 语法

欢迎留言讨论!

猜你喜欢

转载自blog.csdn.net/horses/article/details/82967268