本篇介绍如何使用 SQL:2016(ISO/IEC 9075:2016)标准中的数据定义语言(DDL)维护表的结构,包括创建表(CREATE TABLE
)、修改表(ALTER TABLE
)、删除表(DROP TABLE
)和截断表(TRUNCATE TABLE
),以及六种主流数据库中的实现及差异:Oracle、MySQL、Microsoft SQL Server、PostgreSQL、Db2、SQLite。
创建表
在 SQL 中,使用 CREATE TABLE
语句创建表:
CREATE TABLE table_name
(
column_1 data_type column_constraint,
column_2 data_type,
...,
table_constraint
);
首先,需要指定一个新的表名 table_name;括号内是字段的定义,包括字段名、数据类型以及可选的约束,多个字段使用逗号进行分隔;最后还可以定义基于表的约束。
以下语句用于创建表 departments:
CREATE TABLE departments
( department_id INTEGER NOT NULL PRIMARY KEY
, department_name CHARACTER VARYING(30) NOT NULL
, manager_id INTEGER
, location_id INTEGER
) ;
其中,部门编号(department_id)是整型数字,并且是该表的主键;部门名称(department_name)是一个变长字符串,非空;另外两个字段都是整型数字,可以为空。
如果想要创建一个自定义名称的主键约束,可以使用基于表的主键定义:
CREATE TABLE departments
( department_id INTEGER NOT NULL
, department_name CHARACTER VARYING(30) NOT NULL
, manager_id INTEGER
, location_id INTEGER
, CONSTRAINT dept_id_pk
PRIMARY KEY (department_id)
) ;
其中,dept_id_pk 是主键的名称。
标识列
标识列(identity column),也称为自增长列(auto increment),用于自动生成一个唯一的数字。它的主要用途就是为主键提供值。
标识列通常是通过一个内部的序列(sequence)来实现,每个表只能有一个标识列。首先来看一下 SQL 标准中的定义:
column_name data_type GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ]
其中,data_type 必须是数字类型(INTEGER、NUMERIC 等);GENERATED ALWAYS
表示总是由系统生成字段的值,不接受用户提供的值,GENERATED BY DEFAULT
表示如果用户提供了相应的值,就使用该值,否则系统会提供一个自动的值;sequence_options 可以控制序列值的生成方式,例如起始值、最大值、最小值、增量等等。
目前只有 Oracle、PostgreSQL 以及 Db2 支持标准 SQL 中的标识列语法。
以下示例为表 emp_identity 创建了一个标识列 emp_id,它是该表的主键:
-- Oracle, PostgreSQL and Db2
CREATE TABLE emp_identity(
emp_id INT GENERATED ALWAYS AS IDENTITY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
PRIMARY KEY (emp_id)
);
尝试往该表中插入一些数据:
-- Oracle, PostgreSQL and Db2
INSERT INTO emp_identity(first_name, last_name) VALUES ('Tony', 'Dong');
INSERT INTO emp_identity(first_name, last_name) VALUES ('Tony', 'Dong');
INSERT INTO emp_identity(first_name, last_name) VALUES ('Tony', 'Dong');
SELECT *
FROM emp_identity;
EMP_ID |FIRST_NAME |LAST_NAME |
-------|-----------|----------|
1 |Tony |Dong |
2 |Tony |Dong |
3 |Tony |Dong |
在 INSERT
语句中,我们没有为 emp_id 提供值,而是依赖系统自动生成的序列值。
对于 GENERATED ALWAYS
选项,如果我们使用自己提供的值,数据库将会提升错误信息:
-- Oracle
INSERT INTO emp_identity(emp_id,first_name, last_name) VALUES (100, 'Tony', 'Dong');
SQL Error: ORA-32795: cannot insert into a generated always identity column
-- PostgreSQL
INSERT INTO emp_identity(emp_id,first_name, last_name) VALUES (100, 'Tony', 'Dong');
ERROR: cannot insert into column "emp_id"
Detail: Column "emp_id" is an identity column defined as GENERATED ALWAYS.
-- Db2
INSERT INTO emp_identity(emp_id,first_name, last_name) VALUES (100, 'Tony', 'Dong');
SQL Error: A value cannot be specified for column "EMP_ID" which is defined as GENERATED ALWAYS.. SQLCODE=-798, SQLSTATE=428C9, DRIVER=4.21.29
PostgreSQL 可以在
INSERT
语句中使用OVERRIDING SYSTEM VALUE
选项避免这个问题。
我们再来看一下 GENERATED BY DEFAULT
选项:
-- Oracle, PostgreSQL and Db2
DROP TABLE emp_identity;
CREATE TABLE emp_identity(
emp_id INT GENERATED BY DEFAULT AS IDENTITY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
PRIMARY KEY (emp_id)
);
-- 使用系统提供的值
INSERT INTO emp_identity(first_name, last_name) VALUES ('Tony', 'Dong');
INSERT INTO emp_identity(first_name, last_name) VALUES ('Tony', 'Dong');
INSERT INTO emp_identity(first_name, last_name) VALUES ('Tony', 'Dong');
-- 使用用户提供的值
INSERT INTO emp_identity(emp_id,first_name, last_name) VALUES (100, 'Tony', 'Dong');
SELECT *
FROM emp_identity;
EMP_ID |FIRST_NAME |LAST_NAME |
-------|-----------|----------|
1 |Tony |Dong |
2 |Tony |Dong |
3 |Tony |Dong |
100 |Tony |Dong |
序列选项可以控制自动增长的一些属性,比如起始值,增长值:
-- Oracle, PostgreSQL and Db2
DROP TABLE emp_identity;
CREATE TABLE emp_identity(
emp_id INT GENERATED ALWAYS AS IDENTITY (START WITH 10 INCREMENT BY 10),
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
PRIMARY KEY (emp_id)
);
INSERT INTO emp_identity(first_name, last_name) VALUES ('Tony', 'Dong');
INSERT INTO emp_identity(first_name, last_name) VALUES ('Tony', 'Dong');
INSERT INTO emp_identity(first_name, last_name) VALUES ('Tony', 'Dong');
SELECT *
FROM emp_identity;
EMP_ID |FIRST_NAME |LAST_NAME |
-------|-----------|----------|
10 |Tony |Dong |
20 |Tony |Dong |
30 |Tony |Dong |
除了标准 SQL 语法之外,许多数据库通过专有的语法实现类似的功能:
-- MySQL
CREATE TABLE emp_identity(
emp_id INT AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
PRIMARY KEY (emp_id)
);
-- SQL Server
CREATE TABLE emp_identity(
emp_id INT IDENTITY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
PRIMARY KEY (emp_id)
);
-- PostgreSQL
CREATE TABLE emp_identity(
emp_id INT SERIAL,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
PRIMARY KEY (emp_id)
);
-- SQLite
CREATE TABLE emp_identity(
emp_id INT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
PRIMARY KEY (emp_id)
);
我们还可以通过另外一种方式,也就是基于一个查询结果或者其他的表创建新表:
CREATE TABLE table_name
AS
SELECT ...;
其中的 SELECT
语句定义了新表的结构和数据。
以下示例使用查询结果创建了一个新表:emp_it 。
-- Oracle, MySQL, PostgreSQL and SQLite
CREATE TABLE emp_it
AS
SELECT employee_id, last_name, job_id, salary
FROM employees
WHERE department_id = 60;
Oracle 和 Postgresql 支持为新表定义新的列名:
CREATE TABLE table_name( new_name1, new_name2, ... )
AS
SELECT ...;
对于 Db2,需要明确指定是否需要包含数据:
-- Db2
CREATE TABLE emp_it(empno, fname, job, salary)
AS (
SELECT employee_id, last_name, job_id, salary
FROM employees
WHERE department_id = 60) WITH DATA;
WITH DATA
表示需要包含查询结果的数据,WITH NO DATA
表示只创建表结构,不会生成数据。
还有一些数据库专用的语法形式:
-- SQL Server and PostgreSQL
SELECT employee_id AS empno, last_name, job_id, salary
INTO emp_it
FROM employees
WHERE department_id = 60;
-- MySQL and Db2
-- 只复制表结构,不包含数据
CREATE TABLE emp_demo
LIKE employees;
修改表
对于一个已有的表,可能会由于业务变更或者代码重构需要修改它的结构。因此,SQL 标准定义了修改表的语句:
ALTER TABLE table_name action;
其中的 action 表示要对表执行的操作,常见的操作包括增加列,修改列,删除列;增加约束,修改约束,删除约束等等。
首先是为表增加一个新的字段:
ALTER TABLE table_name
ADD [COLUMN] column_name data_type column_constraint;
添加字段的内容和创建表时类似,包括字段名、数据类型以及可选的列约束。
Oracle 和 SQL Server 不支持可选的
COLUMN
关键字
以下语句为表 emp_identity 新增一个字段 commission_pct:
ALTER TABLE emp_identity
ADD commission_pct NUMERIC(2,2) DEFAULT 0 NOT NULL;
MySQL 支持为新增的列指定位置:
ALTER TABLE table_name
ADD [COLUMN] column_name data_type column_constraint FIRST | AFTER some_column;
有时候我们需要修改表中字段的某些属性,比如数据类型,约束等。SQL 使用 ALETR TABLE ... ALTER COLUMN
语句修改字段的属性:
-- MySQL, SQL Server, PostgreSQL and Db2
ALTER TABLE table_name
ALTER COLUMN column_name action;
-- Oracle
ALTER TABLE table_name
MODIFY column_name action;
Oracle 使用
MODIFY
子句修改字段的属性。
SQLite 不支持修改字段的属性。
需要注意的是,不同的数据库支持的操作(action)并不相同。
以下不同语句都是修改表 emp_identity 的字段 last_name 的长度:
-- Oracle
ALTER TABLE emp_identity MODIFY last_name varchar(30);
-- MySQL
ALTER TABLE emp_identity CHANGE last_name last_name varchar(30);
-- SQL Server
ALTER TABLE emp_identity ALTER COLUMN last_name varchar(30);
-- PostgreSQL and Db2
ALTER TABLE emp_identity ALTER COLUMN last_name SET DATA TYPE varchar(30);
对字段的另一种常见的修改就是重命名,不过 SQL 标准没有对此进行定义。不同的数据库提供了自己的实现方式:
-- Oracle, MySQL, PosgtreSQL, Db2 and SQLite
ALTER TABLE emp_identity RENAME COLUMN last_name TO family_name;
-- SQL Server
EXEC sp_rename 'emp_identity.last_name', 'family_name', 'COLUMN';
只有 SQL Server 使用一个系统过程 sp_rename 实现该功能,其他数据库语法一致。
与重命名字段对应,通常也可以对表进行重命名:
-- Oracle, MySQL, PostgreSQL and SQLite
ALTER TABLE old_table RENAME TO new_table;
-- Oracle, MySQL and Db2
RENAME old_table TO new_table;
-- SQL Server
EXEC sp_rename 'old_table', 'new_table';
如果某个字段不再需要,可以删除:
-- Except for SQLite
ALTER TABLE emp_demo
DROP COLUMN department_id;
SQLite 不支持删除列的操作。
删除表
删除一个表的基本语法如下:
DROP TABLE table_name;
同样,许多数据库提供了扩展的功能选项:
-- Oracle 支持级联删除依赖的对象
DROP TABLE departments CASCADE CONSTRAINTS;
-- PostgreSQL 支持 IF EXISTS,同时删除多个表,级联删除
-- MySQL 支持 IF EXISTS,同时删除多个表,级联删除没有实际效果
DROP TABLE IF EXISTS departments, jobs CASCADE;
-- SQL Server 支持 IF EXISTS,同时删除多个表
DROP TABLE IF EXISTS departments, jobs;
-- SQLite 支持 IF EXISTS
DROP TABLE IF EXISTS departments;
截断表
SQL 还提供了一种特殊的操作,即截断表(TRNACATE),用于删除表中的所有数据:
-- Oracle, MySQL, SQL Server and PostgreSQL
TRUNCATE TABLE table_name;
Db2 需要增加一个额外的关键字 IMMEDIATE
:
-- Db2
TRUNCATE TABLE table_name IMMEDIATE;
MySQL、PostgreSQL 和 Db2 可以省略
TABLE
关键字。
SQLite 不支持 TRUNCATE
语句;但是对于不包含 WHERE
条件的 DELETE
操作,并且表上没有触发器,SQLite 可以执行一个类似的优化,快速删除表中所有的数据。
另外,对于外键关联中的父表,执行截断操作会导致违反外键约束。因此,有些数据库提供了级联截断的扩展支持:
-- Oracle 支持级联的截断操作
TRUNCATE TABLE departments CASCADE;
-- PostgreSQL 支持同时截断多个表,支持级联的截断操作
TRUNCATE TABLE departments, jobs CASCADE;
数据库 | 创建表 | 修改表 | 删除表 | 截断表 | 描述 |
---|---|---|---|---|---|
Oracle | OK | OK | OK | OK | 支持级联删除 支持级联截断 |
MySQL | OK | OK | OK | OK | 支持 CREATE TABLE LIKE 语法 支持同时删除多个表 |
SQL Server | OK | OK | OK | OK | 使用 SELECT INTO 语法 支持同时删除多个表 |
PostgreSQL | OK | OK | OK | OK | 支持 SELECT INTO 语法 支持同时删除多个表,支持级联删除 支持级联截断,同时截断多个表 |
Db2 | OK | OK | OK | OK | 支持 CREATE TABLE LIKE 语法 |
SQLite | OK | OK | OK | OK | 不支持修改列和删除列 通过特定的 DELETE 语句实现快速删除 |
欢迎留言讨论!