如何學習SQL?---來自CSDN上面的問答區

我覺得這篇講解SQL的文章很精闢,自己就收下了,方便以後自己回頭看看,基礎知識講解得很到位,贊贊贊奮鬥奮鬥奮鬥


目錄

Table of Content


第一部分:SQL基礎
1. 爲什麼學習SQL
2. 學習SQL的參考資料
3. 幾組基本概念
3.1. 單機數據庫與服務器級數據庫
3.2. 服務器(Server)與客戶端(Client)
3.3. 數據庫(DB)與數據庫管理系統(DBMS)
3.4. SQL與SQL方言(dialect)
3.5. 語句、表達式和斷言
4. SQL不同於一般編程語言的地方
4.1. SQL操作的是數據
4.2. SQL是基於集合的說明式語言
5. SQL的三個子集

第二部分:從關係角度理解SQL
6. 從關係角度理解SQL
6.1. 關係和表
6.2. 關係模型
6.3. 關係運算
6.4. 數據查詢
6.5. 數據修改
6.6. 表的邏輯含義

第三部分:SQL數據類型與三值邏輯
7. 數據類型
8. NULL與三值邏輯

第四部分:DBMS擴展功能與SQL高級話題
9. DBMS提供的擴展功能
9.1. 控制流
9.2. 動態語句
9.3. DBMS支持的數據庫對象
9.4. DBMS提供的系統函數、系統視圖和系統存儲過程
9.5. DBMS提供的工具

10. 高級話題


第一部分:SQL基礎


1. 爲什麼學習SQL

自人類社會形成之日起,社會的運轉就在不斷地產生和使用各種信息(文獻、檔案、資料、數據等);在如今所謂的信息時代,由於計算機和互聯網的作用,信息的產生和使用達到前所未有的廣度和深度。如何管好和用好信息,是(而且將一直是)IT行業一塊重要的領域。
在過去幾十年中,關係數據庫一直在這一領域占主導地位,而建立在關係理論基礎之上的SQL也成爲數據庫領域的既定標準。

目前的數據存儲領域可稱爲三分天下:
a. 少量數據的存儲:
自定義數據文件或通用數據文件(單機數據庫),通過自定義接口或通用API訪問數據。如需要存儲數據的單機軟件或小型的動態網站。
b. 對一致性要求高的大量數據的存儲:
關係數據庫管理系統(RDBMS)。如各種傳統的信息系統(ERP、CRM、HRMS、MIS等)。
c. 對併發性要求高的大量數據的存儲:
NoSQL數據庫系統。如Web2.0網站的後臺數據系統。

以上,“狐假虎威”地借數據存儲的重要性來闡述了一番學習SQL的偉大意義。但重要的是,對SQL善於做什麼和不善於做什麼有個清楚的認識。


2. 學習SQL的參考資料

不久前整理了數據庫圖書ABC一文,對數據庫相關的參考資料作了粗略的分類。

對於初學者而言,可以結合着《數據庫系統概念(第5版)》一書和某個DBMS平臺的入門技術手冊練習,自行尋找或構思一個小需求,建一個數據庫,創建幾個表和視圖,練習寫查詢和修改語句。基礎理論和技術實踐可以相互促進。


3. 幾組基本概念

3.1. 單機數據庫與服務器級數據庫


單機數據庫(如sqlite、Access等,Excel也勉強可以算是)是應用於單個計算機的數據庫引擎,通常不具備網絡連接功能,適用於小型應用;程序部署時,一般只需要附帶數據文件即可。有時也稱作桌面數據庫。
服務器級數據庫(如Oracle、DB2、SQL Server、MySQL、PostgreSQL等)是具備網絡連接功能、可作爲單獨數據庫服務器的DBMS,適用於大型信息系統;程序部署時,需要專門安裝相應的DBMS,甚至要單獨進行數據庫服務器的架構設計。此類數據庫是我們討論的重點。

3.2. 服務器(Server)與客戶端(Client)

數據庫服務器是運行在一臺主機(Host)(或主機集羣)上的服務程序,維護着一個或多個數據庫,並通過網絡連接響應數據庫客戶端提交的SQL語句。
數據庫客戶端是向數據庫服務器發送查詢請求的應用程序,可能是DBMS的GUI管理界面或命令行應用程序,也可能是前端的Web服務器。數據庫客戶端和數據庫服務器可能是在同一臺主機上,但更多情況下則是位於不同的主機上,通過局域網訪問。

例如對於SQL Server來說,一個服務器實例(Instance)即是一個數據庫服務器,一臺主機上可以安裝多個服務器實例;而查詢分析器或SSMS、sqlcmd、以及連接數據庫服務器的IIS,都是數據庫客戶端。

比如你在SSMS中備份/還原/附加一個數據庫,或是通過xp_cmdshell執行一個命令程序,所操作的都是你所連接的數據庫服務器所在主機的文件,而不是你運行SSMS數據庫客戶端所在主機的文件。

一臺主機有時候會被稱作一臺(操作系統)服務器,而數據庫服務器和Web服務器都是運行在主機之上的應用服務器。它們都被稱作服務器,不要因此搞混了。

一個典型的基於SQL Server的網站系統的架構示例如下:
用戶瀏覽器(Web Client) <----> IIS(Web Server/DB Client) <----> SQL Server(DB Server)

3.3. 數據庫(DB)與數據庫管理系統(DBMS)

“數據庫”這個詞已經被濫用,可能用來指一個數據系統(如中國移動的號碼數據庫),可能用來指一種數據存儲技術(如關係數據庫和NoSQL數據庫),還可能用來指DBMS(如人們常說SQL Server是數據庫軟件)。這種混亂已然形成,恐怕難以改變(比如上文的描述即是如此)。我們只能根據上下文來判斷具體含義。
按最狹義的技術含義,數據庫(Database)是指位於一個數據庫服務器實例上的一個庫,而DBMS則是指類似SQL Server、Oracle等等此類軟件。初學者要注意這些概念之間的差別。論壇上常常見到這樣的帖子:“連不上數據庫”、“數據庫打不開了”,又沒有上下文,可見發問者概念混亂,搞得解答者也是一頭霧水。

3.4. SQL與SQL方言(dialect)

SQL是一個關係數據庫查詢語言的標準,而SQL方言則是各種DBMS在SQL標準上進行的擴展,如增加新的關鍵字、查詢功能、特有的數據類型、支持過程化的控制流語句等。例如SQL Server的T-SQL和Oracle的PL/SQL都是常見的SQL方言。
這就好比ANSI C標準與各種編譯器實現的C語言的差別。但不同SQL方言之間的差異遠大於不同C編譯器之間的差異。SQL方言之間的差異,對於跨DBMS的學習和開發,都是必須注意的。

3.5. 語句、表達式和斷言

語句(statement)是SQL中一個可以單獨執行的單元。如SELECT * FROM table;即是一個語句,其中包含了SELECT子句(clause)和FROM子句。SQL標準規定用分號作爲語句的結束,但在目前的T-SQL中,語句結束的分號是可選的。

表達式(expression)是SQL中的一個值(可能是變量、常量、查詢字段或計算結果),對應一種特定的數據類型。SQL中的表達式分爲標量表達式和表值表達式,其中表值表達式作爲單獨語句則是SELECT語句,作爲語句的一部分則稱爲子查詢。比如0, col + 2, DATEADD(second, 30, GETDATE())都是(標量)表達式。

需要特別說明的是,SQL中的CASE WHEN是標題表達式,而不是條件語句。比如CASE WHEN中可以使用表達式,卻不能使用語句;CASE WHEN的結果是一個特定數據類型的標量值;CASE WHEN可以用在SELECT、GROUP BY或ORDER BY子句中,但IF ELSE則不行。

斷言(predicate)是SQL中進行比較的結果,即真值,可理解爲布爾表達式,因爲SQL中沒有bool數據類型,所以將斷言特別從表達式中區分出來。比如1是一個標量表達式,而1 = 1則是一個斷言,後者可以用在WHERE、ON、HAVING、CHECK等需要真值條件的地方,但前者則不可以。由於NULL的存在,SQL中的斷言是三值邏輯,即True/False/Unknown,詳見下文“NULL與三值邏輯”。

下文中多次用到計算和比較兩個詞。表達式和表達式進行計算,結果是新的表達式;表達式和表達式進行比較,結果是一個斷言;斷言和斷言可以進行邏輯運行(AND/OR/NOT),結果是新的斷言。注意其中的區別。


4. SQL不同於一般編程語言的地方

4.1. SQL操作的是數據


SQL是數據庫的查詢語言,因而可以對系統數據產生持久化影響。在常規編程中,一個錯誤通常只會造成程序的crash或bug,修改並重新調試往往就可以了;而在SQL中,一個不小心就可能造成系統數據的破壞和丟失。常常有新手執行SQL時,不小心遺漏了DELETE或UPDATE語句中的WHERE子句,這往往是很大的麻煩。

因此,學習和使用SQL,一開始應該養成兩個習慣:
1. 細心。在執行SQL語句認真檢查一下,要清楚自己在做什麼。
2. 及時備份,並考慮對系統的元數據進行版本控制。爲偶爾的意外準備好後悔藥。

常見的可能造成破壞性影響的SQL關鍵詞:DELETE, UPDATE, DROP, TRUNCATE TABLE。

4.2. SQL是基於集合的說明式語言

SQL擅長集合操作,而不是循環。所謂說明式語言,你只需要告訴SQL需要做什麼,而不是怎麼做。

初學SQL的人,思維往往難免帶有過程式語言的痕跡,解決問題時常常不由地考慮循環。在學習SQL編程時,每當想要用循環時,先反問一下自己:這個問題是不是必須用循環來解決?事實上,多數情況下,這個問題的答案是否定的。

有本書中講,SQL代碼中出現一次IF便要減一些分(比如-1),出現一次WHILE便要減很多分(比如-10或-50,不誇張)。可以嘗試用這樣的方法爲自己的存儲過程打分。


5. SQL的三個子集

SQL從功能上可以劃分爲三個子集:

1. DML(Data Manipulation Language):
數據操縱語言,是對數據進行查詢和修改(增、刪、改)操作的語言。
包含語句:SELECT+INSERT/DELETE/UPDATE
使用對象:DB User

2. DDL(Data Definition Language):
數據定義語言,是對域(數據類型)和關係(表)及其它數據庫對象進行定義的語言。
包含語句:CREATE/DROP/ALTER
使用對象:DB Designer/Developer

3. DCL(Data Control Language):
數據控制語言:是對數據的訪問進行權限控制的語言。
包含語句:GRANT/DENY/REVOKE
使用對象:DBA

補充說明:
- 嚴格地說,DML只包含對數據進行修改的語句(INSERT/DELETE/UPDATE),但SELECT語句與DML關係緊密、形式類似,故通常放在一起。既可以統稱爲DML,也可以並稱爲Query+DML。
- 以上三個子集不包含BACKUP/RESTORE語句。


第二部分:從關係角度理解SQL


6. 從關係角度理解SQL

6.1. 關係和表


衆所周知,我們目前所用的數據庫,通常都是關係數據庫。關係自然在其中處於關鍵位置。初學數據庫原理的人可能會很困惑關係和表是什麼聯繫,如果沒有清楚的理解,很可能會認爲關係這個概念沒有實際意義,只會引起混淆。
其實這兩組概念只是由於理論界與技術界的着重點不同。前者需要用一個專業的、沒有歧義的概念來進行理論探討,後者則希望在實際應用中能夠使用一個直觀的、容易理解的詞彙。通常情況下,可以認爲關係和表是一回事。

就定義來說:關係是元組(即表的記錄,或行)的集合。此外,關係還有以下特徵:
- 關係含有一組屬性(即表的字段,或列),含有N個屬性的關係可稱爲N元關係。
- 一個關係的元組含有與關係相同的屬性,N元關係的元組都是N元組,一個元組中對應每個屬性有一個值。
- 一個屬性的域(即字段的數據類型,但域的要求更嚴格,詳見下文“數據類型”),即該屬性所有可能的值的集合。

從這裏可以看出關係和表的區別:關係作爲一種集合,不會包含重複元組;而表則可以包含重複記錄。這是SQL面對的諸多指責之一,但有其技術合理性。這裏的區別在理解上影響不大,不妨把表理解爲“可能(但通常不應該)重複的集合”。注意到這點區別,以下我們便可以對關係和表不加區別的使用了。

另外,這裏的關係和表,指的是所有表值的東西,包含物理表、虛擬表(視圖)、派生表(一個用在FROM子句的子查詢)、表變量、表值函數、等等。它們在物理上有區別,但在邏輯上是等價的。

6.2. 關係模型

數據庫建模(即表結構設計)的過程,是根據現實世界的業務需求,設計一個表示和存儲業務數據的關係數據模型。在設計過程中可以藉助E-R模型來簡化問題,因爲E-R模型可以更直觀地對應於現實世界,也可以很容易地轉化爲關係模型。對於熟練的設計者,可以省略E-R模型,直接構建關係模型。

而關係模型在關係數據庫中基本上可以直接表示,所以關係模型與物理模型差別不大。物理模型通常只是根據需要添加必要的索引,或是將概念上的表在物理上映射爲分區視圖或分區表。

以上幾個模型的關係見下圖:

簡單總結一下關係模型設計中的兩個要點:

1. 完整性約束(Integrity constraint)

完整性約束保證數據的一致性(符合基本條件),包含以下3種類型:
- 實體完整性(主鍵約束):一個表的主鍵不能爲空。
- 參照完整性(外鍵約束):一個表的外鍵必須存在於所參照的表中。
- 自定義完整性(CHECK約束,UNIQUE約束):即表中的數據不能違反約束定義的條件(不能使CHECK的表達式爲False,不能使UNIQUE約束的字段或字段組合出現重複值)。

完整性約束定義了系統概念模型的邊界,很大程度上防止了髒數據進入系統,這是非常重要的,因爲髒數據往往比沒有數據還要討厭(這與“錯誤的觀點勝過沒有觀點”恰恰相反)。

在設計表結構時,外鍵、CHECK、UNIQUE約束或許可以適當省略(出於運行性能和開發效率的考慮,並且相信表數據只有統一存儲過程修改,不會出現髒數據),但主鍵通常是一定需要的。主鍵不僅意味着可以高效查詢(因爲目前DBMS的主鍵通常都是通過B+樹聚集索引實現的),更重要的是清楚地說明了表中數據的唯一標識是什麼。(目前我只發現一種不需要主鍵的情況:日誌表——同一時刻可能有多筆記錄,所以datetime不能作爲主鍵;而一個遞增的LogID也沒有太大實際意義,參看關於聚集索引選擇方案的疑問一帖。)

關於主鍵的選擇方案,詳見一個基礎問題一帖。

給我看錶的數據樣本,以及(可能)過時的數據字典和程序文檔,我仍然迷惑不解。如果給我看完整的表的定義(要包括各種完整性約束,特別是主鍵),通常就不需要查看錶中的數據樣本了,甚至連文檔也可以省去。(這兩句話借鑑了Brooks在《人月神話》一書中的話。參見《UNIX編程藝術》1.6節腳註。)

2. 範式(Normal Form, NF)

範式是一組關係(表)設計的原則,通過避免冗餘防止出現數據的更新異常(即DRY原則的體現)。在實踐上常用的是以下3個層次的範式:
- 1NF:表中的字段都是原子的。
- 2NF:表中的所有字段都可以由主鍵唯一決定(函數依賴)。
- 3NF:除完整主鍵以外,其它字段(包括部分主鍵)之間不存在決定關係(函數依賴)。

首先說明一下1NF的“原子的”。這個“原子的”是指業務需求不需要對這個值進行拆分(沒有前提條件,“拆分”一詞是有多種解釋的,如字符串可以拆分成字符,整數可以拆分成二進制的位串或素因子的乘積)。例如,城區、街道、門牌號是地址的三部分,如果地址只是作爲一個記錄,不需要更細粒度的處理,則可以將三部分存在一個字段;如果需要根據城區進行查詢和分組統計,則至少需要把城區作爲一個單獨的字段。所以,一個字段是不是“原子的”必須根據業務需求這個條件來定義。實踐中業務需求是會變化的,因而系統設計還需要一定的前瞻性。目前一個原子的字段可能隨着需求變化而不再是原子的。

範式給出了一組表應該怎樣設計的原則,但沒有說明如何把表設計成這樣。數據庫理論上的關係範式分解過於抽象,以下是一點實用性的思路:
1NF:讓表中的每個字段都不需要拆分處理(至少不需要太複雜的拆分處理)。如姓名的結構很簡單,通常不需要設計成姓和名兩個字段,但如果是一個國際化的系統,不同文化中姓名的結構可以不同,這時則最好把LastName和FirstName分開存放,比如Facebook、Twitter等網站的設計。
2NF:給表定義主鍵。參看上文關於實體完整性的討論。
3NF:不要在同一個表中存放相關數據或派生數據,只存放主要數據,其它數據通過聯接查詢或計算獲得。如不要在員工表中同時存放部門ID和部門名稱(相關數據)或同時存放出生年月和年齡(派生數據),其中部門ID或出生年月是主要數據,部門名稱可以通過聯接查詢獲得,年齡可以通過計算獲得。

有些情況下出於結構的直觀或查詢性能的考慮,可能會需要反範式的設計。如在一個字符串字段中存放逗號分隔的多個值(形如'1,2,3,5,8',違反1NF),或是在一個表中同時存放相關數據或派生數據來避免聯接或計算開銷(比如同時存放部門ID和部門信息來避免聯接部門表,或同時存放員工各項薪酬福利和總薪資來避免複雜的薪資計算,違反3NF)。反範式的設計會帶來複雜的查詢處理或冗餘,更好的方案是基本數據用符合範式的表存儲,通過統一的過程來計算和刷新緩衝表來提高查詢時的性能,參看《程序員修煉之道》第7節關於DRY原則的討論。

6.3. 關係運算

表的查詢,與關係代數(Relational Algebra)定義的關係運算是等價的。理解關係運算,或許可以簡化對查詢的認識。

常用的基本關係運算只有4類(夠簡單吧):

1. 基本的集合運算(雙目運算)

關係是元組的集合,所以關係也支持基本的集合運算:
- 並(union):對應SQL關鍵字UNION
- 交(intersection):對應SQL關鍵字INTERSECT
- 差(difference):對應SQL關鍵字EXCEPT

不同的是,關係的集合運算,要求參與運算的兩個關係必須含有相同屬性集(屬性的個數和類型都一樣)。
由於表允許重複記錄,在SQL中以上三種操作還可以是UNION ALL/INTERSECT ALL/EXCEPT ALL的形式,結果不去除重複記錄。

2. 提取關係的一部分的運算(單目運算)

- 選擇(selection):根據條件過濾出指定的元組(行),對應SQL查詢的WHERE子句
- 投影(projection):根據列表過濾出指定的屬性(列),對應SQL查詢的SELECT子句

由於表允許重複記錄,關係的投影運算事實上等價於SELECT DISTINCT的效果。而SELECT的默認效果是不去除重複記錄。

3. 兩個關係的聯接(雙目運算)

- 笛卡爾積(Cartesian product):對應SQL關鍵字CROSS JOIN(與FROM後的多個表直接用逗號分隔效果相同)
- 內聯接(Inner Join):對應SQL關鍵字INNER JOIN
- 外聯接(Outer Join):對應SQL關鍵字{LEFT | RIGHT | FULL} OUTER JOIN

4. 聚合運算(單目運算)

根據指定屬性(列)分組,同時可以使用聚合函數。對應SQL查詢的GROUP BY子句。

以上4類關係運算,不管是單目運算還是雙目運算,其結果依然是一個關係,因而可以繼續進行運算。

通常情況下的SQL查詢,除一些特殊的語言特性外(如TOP、排序函數等),主要的查詢邏輯都是這4類關係運算的組合。

6.4. 數據查詢

1. 查詢的邏輯處理過程

以T-SQL爲例,一個查詢(完整SELECT語句)的邏輯處理過程如下(其中括號中的數字表示處理順序):

(8)    SELECT (9) DISTINCT (11) <TOP_specification> <select_list>
(1)    FROM <left_table>
(3)    <join_type> JOIN <right_table>
(2)    ON <join_condition>
(4)    WHERE <where_condition>
(5)    GROUP BY <group_by_list> (6) WITH {CUBE | ROLLUP}
(7)    HAVING <having_condition>
(10)    ORDER BY <order_by_list>

說明:
+ 有些子句是可選的。比如JOIN可能出現0到多次,GROUP BY和HAVING可能出現0到1次。
+ 從以上順序可以看出,爲何在WHERE子句不能使用SELECT的計算結果,但在ORDER BY子句卻可以。
+ 查詢的邏輯處理過程與物理處理過程可能並不相同。但對於SQL的學習來說,先理解邏輯處理過程是必須的。先要知道怎樣計算出正確的結果,才談得上怎樣更高效地計算出正確的結果。

該內容詳見《Microsoft SQL Server 2005技術內幕:T-SQL查詢》第1章。

2. 查詢條件

在SQL Server聯機叢書中,查詢條件的BNF語法圖如下:

<pre name="code" class="sql">Search Condition
< search_condition > ::=
    { [ NOT ] <predicate> | ( <search_condition> ) }
    [ { AND | OR } [ NOT ] { <predicate> | ( <search_condition> ) } ]
[ ,...n ]
<predicate> ::=
    { expression { = | < > | ! = | > | > = | ! > | < | < = | ! < } expression
    | string_expression [ NOT ] LIKE string_expression [ ESCAPE 'escape_character' ]
    | expression [ NOT ] BETWEEN expression AND expression
    | expression IS [ NOT ] NULL
    | expression [ NOT ] IN ( subquery | expression [ ,...n ] )
    | expression { = | < > | ! = | > | > = | ! > | < | < = | ! < } { ALL | SOME | ANY} ( subquery )
    | EXISTS ( subquery )     }
    | CONTAINS ( { column | * } , '< contains_search_condition >' )
    | FREETEXT ( { column | * } , 'freetext_string' )


其中:predicate爲斷言,expression爲標量表達式,subquery爲子查詢。查詢語句返回使查詢條件爲True的結果。3. 子查詢一個查詢如果作爲一個語句的一部分,則稱爲子查詢。a. 按結果分類:- scalar subquery:如果查詢結果是標量值,即只有一行一列,則爲標量子查詢(標量表達式)。- table-valued subquery:反之則是表值子查詢(表值表達式)。b. 按查詢是否涉及外層表分類:- self-contained subquery:不涉及外層表的子查詢是不相關子查詢,如SELECT a.* FROM a WHERE a.id IN (SELECT b.id FROM b)。- correlated subquery:反之則是相關子查詢,如SELECT a.* FROM a WHERE EXISTS (SELECT * FROM b WHERE b.id = a.id)。c. 按子查詢所在的位置分類:- In search_condition:在查詢條件中的子查詢,比如上文語法圖中的所有subquery。- In FROM clause:在FROM子句中的子查詢,又稱派生表,如SELECT * FROM (SELECT * FROM a) tmp,派生表一定要指定表別名。(SQL Server 2005之後支持Common Table Expressions,可視爲派生表的變形,但可以在一個查詢中多次使用,而且支持Recursive CTE這種高級功能,詳見聯機叢書。)- In SELECT clause:在SELECT子句中的子查詢,如SELECT d.DepID, ManagerName = (SELECT e.EmpName FROM Employee e WHERE e.EmpID = d.ManagerID) FROM Department d WHERE ...,這種子查詢性能較差,通常可以用JOIN代替。如果可能,儘量避免使用SELECT子句中的子查詢。6.5. 數據修改在SQL Server中,修改數據(增、刪、改)的語句支持以下格式:1. 增(INSERT)

INSERT INTO <table>(<column_list>) VALUES(<values>)
INSERT INTO <table>(<column_list>) SELECT <values> FROM ...
INSERT INTO <table>(<column_list>) EXEC <usp>
SELECT <values> INTO <table> FROM ...

以上4個語句:
第1個是SQL標準的插入語句(SQL Server 2008還支持在VALUES子句指定多個元組);
第2個和第3個是T-SQL擴展的插入語句,但要求SELECT語句和EXEC存儲過程的結果集與目標表的指定插入列字段個數一致且數據類型一一對應(或可以隱式轉換);
第4個不是插入語句,而是根據SELECT語句的結果集創建一個表並將結果數據插入其中,注意與第2個語句的區別。

2. 刪(DELETE)

DELETE FROM <table> [WHERE <where_condition>] !!!
DELETE FROM <table> FROM <table> JOIN <another_table> ON <join_condition> WHERE <where_condition>
TRUNCATE TABLE <table>

以上3個語句:
第1個是SQL標準的刪除語句(WHERE子句省略的結果是刪除全部數據,注意!);
第2個是T-SQL擴展的刪除語句,效果是將符合聯接查詢條件的目標表數據刪除(將DELETE FROM <table>改爲SELECT DISTINCT <table>.*可以看到刪除哪些數據);
第3個實際上是DDL而不是DML,需要的權限和運行條件都與DELETE不同,但效果卻是清除表中所有數據,而且比DELETE高效。

3. 改(UPDATE)

UPDATE <table> SET <col> = <new_value> [WHERE <where_condition>] !!!
UPDATE <table> SET <col> = <new_value> FROM <table> JOIN <another_table> ON <join_condition> WHERE <where_condition>

以上2個語句:
第1個是SQL標準的更新語句(WHERE子句省略的結果是更新全部數據,注意!);
第2個是T-SQL擴展的更新語句,效果是將符合聯接查詢條件的目標表數據更新爲指定結果(將UPDATE <table> SET <col> = <new_value>改爲SELECT <table>.<col>, <new_value>可以看到把哪些數據更新爲哪些新值,其中<new_value>可以是聯接查詢的計算值,但如果聯接查詢結果使得目標表的<col>和新值<new_value>成爲一對多的關係,則<col>會更新爲哪個<new_value>是不確定的,這種情況可能導致意想不到的bug)。

該內容詳見《Microsoft SQL Server 2005技術內幕:T-SQL查詢》第8章。

6.6. 表的邏輯含義

很多使用數據庫的人,不瞭解數據庫原理,不能從邏輯上理解表的含義,從而只能把表看作一種數據結構,看作一種類似二維數組的東西,於是寫出低效的循環語句就不難理解了,數據一致性更是難以保證。

可以從以下兩個層面來理解表:

1. 一個表是一類事物(物件object和事實fact的統稱)的集合,其中表的每行記錄表示一個該類事物,主鍵是一個事物的唯一標識。
如:學生表(#學號,姓名,專業,……)是學生(物件)的集合,一個學號可以唯一標識一個學生;學生選課表(#學號,#課程ID,選課時間)是學生選課(事實)的集合,聯合主鍵學號和課程ID可以唯一標識某個學生選了某門課的事實。

數據庫建模就是根據業務需求設計一組表,用來表示業務系統中的所有事物。

2. 一個表的表結構定義了一個謂詞,表中的每行記錄都是對該謂詞的一個真值量化。由於量化後的謂詞是一個命題,所以,一個表是一組真命題的集合
(關於謂詞和量化的概念,可參看《離散數學及其應用(第5版)》一書中關於數據邏輯的部分。)
如:學生選課表(#學號,#課程ID,選課時間)定義了這樣一個謂詞——“學生{#學號},在{選課時間},選了課程{#課程ID}。”,其中{}中的部分是一個變量。謂詞不是命題,只有對其量化(或稱實例化)之後纔是命題。該表的一行記錄('S001','C0001','2010-08-24 17:16:58')表示一個真值量化,量化後的命題是“學生S001,在2010-08-24 17:16:58,選了課程C0001。”。

一個數據庫系統包含了很多表,每個表是一組真命題的集合,所有這些真命題則表示了系統中可信的知識。
設計一個表,就是設計一個謂詞。只要表結構文檔把這個謂詞的含義說明清楚了,則表中記錄的含義也自然清楚了;反之,如果一個表的謂詞存在模糊或歧義,則表中的記錄也是沒有意義的。

從這個角度理解完整性約束:
- 實體完整性(主鍵約束):如果一個表包含了完全相同的兩條記錄,則把一個真命題重複一遍並不能增加知識;如果一個表的兩條不同記錄有着相同主鍵,則說明這兩個命題是衝突的。實體完整性保證了每個命題的唯一和無衝突。
- 參照完整性(外鍵約束):參照完整性保證了每個命題涉及的事物都是有意義的(在該事物的表中有定義)。
- 域完整性(CHECK約束):域完整性保證每個命題都是一致的(不違反CHECK約束)。“每個命題都是一致的”是“每個命題都是正確的”的必要非充分條件,所以約束只是一種最小保證。


第三部分:SQL數據類型與三值邏輯


7. 數據類型

在數據庫理論中,關係模型和數據類型這兩部分內容是正交的(參看《程序員修煉之道》第8節關於“正交性”的討論),互不依賴。換言之,關係模型並不關心每個表的字段的數據類型是什麼,是整數、字符串等基本類型也好,是組合類型、類等自定義類型也好,關係模型只要求每個字段是原子的。

在數據庫理論中,數據類型又被稱爲域,但域是更爲嚴格的定義。比如一個班級的學生個數和學生平均分可能都是int類型,但這是兩個不同的域,“學生個數 * 學生平均分 = 學生總分”,但“學生個數 + 學生平均分”是沒有意義的。目前的主流DBMS似乎尚未對域有很好的支持,但未來的情況可能會有所改變,而且,設計自定義類型也需要對這一問題有充分的認識。詳見《深度探索關係數據庫》第2章。

對於數據庫和SQL的應用來說,除掌握關係模型的原理,還需要對DBMS支持的數據類型及其轉換規則有所認識。

1. 基本數據類型

一個DBMS通常都會支持以下幾類基本數據類型(以SQL Server爲例):
- 精確數字:整數(bigint/int/smallint/tinyint/bit),定點小數(decimal),貨幣(money/smallmoney)
- 近似數字:浮點數(float)
- 日期和時間:datetime/smalldatetime; date, time, datetime2, datetimeoffset(後4種爲SQL Server 2008的新增類型)
- 字符串和Unicode字符串:varchar/nvarchar, char/nchar(text/ntext已不再建議使用,用varchar(max)/nvarchar(max)代替)
- 二進制串(即字節流):varbinary, binary(image已不再建議使用,用varbinary(max)代替)
- 其他數據類型:具有特殊功能的類型(sql_variant, timestamp, uniqueidentifier, xml),不能用於表的特殊類型(cursor, table)

2. 關於數據類型需要注意的問題

a. 兩類特殊的數據類型
日期和時間類型的數據存儲方式和可用值範圍、相關的計算、比較、顯示(轉換爲指定格式的字符串)都比較複雜,還涉及一組日期時間函數。參看datetime類型分析一帖。
字符串類型涉及到字符編碼和排序規則,比較操作還包含LIKE匹配(未來還可能會支持正則表達式匹配),非常需要注意。參看理解字符編碼SQLServer中文處理二帖。

b. 如果對不同排序規則的兩個字符串進行計算或比較,將會根據排序規則優先順序來決定計算結果的排序規則或比較的方法。

c. 如果對不同類型的兩個值進行計算或比較,將會根據數據類型優先級進行隱式轉換。數據類型優先級基本規則如下:
- 大 > 小(>指優先級高於,下同):如bigint > int > smallint > tinyint > bit,varchar(20) > varchar(10),datetime > smalldatetime,等等。
- 可變 > 固定:如float > decimal,varchar > char,nvarchar > nchar,varbinary > binary。
- 各類型大類的優先級:datetime > float > decimal > integer > unicode string > ansi string > binary。
- 特殊數據類型的優先級和轉換規則需要特殊考慮,詳見聯機叢書。

d. 如果對不同大小的兩個值進行計算,將會根據精度、小數位數和長度的規則來產生新的類型大小。

e. 常量的數據類型(可以通過SELECT col = 常量值 INTO testdt然後查看testdt表col字段的數據類型來觀察)
- 如果不顯式指定和隱式轉換,NULL會按int類型處理。
- '', N'', 1, 0x01, 1.0, 1E0, $1分別對應varchar, nvarchar, int, varbinary, decimal, float, money類型,並且長度是存儲相應值所需要的最小長度。

f. 在軟件開發領域衆所周知:“隱式轉換是bug的源泉”。因此,有兩個建議:
使用常量時,最好使用對應類型的常量。比如,如果table.col是varchar類型,那麼WHERE table.col = 10的查詢將不能使用索引,而且當遇到col中存放有不能轉換爲數字的值時將出錯。
- 除非相應值的隱式轉換非常直觀,否則寧可用CAST()/CONVERT()指定明確的顯式轉換

以上內容中,加下劃線的粗體是聯機叢書的標題。詳細分析參看《Microsoft SQL Server 2005技術內幕:T-SQL程序設計》第1章。


8. NULL與三值邏輯

三值邏輯(3VL, Three-valued Logic)絕對是SQL修煉中的一個緊要關卡,值得特別注意,閉關靜修。待衝破這一關卡之後,SQL中的NULL與NOT NULL將別無二致。

關於SQL是否應該允許NULL,在數據庫領域已經近乎一個信仰式的爭論。E.F.Codd認爲NULL有存在的必要,但他的好友C.J.Date認爲NULL完全可以取消。最終結果是,SQL標準支持NULL。

理論上的爭論且不管。但在實踐中,一定要知道NULL的三值邏輯會帶來很多困擾的問題。
a. 不使用NULL的理由:
- NULL會引入複雜的三值邏輯。
- NULL在查詢條件、外鍵和CHECK約束、唯一約束、GROUP BY、ORDER BY中的行爲都是不一致的。
b. 使用NULL的理由:
- 當需要表示一個未知的、不確定的值時,用NULL更自然。比如一個現在職員工的離職時間、頂級員工(BOSS)的上級員工,等等。
- 外聯接通常會引入NULL,即使所有表的字段都定義爲NOT NULL。

首先,如果可能,儘量讓所有字段都聲明爲NOT NULL。除非是更適合使用NULL的場合(從業務出發)。

其次,在使用NULL時,一定要搞清楚三值邏輯和數據庫引擎對NULL的處理
(SQLServer有一個選項SET ANSI_DEFAULTS,默認爲ON,即與SQL標準一致。設爲OFF的效果詳見聯機叢書。)

1. NULL與別的值進行+-*/等計算操作(包括在大多數函數中使用NULL)後,結果是NULL(標量表達式)。NULL與別的值進行=、>、<等比較操作後,結果是Unknown(斷言)。
Unknown相關的邏輯運算:

NOT Unknown --> Unknown
Unknown AND/OR Unknown --> Unknown
Unknown OR TRUE --> TRUE
Unknown AND TRUE --> Unknown
Unknown OR FALSE --> Unknown
Unknown AND FALSE --> FALSE

具體可查三值邏輯的真值表。

2. 在where/on/having和if/case when中,只有True才使條件成立(即Unknown當作False來處理)。比如:
where column = value:表中column爲NULL的行永遠不會返回,即使value是NULL;
case value when NULL then XXX when ... end:XXX永遠不會執行,即使value是NULL;
if <Unknown> XXX else YYY end或case when <Unknown> then XXX else YYY end:這兩種情況下,YYY會執行。

3. 包含外鍵約束和Check約束的字段允許NULL(即約束只當條件爲False時出錯,Unknown是不管的)。
4. 包含唯一約束(unique index)的字段只允許一個NULL的行,再插入或更新該字段爲NULL的行會報字段重複的錯誤。
5. GROUP BY時,所有NULL被視爲一組。
6. ORDER BY時,所有NULL排在一起,但NULL排在非空值的前面(如SQL Server)還是後面(如Oracle),SQL標準未規定。
7. 聚集函數(SUM/AVG/MAX/MIN/COUNT)忽略NULL的行。
8. declare的變量,在未賦值之前爲NULL。
9. 與NULL處理相關的運算符和函數:
- IS NULL/IS NOT NULL:用這兩個運算符來判斷一個值是否爲NULL,而不是=或<>。
- ISNULL/COALESCE:取第一個非空值(注意兩個函數的數據類型轉換規則不同)。
- NULLIF(a,b):等價於CASE WHEN a = b THEN NULL ELSE a END。


第四部分:DBMS擴展功能與SQL高級話題


9. DBMS提供的擴展功能

掌握了基本的關係模型原理和DBMS的數據類型,還需要對DBMS提供的擴展功能有所瞭解,才能充分運用DBMS進行數據庫開發。

9.1. 控制流

SQL是說明式語言,但DBMS面對實際開發的需求,通常在SQL方言中都提供了過程式的擴展,包括(以T-SQL爲例):
1. 變量定義和賦值

DECLARE @var <datetype>                       --變量定義語句
SET @var = <value>                            --通過SET語句賦值
SELECT @var = MAX(column_value) FROM [table]  --通過SELECT語句賦值

2. 代碼塊
BEGIN ... END定義一個代碼塊。
對於下面的IF/ELSE和WHILE,如果忽略了BEGIN ... END代碼塊,條件和循環將只對其後的第一個語句生效。
3. 條件分支語句
IF ... ELSE ...
注意IF語句與CASE WHEN表達式的區別。
4. 循環控制語句
WHILE可以進行循環。BREAK/CONTINUE可以跳出或進行下一次循環。
5. 異常處理語句
SQL Server 2005支持TRY-CATCH語句進行異常處理,但只能處理一部分異常。詳見聯機叢書。

9.2. 動態語句

SQL動態語句的功能很強大,但是難以調試和維護(字符串拼接、無語法高亮)、效率低(難以重用執行計劃)、安全性差(SQL注入)。除非功能上必須,否則儘量避免使用動態SQL。

假如真的需要使用動態SQL,使用sp_executesql的方式優於EXEC()的方式。因爲前者有些時候可以重用執行計劃而改善性能,而且允許傳參,數據類型上更安全。

9.3. DBMS支持的數據庫對象

SQL Server還支持臨時表、視圖、存儲過程、自定義函數(標量和表值)、觸發器、遊標等數據庫對象,這是利用SQL Server進行開發必須掌握的知識。參看《Microsoft SQL Server 2005技術內幕:T-SQL程序設計》相關章節。

9.4. DBMS提供的系統函數、系統視圖和系統存儲過程

- System Functions:提供特定的表達式運算功能,如日期時間函數、字符串函數、數學函數、聚合函數等,是T-SQL編程必需的。
- System Views:包含了數據庫元數據、系統內部運行數據等,如目錄視圖(用來代替SQL Server 2000中的系統表)、信息架構視圖、動態管理視圖等。
- System Procedures:查看系統信息、修改系統配置等,如目錄存儲過程、數據庫引擎存儲過程等。

9.5. DBMS提供的工具

- 數據庫服務器配置工具:配置管理器、外圍應用配置器等
- 數據庫客戶端應用工具:SSMS、sqlcmd、bcp等
- 數據庫性能工具:SQL Server Profiler等


10. 高級話題

以下是數據庫相關的高級話題,每一塊都值得單獨討論,本帖不再詳述。

1. 高級技術專題
- 數據庫設計
- 服務器架構
- 索引和性能優化
- 事務、鎖定與併發
- 備份與還原
相關書目:
- 《SQL Server 2005數據庫服務器架構設計》
- 《Microsoft SQL Server 2005技術內幕:存儲引擎》
- 《Microsoft SQL Server 2005技術內幕:查詢、調整和優化》
- 《SQL Server 2005性能調校》

2. 開發和維護相關的管理專題
- 數據庫權限管理
- 數據庫對象的版本控制
- 數據庫開發的命名規範


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章