插入、更新和删除
1、插入新记录
问题:你想在表中插入一条新记录。例如,你想在 DEPT 表中插入一条新记录,并将其 DEPTNO 列、DNAME 列和 LOC 列分别设置为 50、PROGRAMMING 和 BALTIMORE。
解决方案:结合使用 INSERT 语句和 VALUES 子句一次插入一行。
sql
insert into dept (deptno,dname,loc)
values (50,'PROGRAMMING','BALTIMORE');
在 DB2、SQL Server、PostgreSQL 和 MySQL 中,除了一次插入一行,还可以一次插入多行,为此可以提供多个 VALUES 列表。
sql
/* 插入多行 */
insert into dept (deptno,dname,loc)
values (1,'A','B'),
(2,'B','C');
INSERT 语句让你能够在数据库表中创建新行。在所有数据库中,插入单行的语法都相同。
作为一种快捷方式,可以在 INSERT 语句中省略列列表。
sql
insert into dept
values (50,'PROGRAMMING','BALTIMORE')
然而,如果没有指定目标列,就必须在所有列中插入数据。另外,应注意 VALUES 列表中值的排列顺序:必须按数据库响应 SELECT * 查询时显示列的顺序提供值。无论使用哪种方式插入,都必须注意列约束,因为如果没有在每列中都插入值,那么在创建的新行中,有些列的值将为 NULL。如果这些列不能为 NULL,则将导致错误。
2、插入默认值
问题:定义表时,可以指定某些列为默认值。你想插入使用默认值的行,这样就不用指定值了。
请看下面的表。
sql
create table D (id integer default 0);
你想插入 0,而不是在 INSERT 语句的值列表中指定 0。你还想显式地插入默认值,而不管默认值是什么。
解决方案:所有数据库都支持使用关键字 DEFAULT 显式地将列值指定为默认值。有些数据库还提供了解决这种问题的其他方式。
下面的示例演示了如何使用关键字 DEFAULT。
sql
insert into D values (default);
还可以显式地指定列名。如果没有在所有列中都插入值,则必须这样做。
sql
insert into D (id) values (default)
由于 Oracle8i 数据库及更早的版本不支持关键字DEFAULT,因此在 Oracle9i 数据库之前,没有显式地插入默认值的方式。
在 MySQL 中,如果所有列都有默认值,则可以让值列表为空。
sql
insert into D values ();
在这种情况下,所有列都将被设置为默认值。
PostgreSQL 和 SQL Server 支持 DEFAULT VALUES 子句。
sql
insert into D default values;
DEFAULT VALUES 子句会导致所有列都为默认值。
在值列表中,关键字 DEFAULT 会在相应列中插入创建表时指定的默认值。所有 DBMS 都支持这个关键字。
如果给表中的所有列都指定了默认值(就像前面的 D 表那样),那么 MySQL、PostgreSQL 和 SQL Server 用户还有另一种选择,就是使用空的 VALUES 列表(用于MySQL)或 DEFAULT VALUES 子句(用于PostgreSQL 和 SQL Server)来创建一个全为默认值的新行。否则,就需要使用 DEFAULT 分别将各列设置为默认值。
sql
create table D (id integer default 0, foo varchar(10))
要在插入行时将 ID 设置为默认值,可以在插入列表中只包含 FOO。
sql
insert into D (name) values ('Bar')
这条语句将插入一个 ID 为 0 且 FOO 为 BAR 的新行。ID 为默认值是因为没有给它指定值。
3、用NULL覆盖默认值
问题:你想在有默认值的列中插入一列,并想用 NULL 覆盖默认值。请看下面的表。
sql
create table D (
id integer default 0,
foo VARCHAR(10)
);
你想在这张表中插入一行数据,并将其 ID 设置为NULL。
解决方案:可以在值列表中显式地指定 NULL。
sql
insert into d (id, foo) values (null, 'Brighten');
并非所有人都知道可以在 INSERT 语句的值列表中显式地指定 NULL。当不想给列指定值时,通常可以不在列列表和值列表中指定它的值。
sql
insert into d (foo) values ('Brighten')
这里没有给 ID 指定值。很多人以为这个列的值将为NULL,但由于创建表时给这个列指定了默认值,因此上述 INSERT 语句将插入一个 ID 值为 0(默认值)的新行。通过将列值指定为 NULL,可以将相应的列设置为NULL,即便该列有默认值(条件是没有设置禁止将该列设置为 NULL 的约束)。
4、将一张表中的行复制到另一张表中
问题:你想使用查询将一张表中的行复制到另一张表中。使用的查询可能很复杂,也可能很简单,但你的终极目标是将查询的结果插入另一张表中。例如,你想将 DEPT 表中的行复制到 DEPT_EAST 表中。假设 DEPT_EAST 表已经创建好,其结构与 DEPT 表相同(列数和列的数据类型都相同),但当前是空的。
解决方案:在 INSERT 语句中将生成所需行的查询用作值列表。
sql
create table dept_east
as
select *
from dept
where 1 = 0;
insert into dept_east (deptno,dname,loc)
select deptno,dname,loc
from dept
where loc in ( 'NEW YORK','BOSTON' );
只需在 INSERT 语句后面跟一个返回所需行的查询。如果要复制源表中所有的行,那么可以在查询中不指定WHERE 子句。与常规插入一样,并非必须显式地指定要插入哪些列,但如果没有指定目标列,则必须在所有列中都插入数据,还必须注意 SELECT 列表中值的排列顺序
5、复制表定义
问题:你想创建一张新表,该表包含的列与一张既有表相同。例如,你想创建 DEPT 表的一个副本,并将其命名为DEPT_2。但是,你不想复制表中的行,而只想复制表的列结构。
解决方案:
DB2:在 CREATE TABLE 命令中使用 LIKE 子句。
sql
create table dept_2 like dept;
Oracle、MySQL 和 PostgreSQL:在 CREATE TABLE 命令中使用一个不返回任何行的子查询。
sql
create table dept_2
as
select *
from dept
where 1 = 0;
SQL Server:在一个不返回任何行的子查询中使用 INTO 子句。
sql
select *
into dept_2
from dept
where 1 = 0;
6、同时插入多张表
问题:你想将查询返回的行插入多张表中。例如,你想将 DEPT表中的行插入 DEPT_EAST 表、DEPT_WEST 表和DEPT_MID 表中。这 3 张目标表的结构与 DEPT 表相同(列数和列的数据类型都相同),并且当前都是空的。
解决方案:解决方案是将查询的结果插入目标表中。
Oracle:使用 INSERT ALL 语句或 INSERT FIRST 语句。这两条语句的语法相同,唯一的差别是一条语句使用的是关键字 ALL,另一条语句使用的是关键字 FIRST。下面的语句使用了 INSERT ALL,因此需要考虑所有的目标表。
sql
insert all
when loc in ('NEW YORK','BOSTON') then
into dept_east (deptno,dname,loc) values (deptno,dname,loc)
when loc = 'CHICAGO' then
into dept_mid (deptno,dname,loc) values (deptno,dname,loc)
else
into dept_west (deptno,dname,loc) values (deptno,dname,loc)
select deptno,dname,loc
from dept;
使用 UNION ALL 合并所有的目标表,以创建一个内嵌视图,再将数据插入这个内嵌视图中。还必须给目标表设置约束条件,确保每行都被插入正确的目标表中。
sql
create table dept_east
( deptno integer,
dname varchar(10),
loc varchar(10) check (loc in ('NEW YORK','BOSTON')))
create table dept_mid
( deptno integer,
dname varchar(10),
loc varchar(10) check (loc = 'CHICAGO'))
create table dept_west
( deptno integer,
dname varchar(10),
loc varchar(10) check (loc = 'DALLAS'))
1 insert into (
2 select * from dept_west union all
3 select * from dept_east union all
4 select * from dept_mid
5 ) select * from dept
MySQL、PostgreSQL 和 SQL Server:这些 DBMS 都不支持多表插入。
7、禁止在特定列中插入值
问题:你想禁止用户(或软件应用程序)在特定列中插入值。例如,你希望一个程序在 EMP 表中插入行,但只能指定EMPNO、ENAME 和 JOB 列的值。
解决方案:创建一个基于表的视图,只暴露那些你想暴露的列,然后规定只能通过这个视图来插入。
例如,创建一个暴露 EMP 表中 3 列数据的视图。
sql
create view new_emps as
select empno, ename, job
from emp
授予用户和程序访问这个视图的权限,让它们能够填充该视图中的 3 列。不授予用户在 EMP 表中插入行的权限。这样,如果用户要新建 EMP 记录,那么可以在NEW_EMPS 视图中插入行,但他们只能给该视图的定义中指定的 3 列设置值,而不能给其他列设置值。
8、修改表中的记录
问题:你想修改表中部分行或全部行的值。例如,你可能想将部门编号为 20 的所有员工的薪水都提高 10%。下面的结果集显示了这个部门所有员工的 DEPTNO、ENAME 和SAL。
sql
select deptno,ename,sal
from emp
where deptno = 20
order by 1,3;
DEPTNO ENAME SAL
------ ---------- ----------
20 SMITH 800
20 ADAMS 1100
20 JONES 2975
20 SCOTT 3000
20 FORD 3000
你想将所有 SAL 值都提高 10%。
解决方案:使用 UPDATE 语句来修改数据库表中既有的行。
sql
update emp
set sal = sal * 1.10
where deptno = 20;
在 UPDATE 语句中,使用 WHERE 子句来指定要更新哪些行。如果没有指定 WHERE 子句,则将更新所有行。在上述解决方案中,表达式 SAL*1.10 返回了提高 10% 后的薪水。
进行大规模更新前,你可能想预览结果。为此,可以执行一条 SELECT 语句,并在其中使用你打算在 SET 子句中使用的表达式。下面的 SELECT 语句显示了加薪 10% 后的结果。
sql
select deptno,
ename,
sal as orig_sal,
sal*.10 as amt_to_add,
sal*1.10 as new_sal
from emp
where deptno=20
order by 1,5;
deptno | ename | orig_sal | amt_to_add | new_sal
--------+-------+----------+------------+---------
20 | SMITH | 880 | 88.00 | 968.00
20 | ADAMS | 1210 | 121.00 | 1331.00
20 | JONES | 3273 | 327.30 | 3600.30
20 | SCOTT | 3300 | 330.00 | 3630.00
20 | FORD | 3300 | 330.00 | 3630.00
(5 rows)
加薪情况被分成了两列,一列显示增加的薪水,另一列显示加薪后的薪水。
9、仅当存在匹配行时才更新
问题:你想更新一张表中的某些行,条件是在另一张表中存在与之对应的行。如果员工出现在了 EMP_BONUS 表中,就(在 EMP 表中)将其薪水提高 20%。下面的结果集显示了 EMP_BONUS 表中当前包含的数据。
sql
select empno, ename
from emp_bonus
EMPNO ENAME
---------- ---------
7369 SMITH
7900 JAMES
7934 MILLER
解决方案:在 UPDATE 语句的 WHERE 子句中,使用子查询在 EMP表中找出那些同时出现在 EMP_BONUS 表中的员工。这样,UPDATE 语句将只作用于子查询返回的员工,并给他们加薪 20%。
sql
update emp
set sal = sal * 1.20
where empno in (
select empno
from emp_bonus
);
子查询返回的结果决定了将更新 EMP 表中的哪些行。谓词 IN 会检查 EMP 表中 EMPNO 的值,看它们是否出现在了子查询返回的 EMPNO 值中。如果是的话,就更新相应的 SAL 值。
也可以使用 EXISTS 来代替 IN。
sql
update emp
set sal = sal * 1.20
where exists (
select null
from emp_bonus
where emp.empno=emp_bonus.empno
);
在 EXISTS 子查询中,看到 SELECT 列表中的 NULL,你可能会感到惊讶。不用担心,这个 NULL 不会给更新带来任何负面影响。可以说这样做反而提高了可读性,因为它凸显了这样一个事实:与结合使用子查询和运算符 IN的解决方案不同,真正决定将更新哪些行的是子查询中的WHERE 子句,而不是子查询的 SELECT 列表返回的值。
10、使用来自另一张表中的值进行更新
问题:你想使用一张表中的值更新另一张表中的行。例如,你有一张 NEW_SAL 表,其中存储了某些员工调整后的薪水。这张表的内容如下。
sql
create table new_sal(
DEPTNO integer,
SAL integer
);
insert into new_sal values (10, 4000);
DEPTNO 列是 NEW_SAL 表的主键。你想使用 NEW_SAL中的值更新 EMP 表中某些员工的薪水和业务提成。如果员工的 EMP.DEPTNO 与 NEW_SAL 表中的某一行的DEPTNO 相同,就将其 EMP.SAL 更新为该行的 SAL值,并将其 EMP.COMM 更新为该行的 SAL 值的 50%。EMP 表的内容如下。
sql
select deptno,ename,sal,comm
from emp
order by 1
DEPTNO ENAME SAL COMM
------ ---------- ---------- ----------
10 CLARK 2450
10 KING 5000
10 MILLER 1300
20 SMITH 800
20 ADAMS 1100
20 FORD 3000
20 SCOTT 3000
20 JONES 2975
30 ALLEN 1600 300
30 BLAKE 2850
30 MARTIN 1250 1400
30 JAMES 950
30 TURNER 1500 0
30 WARD 1250 500
解决方案:一种方法是,通过连接 NEW_SAL 表和 EMP 表,找到新的 COMM 值并将其返回给 UPDATE 语句。对于这样的更新,经常使用关联子查询或 CTE 来执行。另一种方法是,创建一个视图(根据数据库的支持情况,可以是传统视图,也可以是内嵌视图),然后更新这个视图。
DB2:使用关联子查询将 EMP 表中的 SAL 列和 COMM 列设置为新的值。另外,使用关联子查询来确定应该更新 EMP 表中的哪些行。
sql
update emp e
set (e.sal,e.comm) = (
select ns.sal, ns.sal/2
from new_sal ns
where ns.deptno=e.deptno
)
where exists (
select *
from new_sal ns
where ns.deptno = e.deptno
);
MySQL:在 UPDATE 子句中指定 EMP 表和 NEW_SAL 表,并在WHERE 子句中连接它们。
sql
update emp e, new_sal ns
set e.sal = ns.sal, comm = ns.sal / 2
where e.deptno = ns.deptno;
Oracle:DB2 的解决方案也适用于 Oracle,但在 Oracle 中,还可以使用另一种解决方案,即更新内嵌视图。
sql
update (
select e.sal as emp_sal, e.comm as emp_comm, ns.sal as ns_sal, ns.sal/2 as ns_comm
from emp e, new_sal ns
where e.deptno = ns.deptno
) set emp_sal = ns_sal, emp_comm = ns_comm;
PostgreSQL:DB2 的解决方案也适用于 PostgreSQL,但还可以在UPDATE 语句中直接连接(非常方便)。
sql
update emp
set sal = ns.sal, comm = ns.sal / 2
from new_sal ns
where ns.deptno = emp.deptno;
SQL Server:DB2 的解决方案也适用于 SQL Server,但还可以在UPDATE 语句中直接连接(类似于 PostgreSQL 解决方案)。
sql
update e
set e.sal = ns.sal, e.comm = ns.sal/2
from emp e, new_sal ns
where ns.deptno = e.deptno;
11、合并记录
问题:
你想根据是否存在相应的记录,来决定插入、更新或删除一张表的记录。(如果存在相应的记录,就更新;如果不存在,就插入;如果更新后不满足特定的条件,就删除。)例如,你想按下面的方式修改 EMP_COMMISSION表。
- 对于 EMP_COMMISSION 表中的员工,如果他们也包含在EMP 表中,就将其业务提成(COMM)更新为 1000。对于 COMM 可能被更新为 1000 的任何员工,如果他们的SAL 低于 2000,就将其删除(他们不应该出现在 EMP_COMMISSION 表中)。
- 对于包含在 EMP 表中但未包含在 EMP_COMMISSION 表中的员工,将他们插入 EMP_COMMISSION 表中,并使用EMP表中的EMPNO值、ENAME 值和 DEPTNO 值。
从本质上说,你要根据 EMP 表中给定行在EMP_COMMISSION 表中是否有匹配的行,来决定在EMP_COMMISSION 中执行 UPDATE 操作还是 INSERT操作。然后根据 UPDATE 操作是否会导致业务提成过高,来决定是否执行 DELETE 操作。
下面显示了 EMP 表和 EMP_COMMISSION 表当前包含的行。
sql
select deptno,empno,ename,comm
from emp
order by 1
DEPTNO EMPNO ENAME COMM
------ ---------- ------ ----------
10 7782 CLARK
10 7839 KING
10 7934 MILLER
20 7369 SMITH
20 7876 ADAMS
20 7902 FORD
20 7788 SCOTT
20 7566 JONES
30 7499 ALLEN 300
30 7698 BLAKE
30 7654 MARTIN 1400
30 7900 JAMES
30 7844 TURNER 0
30 7521 WARD 500
select deptno,empno,ename,comm
from emp_commission
order by 1
DEPTNO EMPNO ENAME COMM
---------- ---------- ---------- ----------
10 7782 CLARK
10 7839 KING
10 7934 MILLER
解决方案:用于解决这种问题的语句是 MERGE 语句,它可以根据需要执行 UPDATE 操作或 INSERT 操作。
sql
merge into emp_commission ec
using (select * from emp) emp
on (ec.empno=emp.empno)
when matched then
update set ec.comm = 1000
delete where (sal < 2000)
when not matched then
insert (ec.empno,ec.ename,ec.deptno,ec.comm)
values (emp.empno,emp.ename,emp.deptno,emp.comm)
当前,MySQL 中没有 MERGE 语句。这种查询适用于本书提及的其他 RDBMS,以及很多别的 RDBMS。
12、删除表中的所有记录
问题:你想删除表中的所有记录。
解决方案:使用 DELETE 命令删除表中的记录。例如,要删除 EMP表中的所有记录,可以使用如下命令。
sql
delete from emp
使用 DELETE 命令时,如果没有指定 WHERE 子句,则将删除指定表中的所有行。TRUNCATE 命令用于表,它不使用 WHERE 子句,因此在有些情况下,使用此命令更合适,因为其速度更快。然而,TRUNCATE 命令是不能撤销的,至少在 Oracle 中是这样。在特定 RDBMS 中,有关TRUNCATE 和 DELETE 在性能和回滚方面的差别,请参阅相关文档。
13、删除特定记录
问题:你想将满足特定条件的记录从表中删除。
解决方案:使用 DELETE 命令,并在其中使用 WHERE 子句指定要删除哪些行。如果要删除编号为 10 的部门的所有员工,那么可以使用如下命令。
sql
delete from emp where deptno = 10;
通过在 DELETE 命令中使用 WHERE 子句,可以删除表中的部分(而不是全部)行。别忘了使用 SELECT 语句预览你编写的 WHERE 子句的影响,确保正确地删除数据,因为即便在非常简单的情况下,也可能错误地删除数据。例如,在前面的实例中,输入错误可能导致删除的是编号为 20 的部门(而不是编号为 10 的部门)的员工!
14、删除单条记录
问题:你想从表中删除单条记录。
解决方案:这是 13 节的特例。关键是确保选择标准足够严,使得只有你要删除的那条记录符合条件。通常,可以根据主键进行删除,例如,要删除员工 CLARK,可以指定条件EMPNO = 7782。
sql
delete from emp where empno = 7782;
执行删除操作时,总是需要指定要删除的行,而 DELETE操作带来的影响取决于其 WHERE 子句。如果省略了WHERE 子句,那么 DELETE 操作的影响范围将是整张表。通过在 WHERE 子句中指定条件,可以将影响范围缩小到只有一组或一条记录。删除单条记录时,通常应该使用主键或独一无二的键来指定要删除哪条记录。
15、删除违反引用完整性的记录
问题:当一张表中的记录引用了另一张表中不存在的记录时,你想将其删除。例如,给有些员工指定的部门不存在,你想将这些员工删除。
解决方案:结合使用谓词 NOT EXISTS 和子查询来检查部门编号是否有效。
sql
delete from emp
where not exists (
select * from dept
where dept.deptno = emp.deptno
)
也可以使用谓词 NOT IN 来编写查询。
sql
delete from emp
where deptno not in (select deptno from dept)
16、删除重复记录
问题:你想删除一张表中的重复记录。请看下面的表。
sql
create table dupes (id integer, name varchar(10));
insert into dupes values (1, 'NAPOLEON');
insert into dupes values (2, 'DYNAMITE');
insert into dupes values (3, 'DYNAMITE');
insert into dupes values (4, 'SHE SELLS');
insert into dupes values (5, 'SEA SHELLS');
insert into dupes values (6, 'SEA SHELLS');
insert into dupes values (7, 'SEA SHELLS');
对于每组重复的名称,比如 SEA SHELLS,你想随便保留其中一个,然后将其他的都删除。就 SEA SHELLS 而言,你不在乎删除的是第 5 行和第 6 行、第 5 行和第 7行还是第 6 行和第 7 行,只要最后只保留一条有关 SEASHELLS 的记录就行。
** 解决方案**:使用一个子查询,并在其中使用聚合函数(如 MIN)来选择要保留的 ID(在本例中,只保留 ID 值最小的NAME)。
sql
delete from dupes
where id not in (
select min(id)
from dupes
group by name
);
MySQL 用户需要使用稍微不同的语法,因为在本书撰写之时,在 DELET 中不能引用同一张表两次。
sql
delete from dupes
where id not in (
select min(id)
from (
select id, name
from dupes
) tmp
group by name
);
17、删除在另一张表中引用了的记录
问题:你想删除在另一张表中引用了的记录。请看下面的表(DEPT_ACCIDENTS),它记录了一家制造企业发生的每次事故,其中每行都包含发生事故的部门以及事故的类型。
sql
create table dept_accidents(
deptno integer,
accident_name varchar(20)
);
insert into dept_accidents values (10,'BROKEN FOOT');
insert into dept_accidents values (10,'FLESH WOUND');
insert into dept_accidents values (20,'FIRE');
insert into dept_accidents values (20,'FIRE');
insert into dept_accidents values (20,'FLOOD');
insert into dept_accidents values (30,'BRUISED GLUTE');
你想将所属部门发生事故的次数不少于 3 次的员工从EMP 表中删除。
解决方案:使用子查询和聚合函数 COUNT 找出发生事故不少于 3 次的部门,然后删除在这些部门工作的员工。
sql
delete from emp
where deptno in (
select deptno
from dept_accidents
group by deptno
having count(*) >= 3
);