SQL必知必會筆記(下)

第14課 組合查詢

組合查詢

多數SQL查詢只包含從一個或多個表中返回數據的單條 SELECT 語句。可是,SQL 也容許執行多個查詢(多條 SELECT 語句),並將結果做爲一個查詢結果集返回。這些組合查詢一般稱爲並(union)或複合查詢(compound query)。程序員

主要有兩種狀況須要使用組合查詢:sql

  • 在一個查詢中從不一樣的表返回結構數據
  • 對一個表執行多個查詢,按一個查詢返回數據

建立組合查詢

能夠用 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 支持不一樣的遊標選項和特性。常見的一些選項和特性以下:

  • 可以標記遊標爲只讀,使數據能讀取,但不能更新和刪除
  • 能控制能夠執行的定向操做(向前、向後、第1、最後、絕對位置、相對位置等)
  • 能標記某些列爲可編輯的,某些列爲不可編輯的。
  • 規定範圍,使遊標對建立它的特定請求(如存儲過程)或對全部請求可訪問。
  • 指示 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 爲此結合使用了操做系統的安全措施,而有的維護本身的用戶及密碼列表,還有一些結合使用外部目錄服務服務器。

通常說來,須要保護的操做有:

  • 對數據庫管理功能(建立表、更改或刪除已存在的表等)的訪問;
  • 對特定數據庫或表的訪問;
  • 訪問的類型(只讀、對特定列的訪問等);
  • 僅經過視圖或存儲過程對錶進行訪問;
  • 建立多層次的安全措施,從而容許多種基於登陸的訪問和控制;
  • 限制管理用戶帳號的能力。
相關文章
相關標籤/搜索