SQL 从入门到出门 第 15 章 维护表结构

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

本篇介绍如何使用 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 语句实现快速删除

欢迎留言讨论!

猜你喜欢

转载自blog.csdn.net/horses/article/details/83750243
今日推荐