SQL必知必會筆記(下)

smallhappy發表於2019-05-14

第14課 組合查詢

組合查詢

多數SQL查詢只包含從一個或多個表中返回資料的單條 SELECT 語句。但是,SQL 也允許執行多個查詢(多條 SELECT 語句),並將結果作為一個查詢結果集返回。這些組合查詢通常稱為並(union)或複合查詢(compound query)。

主要有兩種情況需要使用組合查詢:

  • 在一個查詢中從不同的表返回結構資料
  • 對一個表執行多個查詢,按一個查詢返回資料

建立組合查詢

可以用 UNION 操作符來組合數條 SQL 查詢。

使用 UNION

使用 UNION 很簡單,所要做的只是給出每條 SELECT 語句,在各條語句之間放上關鍵字 UNION。

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
複製程式碼

使用多條WHERE子句而不是UNION的相同查詢:

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
OR cust_name = 'Fun4All';
複製程式碼

對於較複雜的過濾條件,或者從多個表(而不是一個表)中檢索資料的情形,使用 UNION 可能會使處理更簡單。

UNION 規則

  • UNION 必須由兩條或兩條以上的 SELECT 語句組成,語句之間用關鍵字 UNION 分隔(因此,如果組合四條 SELECT 語句,將要使用三個UNION關鍵字)。
  • UNION 中的每個查詢必須包含相同的列、表示式或聚集函式(不過,各個列不需要以相同的次序列出)。
  • 列資料型別必須相容:型別不必完全相同,但必須是DBMS可以隱含轉換的型別(例如,不同的數值型別或不同的日期型別)。

包含或取消重複的行

UNION 從查詢結果集中自動去除了重複的行;換句話說,它的行為與一條 SELECT 語句中使用多個 WHERE 子句條件一樣。

如果想返回所有的匹配行,可使用 UNION ALL 而不是 UNION。

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION ALL
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
複製程式碼

UNION ALL 為 UNION 的一種形式,它完成 WHERE 子句完成不了的工作。

對組合查詢結果排序

在用 UNION 組合查詢時,只能使用一條 ORDER BY 子句,它必須位於最後一條 SELECT 語句之後。

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All'
ORDER BY cust_name, cust_contact;
複製程式碼

第15課 插入資料

資料插入

INSERT 用來將行插入(或新增)到資料庫表。插入有幾種方式:

  • 插入完整的行
  • 插入行的一部分
  • 插入某些查詢的結果

插入及系統安全:使用 INSERT 語句可能需要客戶端/伺服器 DBMS 中的特定安全許可權。在你試圖使用 INSERT 前,應該保證自己有足夠的安全許可權。

插入完整的行

把資料插入表中的最簡單方法是使用基本的 INSERT 語法,它要求指定表名和插入到新行中的值。

INSERT INTO Customers
VALUES('1000000006', 'Toy Land', '123 Any Street', 'New York', 'NY', '11111', 'USA', NULL, NULL);
複製程式碼

儲存到表中每一列的資料在 VALUES 子句中給出,必須給每一列提供一個值。如果某列沒有值,則應該使用 NULL 值(假定表允許對該列指定空值)。各列必須以它們在表定義中出現的次序填充。

雖然這種語法很簡單,但並不安全,應該儘量避免使用。上面的 SQL 語句高度依賴於表中列的定義次序,還依賴於其容易獲得的次序資訊。即使可以得到這種次序資訊,也不能保證各列在下一次表結構變動後保持完全相同的次序。因此,編寫依賴於特定列次序的 SQL 語句是很不安全的,這樣做遲早會出問題。

編寫 INSERT 語句的更安全(不過更繁瑣)的方法如下:

INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES('1000000006', 'Toy Land', '123 Any Street', 'New York', 'NY', '11111', 'USA', NULL, NULL);
複製程式碼

因為提供了列名,VALUES 必須以其指定的次序匹配指定的列名,不一定按各列出現在表中的實際次序。其優點是,即使表的結構改變,這條 INSERT 語句仍然能正確工作。

給出列名的情況下,以不同的次序填充仍然正確:

INSERT INTO Customers(cust_id, cust_contact, cust_email, cust_name, cust_address, cust_city, cust_state, cust_zip)
VALUES('1000000006', NULL, NULL, 'Toy Land', '123 Any Street', 'New York', 'NY', '11111');
複製程式碼

小心使用 VALUES:不管使用哪種 INSERT 語法,VALUES 的數目都必須正確。如果不提供列名,則必須給每個表列提供一個值;如果提供列名,則必須給列出的每個列一個值。否則,就會產生一條錯誤訊息,相應的行不能成功插入。

插入部分行

使用 INSERT 的推薦方法是明確給出表的列名。使用這種語法,還可以省略列,這表示可以只給某些列提供值,給其他列不提供值。

INSERT INTO Customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country)
VALUES('1000000006', 'Toy Land', '123 Any Street', 'New York', 'NY', '11111', 'USA');
複製程式碼

省略的列必須滿足以下條件:

  • 該列定義為允許 NULL 值(無值或空值)
  • 在表定義中給出預設值。這表示如果不給出值,將使用預設值。

插入檢索出的資料

INSERT 還存在另一種形式,可以利用它將 SELECT 語句的結果插入表中,這就是所謂的INSERT SELECT。顧名思義,它是由一條 INSERT語句和一條 SELECT語句組成的。

INSERT INTO Customers(cust_id, cust_contact, cust_email, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country)
SELECT cust_id, cust_contact, cust_email, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country
FROM CustNew;
複製程式碼

如果 CustNew 這個表確實有資料,則所有的資料將被插入到 Customers 表。

從一個表複製到另一個表

有一種資料插入不使用 INSERT 語句。要將一個表的內容複製到一個全新的表(執行中建立的表),可以使用 SELECT INTO 語句。

SELECT *
INTO CustCopy
FROM Customers;
複製程式碼

要想只複製部分的列,可以明確給出列名,而不是使用 * 萬用字元。

在使用SELECT INTO時,需要知道一些事情:

  • 任何SELECT選項和子句都可以使用,包括 WHERE和 GROUP BY
  • 可利用聯結從多個表插入資料
  • 不管從多少個表中檢索資料,資料都只能插入到一個表中

第16課 更新和刪除資料

更新資料

更新(修改)表中的資料,可以使用 UPDATE 語句。有兩種使用 UPDATE 的方式:

  • 更新表中的特定行
  • 更新表中的所有行

基本的 UPDATE 語句由三部分組成,分別是:

  • 要更新的表
  • 列名和它們的新值
  • 確定要更新那些行的過濾條件
UPDATE Customers
SET cust_email = 'kim@thetoystore.com
WHERE cust_id = '1000000005';
複製程式碼

不要省略 WHERE 子句:在使用 UPDATE 時一定要細心。因為稍不注意,就會更新表中的所有行。

更新更多列的語法稍有不同:

UPDATE Customers
SET cust_contact = 'Sam Roberts',
	cust_email = 'sam@toyland.com'
WHERE cust_id = '1000000006';
複製程式碼

要刪除某個列的值,可設定它為 NULL(假如表定義允許 NULL 值)。

UPDATE Customers
SET cust_email = NULL
WHERE cust_id = '1000000005'
複製程式碼

其中 NULL 用來去除 cust_email 列中的值。這與儲存空字串很不同(空字串用''表示,是一個值),而 NULL 表示沒有值。

刪除資料

從一個表中刪除(去掉)資料,使用 DELETE 語句。有兩種使用 DELETE 的方式:

  • 從表中刪除特定的行
  • 從表中刪除所有行
DELETE FROM Customers
WHERE cust_id = '1000000006';
複製程式碼

不要省略 WHERE 子句:在使用 DELETE 時一定要細心。因為稍不注意,就會錯誤地刪除表中所有行。

友好的外來鍵:使用外來鍵確保引用完整性的一個好處是,DBMS 通常可以防止刪除某個關係需要用到的行。例如,要從 Products 表中刪除一個產品,而這個產品用在 OrderItems 的已有訂單中,那麼 DELETE 語句將丟擲錯誤並中止。這是總要定義外來鍵的另一個理由。

更新和刪除的指導原則

如果省略了 WHERE 子句,則 UPDATE 或 DELETE 將被應用到表中所有的行。因此許多 SQL 程式設計師使用 UPDATE 或 DELETE 時需要遵循以下原則:

  • 除非確實打算更新和刪除每一行,否則絕對不要使用不帶 WHERE子句的 UPDATE 或 DELETE 語句。
  • 保證每個表都有主鍵(如果忘記這個內容,請參閱第 12課),儘可能像 WHERE 子句那樣使用它(可以指定各主鍵、多個值或值的範圍)。
  • 在 UPDATE 或 DELETE 語句使用 WHERE 子句前,應該先用 SELECT 進行測試,保證它過濾的是正確的記錄,以防編寫的 WHERE 子句不正確。
  • 使用強制實施引用完整性的資料庫(關於這個內容,請參閱第12課),這樣 DBMS 將不允許刪除其資料與其他表相關聯的行。
  • 有的 DBMS 允許資料庫管理員施加約束,防止執行不帶 WHERE 子句的 UPDATE 或 DELETE 語句。如果所採用的 DBMS 支援這個特性,應該使用它。

第17課 建立和操縱表

建立表

一般有兩種建立表的方法:

  • 多數 DBMS 都具有互動式建立和管理資料庫表的工具
  • 表也可以直接用 SQL 語句操縱 用程式建立表,可以使用 SQL 的 CREATE TABLE 語句。需要注意的是,使用互動式工具時實際上就是使用 SQL 語句。這些語句不是使用者編寫的,介面工具會自動生成並執行相應的 SQL 語句(更改已有的表時也是這樣)。

表建立基礎

利用 CREATE TABLE 建立表,必須給出下列資訊:

  • 新表的名字,在關鍵字 CREATE TABLE 之後給出
  • 表列的名字和定義,用逗號分隔
  • 有的 DBMS 還要求指定表的位置
CREATE TABLE Products
(
	prod_id		CHAR(10)			NOT NULL,
	vend_id		CHAR(10)			NOT NULL,
	prod_name	CHAR(254)		NOT NULL,
	prod_price	DECIMAL(8, 2)		NOT NULL,
	prod_desc	VARCHAR(1000)	NULL
);
複製程式碼

替換現有的表:在建立新的表時,指定的表名必須不存在,否則會出錯。防止意外覆蓋已有的表,SQL 要求首先手工刪除該表(請參閱後面的內容),然後再重建它,而不是簡單地用建立表語句覆蓋它。

使用 NULL 值

在插入或更新行時,該列必須有值。每個表列要麼是 NULL列,要麼是 NOT NULL 列,這種狀態在建立時由表的定義規定。

CREATE TABLE Orders
(
	order_num	INTEGER		NOT NULL,
	order_date	DATETIME	NOT NULL,
	cust_id		CHAR(10)		NOT NULL
);
複製程式碼

這三列都需要,因此每一列的定義都含有關鍵字 NOT NULL。這就會阻止插入沒有值的列。如果插入沒有值的列,將返回錯誤,且插入失敗。

CREATE TABLE Vendors
(
	vend_id			CHAR(10)		NOT NULL,
	vend_name		CHAR(50)	NOT NULL,
	vend_address		CHAR(50)	,
	vend_city			CHAR(50)	,
	vend_state		CHAR(5)		,
	vend_zip			CHAR(10)		,
	vend_country		CHAR(50)
);
複製程式碼

NULL 為預設設定,如果不指定 NOT NULL,就認為指定的是 NULL。

主鍵和 NULL 值:主鍵是其值唯一標識表中每一行的列。只有不允許 NULL 值的列可作為主鍵,允許 NULL 值的列不能作為唯一標識。

指定預設值

CREATE TABLE OrderItems
(
	order_num		INTEGER			NOT NULL,
	order_item		INTEGER			NOT NULL,
	prod_id			CHAR(10)			NOT NULL,
	quantity			INTEGER			NOT NULL		DEFAULT 1,
	item_price		DECIMAL(8, 2)		NOT NULL
);
複製程式碼

預設值經常用於日期或時間戳列。

更新表

更新表定義,可以使用 ALTER TABLE 語句。以下是使用 ALTER TABLE 時需要考慮的事情。

  • 理想情況下,不要在表中包含資料時對其進行更新。應該在表的設計過程中充分考慮未來可能的需求,避免今後對錶的結構做大改動
  • 所有的 DBMS 都允許給現有的表增加列,不過對所增加列的資料型別(以及 NULL 和 DEFAULT 的使用)有所限制
  • 許多 DBMS 不允許刪除或更改表中的列
  • 多數 DBMS 允許重新命名錶中的列
  • 許多 DBMS 限制對已經填有資料的列進行更改,對未填有資料的列幾乎沒有限制

使用 ALTER TABLE 更改表結構,必須給出下面的資訊:

  • 在 ALTER TABLE 之後給出要更改的表名(該表必須存在,否則將出錯)
  • 列出要做哪些更改
ALTER TABLE Vendors
ADD vend_phone CHAR(20);
複製程式碼

更改或刪除列、增加約束或增加鍵,這些操作也使用類似的語法:

ALTER TABLE Vendors
DROP COLUMN vend_phone;
複製程式碼

複雜的表結構更改一般需要手動刪除過程,它涉及以下步驟:

(1) 用新的列布局建立一個新表; (2) 使用 INSERT SELECT 語句(關於這條語句的詳細介紹,請參閱第 15課)從舊錶複製資料到新表。有必要的話,可以使用轉換函式和計算欄位; (3) 檢驗包含所需資料的新表; (4) 重新命名舊錶(如果確定,可以刪除它); (5) 用舊錶原來的名字重新命名新表; (6) 根據需要,重新建立觸發器、儲存過程、索引和外來鍵。

小心使用 ALTER TABLE:使用 ALTER TABLE 要極為小心,應該在進行改動前做完整的備份(表結構和資料的備份)。資料庫表的更改不能撤銷,如果增加了不需要的列,也許無法刪除它們。類似地,如果刪除了不應該刪除的列,可能會丟失該列中的所有資料。

刪除表

DROP TABLE CustCopy;
複製程式碼

刪除表沒有確認,也不能撤銷,執行這條語句將永久刪除該表。

使用關係規則防止意外刪除:許多 DBMS 允許強制實施有關規則,防止刪除與其他表相關聯的表。在實施這些規則時,如果對某個表釋出一條 DROP TABLE 語句,且該表是某個關係的組成部分,則DBMS將阻止這條語句執行,直到該關係被刪除為止。如果允許,應該啟用這些選項,它能防止意外刪除有用的表。

重新命名錶

所有重新命名操作的基本語法都要求指定舊錶名和新表名。不過,存在 DBMS 實現差異。關於具體的語法,請參閱相應的 DBMS 文件。

第18課 使用檢視

檢視

檢視是虛擬的表。與包含資料的表不一樣,檢視只包含使用時動態檢索 資料的查詢。

用下面的 SELECT 語句從三個表中檢索資料:

SELECT cust_name, cust_contact
FROM Customers, Orders, OrderItems
WHERE Customer.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num
AND prod_id = 'RGAN01';
複製程式碼

現在,假如可以把整個查詢包裝成一個名為 ProductCustomers 的虛擬表,則可以如下輕鬆地檢索出相同的資料:

SELECT cust_name, cust_contact
FROM ProductCustomers
WHERE prod_id = 'RGAN01';
複製程式碼

這就是檢視的作用。ProductCustomers 是一個檢視,它不包含任何列或資料,包含的是一個查詢(與上面用以正確聯結表的查詢相同)。

為什麼使用檢視

下面是檢視的一些常見應用:

  • 重用 SQL 語句
  • 簡化複雜的SQL操作。在編寫查詢後,可以方便地重用它而不必知道其基本查詢細節。
  • 使用表的一部分而不是整個表。
  • 保護資料。可以授予使用者訪問表的特定部分的許可權,而不是整個表的訪問許可權。
  • 更改資料格式和表示。檢視可返回與底層表的表示和格式不同的資料。

建立檢視之後,可以用與表基本相同的方式使用它們。可以對檢視執行 SELECT 操作,過濾和排序資料,將檢視聯結到其他檢視或表,甚至新增和更新資料(新增和更新資料存在某些限制,關於這個內容稍後做介紹)。

重要的是,要知道檢視僅僅是用來檢視儲存在別處資料的一種設施。檢視本身不包含資料,因此返回的資料是從其他表中檢索出來的。在新增或更改這些表中的資料時,檢視將返回改變過的資料。

效能問題:因為檢視不包含資料,所以每次使用檢視時,都必須處理查詢執行時需要的所有檢索。如果你用多個聯結和過濾建立了複雜的檢視或者巢狀了檢視,效能可能會下降得很厲害。因此,在部署使用了大量檢視的應用前,應該進行測試。

檢視的規則和限制

關於檢視建立和使用的一些最常見的規則和限制:

  • 與表一樣,檢視必須唯一命名(不能給檢視取與別的檢視或表相同的名字)
  • 對於可以建立的檢視數目沒有限制。
  • 建立檢視,必須具有足夠的訪問許可權。這些許可權通常由資料庫管理人員授予。
  • 檢視可以巢狀,即可以利用從其他檢視中檢索資料的查詢來構造檢視。所允許的巢狀層數在不同的DBMS中有所不同(巢狀檢視可能會嚴重降低查詢的效能,因此在產品環境中使用之前,應該對其進行全面測試)。
  • 許多 DBMS 禁止在檢視查詢中使用 ORDER BY 子句。
  • 有些 DBMS 要求對返回的所有列進行命名,如果列是計算欄位,則需要使用別名(關於列別名的更多資訊,請參閱第7課)。
  • 檢視不能索引,也不能有關聯的觸發器或預設值。
  • 有些 DBMS 把檢視作為只讀的查詢,這表示可以從檢視檢索資料,但不能將資料寫回底層表。詳情請參閱具體的 DBMS 文件。
  • 有些 DBMS 允許建立這樣的檢視,它不能進行導致行不再屬於檢視的插入或更新。例如有一個檢視,只檢索帶有電子郵件地址的顧客。如果更新某個顧客,刪除他的電子郵件地址,將使該顧客不再屬於檢視。這是預設行為,而且是允許的,但有的 DBMS 可能會防止這種情況發生。

建立檢視

檢視用 CREATE VIEW 語句來建立。刪除檢視可以用 DROP VIEW 。

利用檢視簡化複雜的聯結

CREATE VIEW ProductCustomers AS
SELECT cust_name, cust_contact, prod_id
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num;
複製程式碼

在以上檢視中進行檢索:

SELECT cust_name, cust_contact
FROM ProductCustomers
WHERE prod_id = 'RGAN01';
複製程式碼

用檢視重新格式化檢索出的資料

SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' AS vend_title
FROM Vendors
ORDER BY vend_name;
複製程式碼

把此語句轉換為檢視:

CREATE VIEW VendorLocations AS
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')' AS vend_title
FROM Vendors;
複製程式碼

再檢索資料:

SELECT *
FROM VendorLocations;
複製程式碼

用檢視過濾不想要的資料

CREATE VIEW CustomerEmailList AS
SELECT cust_id, cust_name, cust_email
FROM Customers
WHERE cust_email IS NOT NULL;
複製程式碼

再檢索資料:

SELECT *
FROM CustomerEMailList;
複製程式碼

使用檢視和計算欄位

SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
複製程式碼

將以上查詢轉成檢視:

CREATE VIEW OrderItemsExpanded AS 
SELECT order_num, prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM OrderItems;
複製程式碼

再檢索資料:

SELECT *
FROM OrderItemsExpanded
WHERE order_num = 20008;
複製程式碼

第19課 使用儲存過程

儲存過程

簡單來說,儲存過程就是為以後使用而儲存的一條或多條 SQL 語句。可將其視為批檔案,雖然它們的作用不僅限於批處理。

為什麼要使用儲存過程

理由很多,下面給出一些主要的:

  • 通過把處理封裝在一個易用的單元中,可以簡化複雜的操作(如前面例子所述)。
  • 由於不要求反覆建立一系列處理步驟,因而保證了資料的一致性。如果所有開發人員和應用程式都使用同一儲存過程,則所使用的程式碼都是相同的。這一點的延伸就是防止錯誤。需要執行的步驟越多,出錯的可能性就越大。防止錯誤保證了資料的一致性。
  • 簡化對變動的管理。如果表名、列名或業務邏輯(或別的內容)有變化,那麼只需要更改儲存過程的程式碼。使用它的人員甚至不需要知道這些變化。 這一點的延伸就是安全性。通過儲存過程限制對基礎資料的訪問,減少了資料訛誤(無意識的或別的原因所導致的資料訛誤)的機會。
  • 因為儲存過程通常以編譯過的形式儲存,所以 DBMS 處理命令所需的工作量少,提高了效能。
  • 存在一些只能用在單個請求中的 SQL 元素和特性,儲存過程可以使用它們來編寫功能更強更靈活的程式碼。

換句話說,使用儲存過程有三個主要的好處,即簡單、安全、高效能。

執行儲存過程

儲存過程的執行遠比編寫要頻繁得多,因此我們先介紹儲存過程的執行。執行儲存過程的 SQL 語句很簡單,即 EXECUTE。EXECUTE 接受儲存過程名和需要傳遞給它的任何引數。

EXECUTE AddNewProduct('JTS01', 'Stuffed Eiffel Tower', 6.49, 'Plush stuffed toy with the text La Tour Eiffel in red white and blue'0;
複製程式碼

這裡執行一個名為 AddNewProduct 的儲存過程,將一個新產品新增到 Products 表中。AddNewProduct 有四個引數,分別是:供應商 ID(Vendors 表的主鍵)、產品名、價格和描述。這 4個引數匹配儲存過程中4個預期變數(定義為儲存過程自身的組成部分)。此儲存過程將新行新增到 Products 表,並將傳入的屬性賦給相應的列。

在 Products表中還有另一個需要值的列 prod_id列,它是這個表的主鍵。為什麼這個值不作為屬性傳遞給儲存過程?要保證恰當地生成此 ID,最好是使生成此 ID 的過程自動化(而不是依賴於終端使用者的輸入)。

以下是儲存過程所完成的工作:

  • 驗證傳遞的資料,保證所有4個引數都有值;
  • 生成用作主鍵的唯一ID;
  • 將新產品插入 Products 表,在合適的列中儲存生成的主鍵和傳遞的資料。

建立儲存過程

Oracle 版本:

CREATE PROCEDURE MailingListCount (
	ListCount OUT INTEGER
)
IS
v_rows INTEGER;
BEGIN
	SELECT COUNT(*) INTO v_rows
	FROM Customers
	WHERE NOT cust_email IS NULL;
	ListCount := v_rows;
END;
複製程式碼

這個儲存過程有一個名為 ListCount 的引數。此引數從儲存過程返回一個值而不是傳遞一個值給儲存過程。關鍵字 OUT 用來指示這種行為。Oracle支援 IN(傳遞值給儲存過程)、OUT(從儲存過程返回值,如這裡)、INOUT (既傳遞值給儲存過程也從儲存過程傳回值)型別的引數。儲存過程的程式碼括在 BEGIN 和 END 語句中,這裡執行一條簡單的 SELECT 語句,它檢索具有郵件地址的顧客。然後用檢索出的行數設定 ListCount(要傳遞的輸出引數)。

第20課 管理事務處理

事務處理

使用事務處理(transaction processing),通過確保成批的 SQL 操作要麼完全執行,要麼完全不執行,來維護資料庫的完整性。

關聯式資料庫把資料儲存在多個表中,使資料更容易操縱、維護和重用。不用深究如何以及為什麼進行關聯式資料庫設計,在某種程度上說,設計良好的資料庫模式都是關聯的。

事務處理是一種機制,用來管理必須成批執行的 SQL 操作,保證資料庫不包含不完整的操作結果。利用事務處理,可以保證一組操作不會中途停止,它們要麼完全執行,要麼完全不執行(除非明確指示)。如果沒有錯誤發生,整組語句提交給(寫到)資料庫表;如果發生錯誤,則進行回退(撤銷),將資料庫恢復到某個已知且安全的狀態。

下面是關於事務處理需要知道的幾個術語:

  • 事務(transaction)指一組 SQL 語句;
  • 回退(rollback)指撤銷指定 SQL 語句的過程;
  • 提交(commit)指將未儲存的 SQL 語句結果寫入資料庫表;
  • 保留點(savepoint)指事務處理中設定的臨時佔位符(placeholder),可以對它釋出回退(與回退整個事務處理不同)。

可以回退哪些語句:事務處理用來管理 INSERT、UPDATE 和 DELETE 語句。不能回退 SELECT 語句(回退 SELECT 語句也沒有必要),也不能回退 CREATE 或 DROP 操作。

控制事務處理

管理事務的關鍵在於將 SQL 語句組分解為邏輯塊,並明確規定資料何時應該回退,何時不應該回退。

MySQL 中的標識:

START TRANSACTION
...
複製程式碼

事務一直存在,直到被中斷。通常,COMMIT 用於儲存更改,ROLLBACK 用於撤銷,詳述如下。

使用 ROLLBACK

DELETE FROM Orders;
ROLLBACK;
複製程式碼

在此例子中,執行 DELETE 操作,然後用 ROLLBACK 語句撤銷。雖然這不是最有用的例子,但它的確能夠說明,在事務處理塊中,DELETE 操作(與 INSERT 和 UPDATE 操作一樣)並不是最終的結果。

使用 COMMIT

一般的 SQL 語句都是針對資料庫表直接執行和編寫的。這就是所謂的隱式提交(implicit commit),即提交(寫或儲存)操作是自動進行的。

在事務處理塊中,提交不會隱式進行。不過,不同 DBMS 的做法有所不同。有的 DBMS 按隱式提交處理事務端,有的則不這樣。

進行明確的提交,使用COMMIT語句。

Oracle 示例:

SET TRANSACTION
DELETE OrderItems WHERE order_num = 12345;
DELETE OrderItems WHERE order_num = 12345;
COMMIT;
複製程式碼

使用保留點

使用簡單的 ROLLBACK 和 COMMIT 語句,就可以寫入或撤銷整個事務。但是,只對簡單的事務才能這樣做,複雜的事務可能需要部分提交或回退。

要支援回退部分事務,必須在事務處理塊中的合適位置放置佔位符。這樣,如果需要回退,可以回退到某個佔位符。

MySQL 和 Oracle 示例:

ROLLBACK TO delete1;
複製程式碼

第21課 使用遊標

遊標

有時,需要在檢索出來的行中前進或後退一行或多行,這就是遊標的用途所在。遊標(cursor)是一個儲存在 DBMS 伺服器上的資料庫查詢,它不是一條 SELECT 語句,而是被該語句檢索出來的結果集。在儲存了遊標之後,應用程式可以根據需要滾動或瀏覽其中的資料。

不同的 DBMS 支援不同的遊標選項和特性。常見的一些選項和特性如下:

  • 能夠標記遊標為只讀,使資料能讀取,但不能更新和刪除
  • 能控制可以執行的定向操作(向前、向後、第一、最後、絕對位置、相對位置等)
  • 能標記某些列為可編輯的,某些列為不可編輯的。
  • 規定範圍,使遊標對建立它的特定請求(如儲存過程)或對所有請求可訪問。
  • 指示 DBMS 對檢索出的資料(而不是指出表中活動資料)進行復制,使資料在遊標開啟和訪問期間不變化。

使用遊標

使用遊標有幾個明確的步驟:

  • 在使用遊標前,必須宣告(定義)它。這個過程實際上沒有檢索資料,它只是定義要使用的SELECT語句和遊標選項。
  • 一旦宣告,就必須開啟遊標以供使用。這個過程用前面定義的 SELECT 語句把資料實際檢索出來。
  • 對於填有資料的遊標,根據需要取出(檢索)各行。
  • 在結束遊標使用時,必須關閉遊標,可能的話,釋放遊標(有賴於具體的DBMS)。

建立遊標

使用 DECLARE 語句建立遊標,這條語句在不同的 DBMS 中有所不同。DECLARE 命名遊標,並定義相應的 SELECT 語句,根據需要帶 WHERE 和其他子句。

DECLARE CustCursor CURSOR
FOR
SELECT * FROM Customers
WHERE cust_email IS NULL;
複製程式碼

使用遊標

OPEN CURSOR CustCursor;
複製程式碼

現在可以用 FETCH 語句訪問遊標資料了。FETCH 指出要檢索哪些行,從何處檢索它們以及將它們放於何處(如變數名)。

使用 Oracle 語法從遊標中檢索一行:

DECLARE TYPE CustCursor IS REF CURSOR RETURN Customers%ROWTYPE
DECLARE CustRecord Customers%ROWTYPE;
BEGIN
	OPEN CustCursor;
	FETCH CustCursor INTO CustRecord;
	CLOSE CustCursor;
END;
複製程式碼

關閉遊標

CLOSE 語句用來關閉遊標。一旦遊標關閉,如果不再次開啟,將不能使 用。第二次使用它時不需要再宣告,只需用 OPEN 開啟它即可。

CLOSE CustCursor
複製程式碼

第22課 高階SQL特性

約束

約束(constraint):管理如何插入或處理資料庫資料的規則。

主鍵

主鍵是一種特殊的約束,用來 一組列)中的值是唯一的,而且永不改動。換句話說,表中的一列(或 多個列)的值唯一標識表中的每一行。這方便了直接或互動地處理表中的行。沒有主鍵,要安全地 UPDATE 或 DELETE 特定行而不影響其他行會 非常困難。

表中任意列只要滿足以下條件,都可以用於主鍵。

  • 任意兩行的主鍵值都不相同。
  • 每行都具有一個主鍵值(即列中不允許 NULL 值)。
  • 包含主鍵值的列從不修改或更新。(大多數 DBMS 不允許這麼做,但 如果你使用的 DBMS 允許這樣做,好吧,千萬別!)
  • 主鍵值不能重用。如果從表中刪除某一行,其主鍵值不分配給新行。

一種定義主鍵的方法是建立它:

CREATE TABLE Vendors
(
	vend_id			CHAR(10)		NOT NULL PRIMARY KEY,
	vend_name		CHAR(50)	NOT NULL,
	vend_address		CHAR(50)	NULL,
	vend_city			CHAR(50)	NULL,
	vend_state		CHAR(5)		NULL,
	vend_zip			CHAR(10)		NULL,
	vend_country		CHAR(50)	NULL
);
複製程式碼

另一種方法:

ALTER TABLE Vendors
ADD CONSTRAINT PRIMARY KEY (vend_id);
複製程式碼

外來鍵

外來鍵是表中的一列,其值必須列在另一表的主鍵中。外來鍵是保證引用完 整性的極其重要部分。

定義外來鍵的方法:

CREATE TABLE Orders
(
	order_num	INTEGER		NOT NULL PRIMARY KEY,
	order_date	DATETIME	NOT NULL,
	cust_id		CHAR(10)		NOT NULL REFERENCES Customers(cust_id)
);
複製程式碼

也可以用 CONSTRAINT 來完成:

ALTER TABLE Orders
ADD CONSTRAINT
FOREIGN KEY (cust_id) REFERENCES Customers (cust_id)
複製程式碼

外來鍵有助防止意外刪除:除幫助保證引用完整性外,外來鍵還有另一個重要作用。在定義外來鍵後,DBMS 不允許刪除在另一個表中具有關聯行的行。

唯一約束

唯一約束用來保證一列(或一組列)中的資料是唯一的。它們類似於主 鍵,但存在以下重要區別。

  • 表可包含多個唯一約束,但每個表只允許一個主鍵。
  • 唯一約束列可包含 NULL 值。
  • 唯一約束列可修改或更新。
  • 唯一約束列的值可重複使用。
  • 與主鍵不一樣,唯一約束不能用來定義外來鍵。

唯一約束的語法類似於其他約束的語法。唯一約束既可以用 UNIQUE 關 鍵字在表定義中定義,也可以用單獨的 CONSTRAINT 定義。

檢查約束

檢查約束用來保證一列(或一組列)中的資料滿足一組指定的條件。檢 查約束的常見用途有以下幾點:

  • 檢查最小或最大值。例如,防止 0 個物品的訂單(即使 0 是合法的數)。
  • 指定範圍。例如,保證發貨日期大於等於今天的日期,但不超過今天 起一年後的日期。
  • 只允許特定的值。例如,在性別欄位中只允許 M 或 F 。

檢查 約束在資料型別內又做了進一步的限制,這些限制極其重要,可以確保插 入資料庫的資料正是你想要的資料。不需要依賴於客戶端應用程式或使用者 來保證正確獲取它,DBMS 本身將會拒絕任何無效的資料。

施加檢查約束:

CREATE TABLE OrderItems
(
	order_num	INTEGER		NOT NULL,
	order_item	INTEGER		NOT NULL,
	prod_id		CHAR(10)		NOT NULL,
	quantity		INTEGER		NOT NULL CHECK (quantity > 0),
	item_price	MONEY		NOT NULL
);
複製程式碼

檢查名為 gender 的列只包含 M 或 F,可編寫如下的 ALTER TABLE 語句:

ADD CONSTRAINT CHECK (gender LIKE '[MF]')
複製程式碼

索引

索引用來排序資料以加快搜尋和排序操作的速度。想像一本書後的索引 (如本書後的索引),可以幫助你理解資料庫的索引。

假如要找出本書中所有的“資料型別”這個詞,簡單的辦法是從第 1 頁 開始,瀏覽每一行。雖然這樣做可以完成任務,但顯然不是一種好的辦法。瀏覽少數幾頁文字可能還行,但以這種方式瀏覽整部書就不可行了。 隨著要搜尋的頁數不斷增加,找出所需詞彙的時間也會增加。

這就是書籍要有索引的原因。索引按字母順序列出詞彙及其在書中的位 置。為了搜尋“資料型別”一詞,可在索引中找出該詞,確定它出現在 哪些頁中。然後再翻到這些頁,找出“資料型別”一詞。

使索引有用的因素是什麼?很簡單,就是恰當的排序。找出書中詞彙的 困難不在於必須進行多少搜尋,而在於書的內容沒有按詞彙排序。如果 書的內容像字典一樣排序,則索引沒有必要(因此字典就沒有索引)。

資料庫索引的作用也一樣。主鍵資料總是排序的,這是 DBMS 的工作。 因此,按主鍵檢索特定行總是一種快速有效的操作。

但是,搜尋其他列中的值通常效率不高。例如,如果想搜尋住在某個州的客戶,怎麼辦?因為表資料並未按州排序,DBMS 必須讀出表中所有行(從第一行開始),看其是否匹配。這就像要從沒有索引的書中找出詞彙一樣。

解決方法是使用索引。可以在一個或多個列上定義索引,使 DBMS 儲存 其內容的一個排過序的列表。在定義了索引後,DBMS 以使用書的索引類似的方法使用它。DBMS 搜尋排過序的索引,找出匹配的位置,然後檢索這些行。

在開始建立索引前,應該記住以下內容:

  • 索引改善檢索操作的效能,但降低了資料插入、修改和刪除的效能。 在執行這些操作時, DBMS 必須動態地更新索引。
  • 索引資料可能要佔用大量的儲存空間。
  • 並非所有資料都適合做索引。取值不多的資料(如州)不如具有更多可能值的資料(如姓或名),能通過索引得到那麼多的好處。
  • 索引用於資料過濾和資料排序。如果你經常以某種特定的順序排序資料,則該資料可能適合做索引。
  • 可以在索引中定義多個列(例如,州加上城市)。這樣的索引僅在以州加城市的順序排序時有用。如果想按城市排序,則這種索引沒有用處。

沒有嚴格的規則要求什麼應該索引,何時索引。大多數 DBMS 提供了可 用來確定索引效率的實用程式,應該經常使用這些實用程式。

索引用 CREATE INDEX 語句建立:

CREATE INDEX prod_name_ind
ON Products (prod_name);
複製程式碼

索引必須唯一命名。

觸發器

觸發器是特殊的儲存過程,它在特定的資料庫活動發生時自動執行。觸發 器可以與特定表上的 INSERT、UPDATE 和 DELETE 操作(或組合)相關聯。

與儲存過程不一樣(儲存過程只是簡單的儲存 SQL 語句),觸發器與單個的表相關聯。與 Orders 表上的 INSERT 操作相關聯的觸發器只在 Orders 表中插入行時執行。 類似地, Customers 表上的 INSERT 和 UPDATE 操作的觸發器只在 Customers 表上出現這些操作時執行。

觸發器內的程式碼具有以下資料的訪問權:

  • INSERT 操作中的所有新資料;
  • UPDATE 操作中的所有新資料和舊資料;
  • DELETE 操作中刪除的資料。

下面是觸發器的一些常見用途:

  • 保證資料一致。例如,在 INSERT 或 UPDATE 操作中將所有州名轉換 為大寫。
  • 基於某個表的變動在其他表上執行活動。例如,每當更新或刪除一行時將審計跟蹤記錄寫入某個日誌表。
  • 進行額外的驗證並根據需要回退資料。例如,保證某個顧客的可用資金不超限定,如果已經超出,則阻塞插入。
  • 計算計算列的值或更新時間戳。

不同 DBMS 的觸發器建立語法差異很大,更詳細的資訊請參閱相應的文件。

Oracle 版本:

CREATE TRIGGER customer_state
AFTER INSERT OR UPDATE
FOR EACH ROW
BEGIN
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERE Customers.cust_id = :OLD.cust_id
END;
複製程式碼

資料庫安全

對於組織來說,沒有什麼比它的資料更重要了,因此應該保護這些資料, 使其不被偷盜或任意瀏覽。當然,資料也必須允許需要訪問它的使用者訪 問,因此大多數 DBMS 都給管理員提供了管理機制,利用管理機制授予 或限制對資料的訪問。

任何安全系統的基礎都是使用者授權和身份確認。這是一種處理,通過這 種處理對使用者進行確認,保證他是有權使用者,允許執行他要執行的操作。 有的 DBMS 為此結合使用了作業系統的安全措施,而有的維護自己的使用者及密碼列表,還有一些結合使用外部目錄服務伺服器。

一般說來,需要保護的操作有:

  • 對資料庫管理功能(建立表、更改或刪除已存在的表等)的訪問;
  • 對特定資料庫或表的訪問;
  • 訪問的型別(只讀、對特定列的訪問等);
  • 僅通過檢視或儲存過程對錶進行訪問;
  • 建立多層次的安全措施,從而允許多種基於登入的訪問和控制;
  • 限制管理使用者賬號的能力。

相關文章