SQL 從入門到出門 第 15 章 維護表結構
文章目錄
本篇介紹如何使用 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 語句實現快速刪除 |
歡迎留言討論!