1. 程式人生 > >SQL 從入門到出門 第 15 章 維護表結構

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 語句實現快速刪除

歡迎留言討論!