《SQL Server 2005 編程入門經典》學習筆記

第一章 RDBMS基礎:SQL Server數據庫構成

數據庫中的對象

數據庫管理系統中包含許多對象。對於SQL Server,它常包含以下重要的數據庫對象:

數據庫 索引 事務日誌 程序集  報表 文件集 全文本目錄 圖表 用戶自定義數據類型 視圖 角色 存儲過程 用戶 用戶自定義函數

 

SQL Server的4個系統數據庫

在給定的SQL Server中,數據庫實際上是最高層對象。在SQL Server中,大部分其他對象爲數據庫對象的子對象。安裝好的SQL Server第一次啓動時包含4個系統數據庫:

  • 主數據庫(master——主數據庫保存一組特殊的表(系統表)以用於系統的總體控制。
  • 模型數據庫(model——模型數據庫是指可以基於該模型得到一個副本。模型數據庫構成新建數據庫的模版。也就是說,如果想要改變新建數據庫的樣式,則可以根據需要更改模型數據庫。注意:由於模型數據庫作爲其他任意數據庫的模版,因此係統中必須保留該數據庫,禁止刪除它。
  • msdb——msdb數據庫是SQL代理進程保存任意系統作業的場所,如計劃對一數據庫在每夜進行備份和執行一次計劃好的存儲過程。
  • tempdb——tempdb數據庫是服務器主要工作區域之一。只要執行一個複雜或者大型的查詢操作,則SQL Server需要建立一些中間表,而建立的中間表就是在tempdb數據庫中。只要建立臨時表,則這些表會建立在tempdb數據庫中,即使您是在當前數據庫中建立的這些表。只要需要臨時保存數據,則很可能是將數據保存在tempdb數據庫中。tempdb數據庫與其他任意數據庫不同。不僅數據庫中的對象是臨時的,連數據庫本身也是臨時的。在每次SQL Server啓動時,tempdb數據庫會被完全重建。

最基本的對象:表

表由稱爲域的數據(列)和實體數據(行)構成。數據庫中實際的數據都存儲在表中。表的定義也包含了描述表中包含數據的類型,即元數據。每一列具有該列可存儲數據類型的一組規則。

索引

索引是僅在特定表或視圖架構內存在的對象。索引的功能非常類似百科全書中的目錄。索引中有以某一特定方式排序的查找值,使用索引可以快速查找數據庫中的實際信息。

索引分爲兩類:

  • 集羣索引——每一個表只能有一個集羣索引。如果是集羣索引,其含義爲:集羣索引對應的表按照其索引進行物理排序。如果爲百科全書做索引,則集羣索引是書的頁碼;按頁碼順序保存百科全書中的信息。
  • 非集羣索引——每一個表可以有多個非集羣索引。非集羣索引的含義與普通"索引"的含義更接近。如百科全書,非集羣索引指的是百科全書後面的關鍵字目錄。

觸發器

觸發器是存在於表框架內的對象。觸發器是在表操作時(如進行插入、更新或刪除等)自動執行的一段邏輯代碼。觸發器有多種用途,但主要用於在插入時複製數據或更新時檢查數據,確保數據滿足相應標準。

約束

約束是僅在表的限制中存在的另一對象。約束就是限制表中數據滿足的某種條件。約束在某種方式上類似觸發器,儘可能解決數據完整性問題,但他們有所不同,各自具有不同的優點。

文件組

數據庫中所有的表及其他對象(日誌除外)都存儲在文件中。這些文件組成了一些所謂的文件組。每個文件組中可以有超過32000個文件。一個數據庫僅能有一個主要文件組,可以有最多255個輔助文件組。

視圖

視圖是一種虛擬表。除了視圖本身不包含任意數據外,視圖的使用基本與表的使用類似。事實上視圖僅僅是存儲在表中的數據的映射和表示,它以查詢的形式存儲在數據庫中。應用視圖的主要目的是控制用戶所要顯示的數據。這有兩方面的原因:安全和易於使用。

存儲過程

存儲過程是SQL Server編程功能的基礎。存儲過程通常是邏輯單元中的Transact-SQL語句的有序集合。存儲過程允許使用變量和參數,也可使用選擇和循環結構。與單條語句相比,服務器中使用存儲過程有一下幾個優點:

  • 不使用長SQL語句字符串而使用短存儲過程名,可減少運行存儲過程中的代碼所要的網絡傳輸。
  • 預先優化和編譯,節省存儲過程每次運行的時間。
  • 通常考慮安全原因,或僅僅是簡化數據庫的複雜性,可以將過程封裝。
  • 可以調用其他的存儲過程,使得它們可以在有限的意義上重用。

但是要注意,存儲過程不是函數,它的返回值只能爲整數。當存儲過程成功執行後會默認返回0。完全可以忽略它的返回值,但如果需要根據返回值確定存儲過程是否成功執行的話,需要在存儲過程的定義中指明返回值。從這點來說,存儲過程更像是一個可執行程序,會根據執行情況返回0或其他值。

用戶自定義函數

用戶自定義函數(UDF)更符合傳統意義上的函數的概念。它和存儲過程的不同處有以下幾點:

  • 返回值的數據類型包括大部分SQL Server數據類型。不包括的返回值類型是:textntextimagecursortimestamp
  • 基本沒有"副作用",即用戶自定義函數不能完成在其範圍之外的功能,比如更改表、發電子郵件或更改系統或數據庫參數。
  • UDF類似於編程語言中使用的函數。函數可以有多個輸入變來那個,可以有一個返回值。UDF中,傳送到函數的所有變量都是按值傳遞。UDF還可以返回一種特殊類型的數據類型——表。

用戶和角色

用戶和角色相互並存。用戶(user)等價於登錄。簡言之,該對象表示登錄SQL Server的標識符。登錄SQL Server的任何人都映射到一個用戶。用戶屬於一個或多個角色(role)。SQL Server中可以直接賦予用戶或角色執行某操作的權限,一個或多個用戶可屬於同一角色。

規則

規則和約束(CHECK)都是限制插入到表中的數據類型的信息。與規則不同的是,約束本身並不是對象,而是描述特定表的多個元數據。規則是爲了向下兼容的需要,在新的開發中應該使用CHECK約束,避免使用規則。

默認值

SQL Server中有兩種類型的默認值。包括對象本身的默認值,以及表中特定列的元數據的默認值(而非真正對象)。與此非常類似,約束是針對對象,而規則是針對元數據。當插入一條記錄時,如果沒有提供該列的值,且該列具有其默認值,則自動插入默認值。

用戶自定義的數據類型

用戶自定義的數據類型是系統定義數據類型的擴展。自SQL Server 2005版本開始,用戶自定義數據類型幾乎可定義任意數據。

SQL Server 2005中的數據類型

數據類型名

長度(以字節爲單位)

數據特點

Bit 

整形

1 

表中的第一個Bit數據類型佔1個字節;其餘7個位也用作Bit數據類型。允許空格使其佔用一個額外的字節。

Bigint 

整形

8 

可處理日常用到的越來越大的數,其取值範圍爲-263~263-1

Int 

整形

4 

取值範圍爲-2147483648~ 2147483647

SmallInt 

整形

2 

取值範圍-32768~32768

TinyInt

整形

1 

取值範圍0~255

Decimal/Numeric 

數字型

可變

固定精度,取值範圍爲-1038-1~1038-1

Money 

貨幣

8 

貨幣單位,取值範圍爲-263~263,精確到4個小數位。注意貨幣單位可以是任意貨幣,不限於美元。

SmallMoney 

貨幣

4 

貨幣單位,取值範圍爲-214748.3648~214748.3647

Float(Real) 

近似小數

可變

由一參數(如Float(20))決定其長度與精度。注意參數值表示位數,不是字節數。

DateTime 

日期/時間

8 

日期與時間,取值範圍爲175311~99991231日,精確到0.03秒。

SmallDateTime 

日期/時間

4 

日期與時間,取值範圍爲190011~207966日,精確到分鐘。

Cursor 

特殊小數

1 

指向光標的指針,只佔用一個字節,記住組成實際光標的結果集也佔內存,佔用內存的大小取決於結果集。

Timestamp/rowversion 

特殊小數(二進制)

8 

給定數據庫的唯一特定值。即使UPDATE語句沒有timestamp列(時間標記),但其值在插入或更新記錄的時間自動由數據庫設定(不允許直接更新timestamp對象)。

UniqueIdentifier 

特殊小數(二進制)

16 

全球唯一標識符(GUID),必須保證在內存空間和時間內唯一。

Char 

字符

可變

定長字符數據。比設定長度短時使用空格填充,爲非Unicode數據,最大長度爲8000字符。

VarChar 

字符

可變

長度可變的字符數據。按需存儲,爲非Unicode數據。允許最大長度爲8000字符,但使用max關鍵字(varchar(max))時表示其長度可足夠大(231字節)。

Text 

字符

可變

SQL Server 2005保持向後兼容的需要。可使用varchar(max)代替。

NChar 

Unicode字符

可變

定長Unicode字符數據。最大長度爲4000字符,比設定長度短時使用空格填充。

NVarChar 

Unicode字符

可變

長度可變的Unicode字符數據。按需存儲。允許最大長度爲8000字符,但使用max關鍵字(nvarchar(max))時表示其長度可足夠大(231字節)。

NText 

Unicode字符

可變

SQL Server 2005保持向後兼容的需要。可使用nvarchar(max)代替。

Binary 

二進制

可變

定長二進制數,最大長度爲8000字節。

VarBinary 

二進制

可變

可變長度二進制數,最大特定長度爲8000字節,可使用max關鍵字(varbinary(max))使其作爲大對象字段(可達231字節)。

Image 

二進制

可變

SQL Server 2005保持向後兼容的需要。可使用varbinary(max)代替

Table 

其他

- 

主要用於結果集,通常作爲用戶自定義函數返回。在表的定義中不作爲可用的數據類型。

SQL_Variant 

其他

- 

用於保存SQL Server數據類型的容器。當列或函數需要處理多種數據類型時可使用這種數據類型。

XML 

字符

可變

定義一字符字段用作XML數據。提供不符合XML模式的數據而面向XML函數的使用的功能。

說明:

SQL Server中沒有無符號整數類型。

Server對象標識符

SQL Server中的所有對象都需要命名,即使在創建時沒有指定名稱(如表中的約束),SQL Server也會自動生成一個名稱。

SQL Server中的命名規則非常簡單,規則允許名字中內嵌空格,甚至允許名字是關鍵字。主要的命名規則如下:

  • 對象的名字必須以Unicode 2.0規範定義的任意字母開頭。大小寫是否敏感取決於服務器配置的方式。
  • 正常對象的名字最多128個字符,臨時對象的名字最多116個字符。
  • SQL Server關鍵字相同或包含內嵌空格的名字必須使用雙引號("")或方括號([])。哪一個視爲關鍵字取決於設置數據庫的兼容水平。

注意:

只有在設置了SET QUOTED IDENTIFIERON,雙引號纔可以作爲列名中的分界符。特定的對象類型還存在其他命名規則。

2 T-SQL語言基礎

T-SQLSQL Server的結構化查詢語言的"方言"。T-SQL語言遵守公共語言運行庫(CLR),簡言之,T-SQL.NET語言。SQL Server 2005可以使用任何.NET語言來訪問數據庫,而T-SQL只保留了操作SQL Server的核心功能。

基本SELECT語句

SELECT語句的基本語法規則如下:

SELECT <column list>

[FROM <source tables>]

[WHERE <restrictive condition>]

[GROUP BY <column name or expression using a column in the SELECT list>]

[HAVING <restrictive condition based on the GROUP BY results>]

[ORDER BY <column list> [ASC|DESC]]

WHERE子句中的邏輯運算符

運算符

實例

功能

=, >, <, >=, <=, <>, !=, !>, !< 

<Column Name> = <Other Column Name>

<Column Name> = 'Bob' 

標準的比較運算符。要注意
1. "
大於""小於""等於"可能因情況不同而改變。如比較字符串時是否區分大小寫。

2. !=<>都表示"不等於",而!<!>分別表示"不小於""不大於"

AND, OR, NOT 

<Column1> = <Column2> AND <Column3> >= <Column4>

<Column1> != "MyLiteral" OR <Column2> = "MyOtherLiteral" 

標準的邏輯運算符。運算優先級爲NOTANDOR

BETWEEN 

<Column> BETWEEN 1 AND 5 

第一個值在第二個與第三個值之間時其值爲TRUE,其等價於A>=B AND A <= C

LIKE 

<Column> Like "ROM%" 

可使用%_作爲通配符。%表示可以和任意長度的字符串匹配。_表示和任意的單個字符匹配。[]指定一個字符、字符串或範圍,匹配其中的任一個對象。[^]匹配指定字符串以外的任意字符。

IN 

<Column> IN (List of Numbers)

IN左邊的表達式與IN右邊的列表中的任意值匹配時返回TRUE

ALL, ANY, SOME 

<Column|Expression> 比較運算符 <ANY|SOME>(子查詢)

子查詢中的條件全部/任一滿足比較運算符時返回TRUEALL指表達式要匹配結果集中的所有值。ANYSOME相同,在表達式中匹配結果集中的任一值時返回TRUE

EXISTS 

EXISTS (子查詢)

子查詢返回至少一行記錄時爲TRUE

常見的統計函數

函數

說明

SUM() 

求和

COUNT() 

統計返回的行數(除非使用COUNT(*),否則會忽略NULL值)

AVG() 

計算平均值

MIN() 

計算最小值

MAX() 

計算最大值

DISTINCT和ALL謂詞

DISTINCTALL均放在SELECT的後面。DISTINCT表示去除重複的行,ALL表示保留重複的行。

SELECT語句默認是保留重複行的,使用SELECT DISTINCT <columns...>將返回沒有重複的結果集(每行多個字段整體沒有重複,而不是單個字段沒有重複)。DISTINCT還可應用與統計函數中,表示統計時首先去除重複的行,所以"COUNT(DISTINCT OrderID)"將比"COUNT(OrderID)"返回的行更少。但是在AVG函數中使用DISTINCT沒有任何意義。

ALL用於保留重複的行,這是SELECT語句的默認設置。但在使用UNION語句時默認會去除重複行,這是可以使用ALL指定保留("SELECT... UNION ALL SELECT...")。

使用INSERT語句添加數據

INSERT語句的語法如下:

INSERT [INTO] <table> [(column_list)]

VALUES (data_values)

注意:

  • 在插入中可以使用DEFAULT關鍵字指定列使用默認值,使用NULL關鍵字指定列爲NULL值。
  • 如果要插入和數據與表的每列一一對應,插入語句可以忽略列名列表可選項。
  • 插入數值類型數據不需要使用引號,而插入字符串或者日期型數據時需要使用引號。
  • 常用的日期型數據格式爲MM/DD/YYYY以及YYYY-MM-DD

存儲過程sp_help

存儲過程sp_help的功能是給出任意數據庫對象、用戶定義的數據類型或SQL Server數據類型的信息。執行存儲過程sp_help的語法結構如下:

EXEC sp_help <name>

要查看sales表的屬性,只要輸入一下命令:

EXEC sp_help sales

INSERT INTO... SELECT語句

INSERT INTO... SELECT語句可完成一次插入一個數據塊的功能。其語法結構爲INSERT語句與SELECT語句語法結構的組合,如下:

INSERT INTO <table_name>

[<column list>]

<SELECT statement>

SELECT語句產生的結果集爲INSERT語句中插入的數據。

用UPDATE語句更改數據

UPDATE語句的語法結構如下:

UPDATE <table_name>

SET <column> = <value> [,<column> = <value>]

[FROM <source table(s)>]

[WHERE <restrictive condition>]

示例:

UPDATE stores

SET city = 'There'

WHERE stor_id = 'TEST'

此外,SET子句還可以使用表達式:

UPDATE titles

SET price = price * 1.1

WHERE title_id LIKE 'BU%'

DELETE語句

語法結構如下:

DELETE <table_name>

[WHERE <search condition>]

SQL Server不允許刪除作爲外鍵約束引用的行。如果一行使用外鍵約束引用另一行,則要先刪除被引用行後才能刪除引用行。

3 連接

幾種形式的JOIN子句

  • 內部連接(INNER JOIN
  • 外部連接(<LEFT|RIGHT> [OUTER] JOIN
  • 完全連接(FULL JOIN
  • 交叉連接(CROSS JOIN

連接的語法結構

SELECT <select list>

FROM <first_table> <join_type> <second_table>

[ON <join_condition>]

別名

使用AS關鍵字(可以省略)給列或者表取別名。同一個查詢中的多個表中,可以選擇哪些表使用別名,哪些表不使用別名,代碼中別名和表名可以混合使用,但是隻要確定了使用表的別名,則這個表必須一直使用別名。

內部連接(INNER JOIN)

內部連接根據一個或幾個相同的字段將記錄匹配在一起,僅僅返回那些匹配的記錄。示例:

SELECT *

FROM Products p

INNER JOIN Suppliers s

    ON p.SupplierID = s.SupplierID

外部連接(OUTER JOIN)

外部連接語法結構:

SELECT <select list>

FROM <left table>

<LEFT|RIGHT> [OUTER] JOIN <right table>

ON <join condition>

LEFT OUTER JOIN會使LEFT表中的所有記錄都包含到結果集中,即使在RIGHT表中相沒有匹配的記錄。而RIGHT OUTER JOIN會使RIGHT表中的所有記錄都包含到結果集中,即使在LEFT表中相沒有匹配的記錄。

注意NULL值無法連接NULL值。因爲NULL值和NULL值是不相等的。

示例:

USE Northwind

 

SELECT c.CustomerID, CompanyName

FROM Customers c

LEFT JOIN Orders o

    ON c.CustomerID = o.CustomerID

WHERE o.CustomerID IS NULL

完全連接(FULL JOIN)

完全連接用來將JOIN兩側的數據完全匹配,並返回所有的記錄,無論是記錄在JOIN的哪一側表中。完全連接的目的是返回沒有參考的記錄之間的所有關係,要返回的是連接兩側表的所有記錄,而且不丟棄任何記錄。

交叉連接(CROSS JOIN)

交叉連接不使用ON運算符,並將CROSS JOIN的左側的所有記錄與右側的所有記錄連接。簡言之,返回的是JOIN兩側的笛卡爾積。

示例:

SELECT v.VendorName, a.Address

FROM Vendors v

CROSS JOIN Address a

聯合(UNION)

UNION用於將一個特殊的運算符,用於將兩個或兩個以上的查詢產生一個結果集。使用UNION處理查詢時,要注意以下關鍵幾點:

  • 所有UNION的查詢必須在SELECT列表中有相同的列數。即如果在第一個查詢中選擇了3列,則在第二個查詢中也要選擇3列。
  • UNION返回的結果集的標題僅從第一個查詢獲得,而忽略其他查詢的列標題。
  • 查詢中的對應列的數據類型必須隱式一致。
  • 與其他非UNION查詢不同,UNION查詢的默認返回選項爲DISTINCT,而不是ALL。可以在UNION查詢中使用ALL關鍵字(UNION ALL),才能返回重複的行。

示例:

USE Northwind

 

SELECT CompanyName AS Name, Address, City, Region, PostalCode, Country

FROM Customers

UNION

SELECT CompanyName AS Name, Address, City, Region, PostalCode, Country

FROM Suppliers

UNION

SELECT FirstName + ' ' + LastName AS Name, Address, City, Region, PostalCode, Country

FROM Employees

4 創建和修改數據表

4.1 SQL Server中的對象名

SQL Server表有4層命名約定。完全限定命名如下所示:

[ServerName.[DatabaseName,[SchemaName.]]]ObjectName

4.1.1 模式名稱

如果使用模式,那麼需要指定對象是在哪種模式下的。不同模式下可以有兩個同名的對象。如果想訪問不在默認模式下的對象,那麼需要特別指明對象的模式名稱(即廣爲人知的所有權)。

 

1. 關於模式的進一步討論

在以前的發佈中,所有權實際上很像它字面上的意思:即是通過完全限定名稱來識別是誰"擁有"這個對象。通常,所有這或者是創建該對象的用戶,或者是數據庫的所有者(通常指dbo)。一個所有者與特定的登錄相關,而模式可以在多個登錄之間共享,一個登錄也可以擁有多個模式。

在默認情況下,只有當用戶或者是sysadmin系統角色的成員,或者是db_ownerdb_ddladmin數據庫角色時,才能在數據庫中創建對象。

用戶可以被授予一定權限來創建特定類型的數據庫及系統對象。如果這些個體用戶已經創建了一個對象,那麼在默認情況下,這個對象被授予那個登錄下默認的模式。

注意:

存在一個特徵,並不是說就應該使用這個特徵。授予CREATE權限給普通用戶可能會出現不愉快的事情。簡單來說,將CREATE權限限制在sa賬戶或sysadmins成員或db_owner安全角色之內。

 

2. 默認模式:dbo

無論誰創建了數據庫,都被認爲是"數據庫所有者",即dbo。在數據庫裏面創建的任何對象都帶有dbo模式,而不是個體的用戶名。

另外,sa(或者sysadmin角色的成員)總是dbo的別名。即無論是誰實際上擁有數據庫,sa總擁有完全的權限,就好像是dbo一樣。而且sa登錄創建的任何對象都顯示所有權爲dbo

例如:假如某個數據庫的普通用戶MySchema,被賦予了CREATE TABLE權限。如果該用戶創建了一個名爲MyTable的表,那麼帶有所有者限定的對象名稱是MySchema.MyTable。注意,由於這時這個表有特定的所有者(MySchema),除了MySchema之外的其他用戶需要提供所有者限定名稱才能正確解析這個表的名稱(MySchema.MyTable)。現在,假如還有一個用戶,登錄名爲Fred。但是Fred是這個數據庫的所有者(不僅僅是db_owner的成員)。假如Fred使用與MySchema同樣的語句創建了名爲MyTable的表,那麼帶所有者限定名稱的表名稱是dbo.MyTable。還有,因爲dbo正好是默認的所有者,所以任何用戶都可以用MyTable來引用該表。注意,db_owner數據庫角色的成員創建的對象的默認模式不是dbo,這些對象將被賦予特定用戶所設定的默認模式。

4.1.2 數據庫名稱

需要在當前數據庫以外的數據庫檢索數據時,需要使用數據庫限定的命名。記住,當前數據庫總是默認的數據庫,所以,如果只需要當前數據庫中的數據,那麼不需要在完全限定的名稱中包括數據庫名稱。

4.2 CREATE語句

CREATE語句用來創建數據庫中的對象。CREATE的第一部分看起來總是這樣的:

CREATE <object type> <object name>

4.2.1 CREATE DATABASE

CREATE DATABASE <database name>

代碼示例:

CREATE DATABASE Accounting

ON

(

    NAME = 'Accounting',

    FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\ AccountingData.mdf',

    SIZE = 10MB,

    MAXSIZE = 50MB,

    FILEGROWTH = 5MB

)

LOG ON

(

    NAME = 'AccountingLog',

    FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data\ AccountingLog.ldf',

    SIZE = 5MB,

    MAXSIZE = 25MB,

    FILEGROWTH = 5MB

)

各選項含義:

1. ON

ON用在兩個地方:一個定義存儲數據的文件的位置,二是定義存儲日誌的文件的位置。

 

2. NAME

指定定義的文件的名稱,但是隻是一個邏輯名稱——SQL Server在內部使用該名稱引用該文件。

 

3. FILENAME

指定文件的物理名稱。數據文件的推薦擴展名爲.mdf,日誌文件的推薦擴展名爲.ldf,附屬文件的推薦擴展名爲.ndf

 

4. SIZE

指定文件的初始大小。默認情況下,大小的單位是MB(兆字節),還可以使用KBGB或者TB。要記住,這個值至少與模型數據庫一樣大,而且必須是整數,否則將出錯。默認的值與模版數據庫一樣。

 

5. MAXSIZE

指定文件長度的最大值。默認情況下,大小的單位是MB。這個選項沒有默認值,如果沒有提供該選項,則表示不限制最大值。

 

6. FILEGROWTH

指定當擴張文件時每次的擴張量,可以提供一個值來說明文件每次增加多少字節,或者提供一個百分比,指定文件每次增長的百分比。

 

7. LOG ON

指定日誌文件。注意日誌文件默認的大小是數據文件大小的25%。其他方面,日誌文件和數據庫文件的說明參數相同。

4.2.2 創建數據表

創建表的語法如下:

CREATE TABLE [database_name.[owner].]table_name

(

    <column name> <data type>

    [[DEFAULT <constant expression>]

        | [IDENTITY [(seed, increment) [NOT FOR REPLICATION]]]]

    [NULL | NOT NULL]

    [<column constraints>]

        | [<column name> AS <computed column expression>]

        | [<table constraint>]

    [,...n]

)

 

1. 表和列名稱

表和列的推薦命名規則:

  • 名稱的每個單詞,首字母大寫,其他字母小寫。
  • 名稱儘量短,但是要具有描述性。
  • 限制使用縮寫,只使用大家都能理解的縮寫。例如"ID"表示標識、"No"表示數字、"Org"表示組織。
  • 當基於其他表來構建表時,需要在新的表名中包含其他父表的名稱。
  • 當名稱中有兩個單詞時,不要用任何分隔符。

 

2. 數據類型

注意沒有默認的數據類型

 

3. DEFAULT

如果要使用默認值,就必須緊跟在數據類型之後給定這個值。

 

4. IDENTITY

當你設定一個列爲標識列時,SQL Server自動分配一個順序號給你插入的每個行。注意IDENTITY列和PRIMARY KEY列是完全不同的概念,既不會因爲有一個IDENTITY列就說明這個值是唯一的(例如,可以重新設置種子,使用前面用過的值)。IDENTITY值通常用於PRIMARY KEY列,但並不是必須這樣使用。

 

5. NOT FOR REPLICATION

NOT FOR REPLICATION參數決定:當列(通過複製)發佈到另外一個數據庫時,是否爲新的數據庫分配一個新的標識值,還是保留已有的值。

 

6. NULL/NOT NULL

默認的設置是列值是NOT NULL,除非指定允許爲空。然而,有很多不同的設置可以改變這個設置,從而影響這個默認值。

 

7. 列約束

列約束就是對單個列設置的關於該列可插入數據的限制和規則。

 

8. 計算列

該列值是由表中其他列動態生成的。具體的語法如下:

<column name> AS <computed column expression>

例如:

ExtendedPrice AS Price * Quantity

ListPrice AS Cost * 1.2

相關的限制條件:

  • 不能使用子查詢,而且值不能來自其他不同的表。
  • SQL Server 2000之前,不能使用計算列作爲鍵的任何部分,也不能和默認約束一起使用。在SQL Server 2005中,可以在約束中使用計算列。
  • 以前版本的另外一個問題是在計算列中創建索引的能力。可以在計算列上創建索引,但是必須採用特定的步驟。

 

9. 表約束

表約束和列約束很相似,但表約束可以基於多個列。表層次的約束包括PRIMARY KEY約束、FOREIGN KEY約束以及CHECK約束。

 

10. ON

表定義中的ON子句可以指定表位於哪個文件組。大多數時間,可以省略ON子句,那些表將位於默認文件組中。

 

11. TEXTIMAGE_ON

該選擇將表的特定部分移動到不同的文件組中。這個子句只有在表的定義中有textntextimage列時纔有效。當使用TEXTIMAGE_ON子句時,只是將BLOB信息移動到分離的文件組中——表的其他部分還在默認文件組或者ON子句選擇的文件組中。

 

12. 創建一個表

USE Accounting

 

CREATE TABLE Customers

(

    CustomerNo INT IDENTITY NOT NULL,

    CustomerName VARCHAR(30) NOT NULL,

    Address1 VARCHAR(30) NOT NULL,

    Address2 VARCHAR(30) NOT NULL,

    City VARCHAR(20) NOT NULL,

    State CHAR(2) NOT NULL,

    Zip VARCHAR(10) NOT NULL,

    Contact VARCHAR(25) NOT NULL,

    Phone CHAR(15) NOT NULL,

    FedIDNo VARCHAR(9) NOT NULL,

    DateInSystem SMALLDATETIME NOT NULL

)

使用sp_help存儲過程查看錶的信息:

EXEC sp_help Customers

4.3 ALTER語句

ALTER語句用來更改對象。ALTER語句總是有相同的開頭:

ALTER <object type> <object name>

4.3.1 ALTER DATEBASE

示例:

ALTER DATABASE Accounting

    MODIFY FILE

    (

        NAME = Accounting,

        SIZE = 100MB

    )

4.3.2 ALTER TABLE

更經常的情況是改變表的結構。這個可以是增加、刪除一列或者改變一列的數據類型等。示例:

ALTER TABLE Employees

    ADD

        PreviousEmployer VARCHAR(30) NULL,

        DataOfBirth DATETIME NULL,

        LastRaiseDate DATETIME NOT NULL, DEFAULT '2005-01-01'

4.4 DROP語句

DROP語句用來刪除對象。

DROP <object type> <object name>[, ...n]

如果需要, 可以同時刪除兩個表:

USE Accounting

 

DROP TABLE Customers, Employees

刪除整個數據庫:

DROP DATABASE Accounting

5 約束

確保數據的完整性不是使用數據的程序的責任,而是數據庫本身的責任。將數據完整性的責任移到數據庫本身是數據庫管理的一次革命。

3種不同類型的約束:

  • 實體約束
  • 域約束
  • 參照完整性約束

具體的約束類型:

  • PRIMARY KEY約束
  • FOREIGN KEY約束
  • UNIQUE約束
  • CHECK約束
  • DEFAULT約束

5.1 約束的類型

5.1.1 域約束

域約束處理一個或多個列,確保一個特定列或一組特定列滿足特定的標準。

5.1.2 實體約束

實體約束都是關於每個行的。這種形式的約束並不關心一個整體的列,只對特定的行感興趣,如PRIMARY KEY約束和UNIQUE約束。

5.1.3 參照完整性約束

參照完整性約束是在某列的值必須與其他列的值匹配時創建的,列可以在同一個表中,或者更通常的是在不同的表中,如FOREIGN KEY約束。

5.2 約束命名

常見的約束的推薦命名規則如下:

  • CHECK約束以CK開頭、主鍵約束以PK開頭、外鍵約束以FK開頭、唯一約束以UN開頭。
  • 後接表名、列名。

如在Customers表上對PhoneNo列設置約束:CK_Customers_PhoneNo,Customers表上的主鍵約束:PK_Custoemrs_CustomerID。

5.3 鍵約束

常用的鍵類型:主鍵、外鍵、唯一約束。

5.3.1 主鍵約束

1. 在創建表的時候創建主鍵約束。

CREATE TABLE Customers

(

    CustomerNo INT IDENTITY NOT NULL PRIMARY KEY,

    ......

)

 

2. 在已存在的表上創建主鍵約束。

USE Accounting

 

ALTER TABLE Employees

    ADD CONSTRAINT PK_EmployeeID

        PRIMARY KEY (EmployeeID)

5.3.2 外鍵約束

CREATE語句中設置一列或幾列外鍵約束的語法如下所示:

<column name> <date type> <nullability>

FOREIGN KEY REPERENCES <table name>(<column name>)

    [ON DELETE {CASCADE|NO ACTION|SET NULL|SET DEFAULT}]

    [ON UPDATE {CASCADE|NO ACTION|SET NULL|SET DEFAULT}]

示例:

USE Accounting

 

CREATE TABLE Orders

(

    OrderID INT IDENTITY NOT NULL

        PRIMARY KEY,

    CustomerNo INT NOT NULL

        FOREIGN KEY REFERENCES Customers(CustomerNo),

    OrderDate SMALLDATETIME NOT NULL,

    EmpoyeeID INT NOT NULL

)

 

1. 在已存在的表中添加一個外鍵

ALTER TABLE Orders

    ADD CONSTRAINT FK_EmployeeCreatesOrder

        FOREIGN KEY (EmployeeID) REFERENCES Employees(EmployeeID)

 

2. 使一個表自引用

在實際創建自引用約束之前,很關鍵的一點是在添加外鍵之前表中至少有一行。

ALTER TABLE Employees

    ADD CONSTRAINT FK_EmployeeHasManager

        FOREIGN KEY (ManagerEmpID) REFERENCES Employees(EmployeeID)

 

3. 級聯動作

外鍵是雙向的,即不僅是限制子表的值必須存在於父表中,還在每次對父表操作後檢查子行。SQL Server的默認行爲是在子表存在時"限制"父表不被刪除。然而,有時會自動刪除任何相關記錄,而不是防止刪除被引用的記錄。同樣,在更新記錄時,可能希望相關的記錄自動引用剛剛更新的記錄。這種進行自動刪除和更新的過程稱爲級聯。通過修改聲明外鍵的語法——添加ON子句,來定義級聯操作。

USE Accounting

 

CREATE TABLE OrderDetails

(

    OrderID INT NOT NULL,

    PartNo VARCHAR(10) NOT NULL,

    Description    VARCHAR(25) NOT NULL,

    Qty INT NOT NULL,

    CONSTRAINT PK_OrderDetails

        PRIMARY KEY (OrderID, PartNo),

    CONSTRAINT FK_OrderContainsDetails

        FOREIGN KEY (OrderID)

            REFERENCES Orders(OrderID)

            ON UPDATE NO ACTION

            ON DELETE CASCADE

)

如果對外鍵定義了CASCADE,則操作會從父表級聯到子表中。即,如果從父表刪除了某項,子表中依賴該項的項都會被刪除;如果從父表中更新了某項,則子表中依賴該項的字段也會被更新。

值得注意的是:CASCADE動作所能影響的深度沒有限制。

 

4. 其他操作

NO ACTION爲默認操作,即如果子表有依賴,則禁止對父表中的該字段進行刪除和更新操作。

SET NULL操作會在父表中的該字段被刪除或者更新時,將子表中的依賴項設爲NULL,前提是子表中的該項可爲NULL值。

SET DEFAULT操作會在父表中的該字段被刪除或者更新時,將子表中的依賴項設爲在子表中定義的默認值,當然前提是在子表中該字段有默認值。

5.3.3 唯一約束

唯一約束不會自動防止您設置一個NULL值。是否允許NULL值取決於表中相應列的NULL選項的設置。然而,要記住如果您確實允許NULL值,那麼只能插入一個NULL

 

在創建表時設置唯一約束:

CREATE TABLE Shippers

(

    ShipperID INT IDENTITY NOT NULL PRIMARY KEY,

    ShipperName VARCHAR(30) NOT NULL,

    Address VARCHAR(30) NOT NULL,

    City VARCHAR(25) NOT NULL,

    State CHAR(2) NOT NULL,

    Zip VARCHAR(10) NOT NULL,

    PhoneNo VARCHAR(14) NOT NULL UNIQUE

)

 

在已存在的表中創建唯一約束:

ALTER TABLE Employees

    ADD CONSTRAINT AK_EmployeeSSN

        UNIQUE (SSN)

在約束名稱中的AK前綴代表"交替鍵(Alternate Key)",也可以使用前綴UQ或者簡單的U,代表唯一約束。

5.4 CHECK約束

CHECK約束使用與WHERE字句一樣的規則來定義。CHECK約束標準的示例如下:

目標

SQL 

限制Month列爲合適的數字

BETWEEN 1 AND 12 

合適的SSN格式

LIKE '[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]' 

限制Shippers的一個特定列表

IN ('UPS', 'Fed Ex', 'USPS') 

價格必須爲正

UnitPrice >= 0 

在同一行中引用另外一個列

ShipDate >= OrderDate 

 

在已存在的表中添加CHECK約束:

ALTER TABLE Customers

    ADD CONSTRAINT CK_CustomerDateInSystem

        CHECK (DateInSystem <= GETDATE())

試着插入違反CHECK約束的記錄會得到錯誤。

5.5 DEFAULT約束

DEFAULT約束定義了當插入新行時,在您定義了默認約束的列中沒有數據時填充的默認值。要注意:

  • 默認值只在INSERT語句中使用——UPDATE語句和DELETE語句中被忽略。
  • 如果在INSERT語句中提供了任意的值(包括NULL值),那麼就不使用默認值。
  • 如果沒有提供值,那麼總是使用默認值。

5.5.1 在CREATE TABLE語句中定義DEFAULT約束

示例:

CREATE TABLE Shippers

(

    ShipperID INT IDENTITY NOT NULL

        PRIMARY KEY,

    ShipperName VARCHAR(30) NOT NULL,

    DataInSystem SMALLDATETIME NOT NULL

        DEFAULT GETDATE()

)

5.5.2 在已存在的表中添加DEFAULT約束

示例:

ALTER TABLE Customers

    ADD CONSTRAINT DF_CustomerDefaultDateInSystem

        DEFAULT GETDATE() FOR DateInSystem

5.6 使約束失效

5.6.1 在創建約束時忽略無效的數據

默認情況下,除非已存在的數據滿足約束標準,否則SQL Server將不會創建約束。要想在創建約束時不檢查已經在表中的數據是否滿足約束,可以在添加約束時添加WITH NOCHECK選項。示例:

ALTER TABLE Customers

    WITH NOCHECK

    ADD CONSTRAINT CK_CustomerPhoneNo

    CHECK

    (Phone LIKE '([0-9][0-9][0-9]) [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]')

5.6.2 臨時使已存在的約束失效

使用NOCHECK選項關閉約束,而不是刪除它。示例:

ALTER TABLE Customers

    NOCHECK

    CONSTRAINT CK_CustomerPhoneNo

當準備重新讓約束起效時,使用CHECK選項代替NOCHECK

ALTER TABLE Customers

    CHECK

    CONSTRAINT CK_CustomerPhoneNo

6 在查詢中添加更多內容

6.1 子查詢的概念

子查詢是嵌套在另外一個查詢中的正常的T-SQL查詢。在有一個SELECT語句作爲部分數據或者另外一個查詢的條件的基礎時,通過使用括號創建子查詢。

子查詢通常用於滿足下列需求之一:

  • 將一個查詢分隔爲一系列的邏輯步驟。
  • 提供一個列表作爲WHERE子句或者INEXISTSANLSOMEALL的目標
  • 爲父查詢中的每個記錄提供一個查詢表。

6.2 嵌套的子查詢

嵌套的子查詢只在一個方向嵌套——返回在外部查詢中使用的單個值,或者在IN運算符中使用的一個完整的值列表。

在最鬆散的意義上說,查詢語法看起來像下面的兩個語法模板:

SELECT <select list>

FROM <some table>

WHERE <some column> = (

    SELECT <single column>

    FROM <some table>

    WHERE <condition that results in only one row returned>)

或者:

SELECT <select list>

FROM <some table>

WHERE <some column> IN (

    SELECT <single column>

    FROM <some table>

    WHERE <where condition >)

6.2.1 使用單個值的SELECT語句的嵌套查詢

例如,假設希望知道每一天通過系統銷售的產品的每個條目的ProductID

SELECT DISTINCK o.OrderDate, od.ProductID

FROM Orders o

INNER JOIN OrderDetails od

    ON o.OrderID = od.OrderID

WHERE o.OrderDate = (SELECT MIN(OrderDate) FROM Orders)

6.2.2. 使用返回多個值的子查詢的嵌套查詢

例如,查看所有具有折扣記錄的商店列表:

USE Pubs

 

SELECT stor_id AS "Store ID", stor_name AS "Store Name"

FROM Stores

WHERE stor_id IN (SELECT stor_id FROM Discounts)

6.2.3. 使用嵌套的SELECT來發現孤立的記錄

這種嵌套的SELECT和前面示例幾乎相同,區別是添加了NOT運算符。這個不同點時的在轉化連接語法時設置等於外部連接而不是內部連接。例如,需要查詢所有在Pubs數據庫中沒有匹配的折扣記錄的商店:

SELECT stor_id AS "Store ID", stor_name AS "Store Name"

FROM Stores

WHERE stor_id NOT IN

    (SELECT stor_id FROM Discounts WHERE stor_id IS NOT NULL)

6.3 相互關聯的子查詢

6.3.1 相互關聯的子查詢的工作原理

在相互關聯的子查詢中,內部查詢在外部查詢提供的信息上運行,反之亦然。有3個步驟的處理過程:

  • 外部查詢獲得一個記錄,然後將該記錄傳遞到內部查詢。
  • 內部查詢根據傳遞的值執行。
  • 內部查詢然後將結果值傳回到外部查詢,外部查詢利用這些值完成處理過程。

6.3.2 在WHERE子句中的相互關聯的子查詢

例如,需要查詢系統中每個顧客第一個訂單的OrderIDOrderDate

SELECT o1.CustomerID, o1.OrderID, o1.OrderDate

FROM Orders o1

WHERE o1.OrderDate = (

    SELECT MIN(o2.OrderDate)

    FROM Orders o2

    WHERE o2.CustomerID = o1.CustomerID)

6.3.3 在SELECT列表中的相互關聯的子查詢

例如,現在需要查詢顧客的姓名和在哪天開始訂購商品:

SELECT cu.CompanyName,

    (SELECT MIN(OrderDate)

    FROM Orders o

    WHERE o.CustomerID = cu.CustomerID) AS "Order Date"

FROM Customers cu

6.3.4 處理NULL數據——ISNULL函數

ISNULL()接受一個變量或者表達式來驗證是否是一個空值。如果值確實是NULL,那麼函數返回其他預指定的值。如果原來的值不是NULL,那麼返回原來的值。語法如下:

ISNULL(<expression to test>, <replacement value if null>)

因此,示例如表所示:

ISNULL表達式

返回值

ISNULL(NULL, 5) 

5 

ISNULL(5, 15) 

5 

ISNULL(MyColumnName, 0) where MyColumnName IS NULL 

0 

ISNULL(MyColumnName, 0) where MyColumnName = 3 

3 

ISNULL(MyColumnName, 0) where MyColumnName = 'Fred Farmer' 

Fred Farmer 

使用示例:

SELECT cu.CompanyName,

    ISNULL(CAST((SELECT MIN(o.OrderDate)

        FROM Orders o

        WHERE o.CustomerID = cu.CustomerID) AS VARCHAR), 'NEVER ORDERED')

    AS "Order Date"

FROM Customers cu

6.3 派生表

例如,現在需要查詢既訂購了Chocolade又訂購了Vegie-spread的所有公司名稱。查詢代碼如下所示:

SELECT DISTINCT c.CompanyName

FROM Customers c

INNER JOIN (

    SELECT CustomerID

    FROM Orders o

    INNER JOIN OrderDetails od

        ON o.OrderID = od.OrderID

    INNER JOIN Products p

        ON od.ProductID = p.ProductID

    WHERE p.ProductName = 'Chocolade') AS spen

    ON c.CustomerID = spen.CustomerID

INNER JOIN (

    SELECT CustomerID

    FROM Orders o

    INNER JOIN OrderDetails od

        ON o.OrderID = od.OrderID

    INNER JOIN Products p

        ON od.ProductID = p.ProductID

    WHERE p.ProductName = 'Vegie-spread') AS spap

    ON c.CustomerID = spap.CustomerID

6.4 EXISTS運算符

使用EXISTS時,根據是否存在數據滿足查詢中EXISTS語句所建立的標準,返回一個簡單的TRUEFALSE。例如:

SELECT CustomerID, CompanyName

FROM Customers cu

WHERE EXISTS (

    SELECT OrderID

    FROM Orders o

    WHERE o.CustomerID = cu.CustomerID)

當使用EXISTS關鍵字時,SQL Server不需要執行一行一行的連接,而是尋找記錄,知道找到第一個匹配的記錄,停止在那裏。只要有一個匹配,EXISTS就爲真,不需要繼續查找。

如果需要查詢沒有訂購任何產品的客戶,可以使用NOT EXISTS

SELECT CustomerID, CompanyName

FROM Customers cu

WHERE NOT EXISTS (

    SELECT OrderID

    FROM Orders o

    WHERE o.CustomerID = cu.CustomerID)

6.5 數據類型轉換:CAST和CONVERT

CASTCONVERT都可以執行數據類型轉換。在大部分情況下,兩者執行相同的功能,不同的是CONVERT還提供一些日期格式轉換,而CAST沒有這個功能。

注意,CASTANSI兼容的,而CONVERT不是。

各自的語法如下:

CAST (expression AS data type)

CONVERT (data type, expression[, style])

CASTCONVERT可以進行很多數據類型轉換,在SQL Server不進行隱式轉換時,需要這種轉換。例如:

SELECT 'The Customer has an Order numbered ' + CAST(OrderID AS VARCHAR)

FROM Orders

WHERE CustomerID = 'ALFKI'

例如,需要將timestamp列轉換爲正常數字。一個timestamp是個二進制數字,因此需要轉換:

SELECT CloTS AS "Uncoverted", CAST(ColTS AS INT) AS "Converted"

FROM ConvertTest

還可以轉換日期:

SELECT OrderDate, CAST(OrderDate AS VARCHAR) AS "Converted"

FROM Orders

WHERE OrderID = 11050

CONVERT還可以控制日期格式:

SELECT OrderDate, CONVERT(VARCHAR, OrderDate, 111) AS "Converted"

FROM Orders

WHERE OrderID = 11050

CONVERT函數最後一個代碼說明需要的格式。注意,任何以超過100表示的是4位的年份;小於100的是兩位數字的年份,不過有很少的一些例外,並且小於100表示的格式加上100後即爲對應的4位的年份表示的格式。

7 視圖

視圖的核心實際上僅僅是一個存儲的查詢。重要的是可以將來自於基本表(或者其他視圖)的數據混合以及匹配,以創建在大多數方面上像另一個基本表那樣起作用的對象。

7.1 簡單的視圖

視圖語法的基本形式:

CREATE VIEW <view name>

[WITH ENCRYPTION]

AS

<select statement>

WITH CHECK OPTION

示例——創建簡單的視圖:

USE Accounting

GO

 

CREATE VIEW CustomerPhoneList_vw

AS

    SELECT CustomerName, Contact, Phone

    FROM Customers

Employees表創建視圖,隱藏隱私信息:

USE Accounting

GO

 

CREATE VIEW Employees_vw

AS

SELECT EmployeeID,

    FirstName,

    MiddleInitial,

    LastName,

    Title,

    HireDate,

    ManagerEmpID,

    Department

FROM Employees

7.2 作爲過濾器的視圖

在創建視圖時使用WHERE字句來過濾查詢中的結果。

示例:

CREATE VIEW CurrentEmployees_vw

AS

SELECT EmployeeID,

    FirstName,

    MiddleInitial,

    LastName,

    Title,

    HireDate,

    TerminationDate,

    ManagerEmpID,

    Department

FROM Employees

WHERE TerminationDate IS NULL

7.3 更加複雜的視圖

在簡單視圖的基礎上添加連接。示例——查詢訂單、零件和消費者信息:

USE Northwind

GO

 

CREATE VIEW CustomerOrders_vw

AS

SELECT cu.CompanyName,

    o.OrderID,

    o.OrderDate,

    od.ProductID,

    p.ProductName,

    od.Quantity,

    od.UnitPrice,

    od.Quantity * od.UnitPrice AS ExtendedPrice

FROM Customers AS cu

INNER JOIN Orders AS o

    ON cu.CustomerID = o.CustomerID

INNER JOIN OrderDetails AS od

    ON o.OrderID = od.OrderID

INNER JOIN Products AS p

    ON od.ProductID = p.ProductID

數據庫的用戶可以方便地查詢到消費者的訂單信息,而不需要關心四個表的連接:

SELECT CompanyName, ExtendedPrice

FROM CustomerOrders_vw

WHERE OrderDate = '1996-9-3'

7.4 通過視圖改變數據

從使用的觀點來看,視圖非常像表那樣地工作。但是,現在來看看一些不同之處。

你可以成功地對視圖執行INSERTUPDATE以及DELETE語句。但是,當通過視圖改變數據的時候,有一些內容需要注意:

  • 如果視圖包含連接,在大多數情況下,除非使用INSTEAD OF觸發器,否則不能對數據執行INSERT或者DELETE操作。在一些情況下,UPDATE可以不使用INSTEAD OF觸發器來工作,但是僅限於個別情況。
  • 如果視圖僅僅引用單個的表,那麼在表中所有需要的字段都在視圖中或者有默認值得情況下,可以直接執行INSERT操作。否則,需要使用INSTEAD OF觸發器。
  • 在一個有限的範圍內,可以限制在視圖中可以插入和更新的內容以及不可以插入和更新的內容。

 

1. 以連接的數據方式處理視圖的變化

如果視圖包含連接,在大多數情況下,除非使用INSTEAD OF觸發器,否則不能對數據執行INSERT或者DELETE操作。

 

2. 必要的字段必須在視圖中出現或者具有默認值

 

3. 約束插入到視圖中的內容——WITH CHECK OPTION

WITH CHECK OPTION的規則很簡單——爲了通過使用視圖更新或者插入數據,結果行必須符合要求以出現在視圖結果中。即如果通過視圖插入的值不能在視圖中顯示出來,則禁止插入該行記錄。

7.5編輯視圖

ALTER語句會完全替換現有的視圖。使用ALTER VIEW語句和先刪除後新建視圖的區別在於:

  • ALTER VIEW期望找到一個已存在的視圖,而CREATE則相反。
  • ALTER VIEW保留了視圖上任何已經建立的權限信息。
  • ALTER VIEW保留了任何依賴信息。

7.6 刪除視圖

DROP VIEW <view name> [, <view name> [...n]]

7.7 保護代碼:加密視圖

使用WITH ENCRYPTION選項加密視圖,注意和WITH CHECK OPTION出現的位置不同:

ALTER VIEW CustomerOrders_vw

WITH ENCRYPTION

AS

SELECT cu.COmpanyName,

    o.OrderDate,

    od.ProductID,

    p.ProductName,

    od.Quantity,

    od.UnitPrice,

    od.Quantity * od.UnitPrice AS ExtendedPrice

FROM Customers AS cu

INNER JOIN Orders AS o

    ON cu.CustomerID = o.CustomerID

INNER JOIN OrderDetails AS od

    ON o.OrderID = od.OrderID

INNER JOIN Products AS p

    ON od.ProductID = p.ProductID

注意,一旦源代碼被加密了,就沒有辦法恢復。

第8章 腳本與批處理

8.1 腳本基礎

腳本示例:

USE Northwind

 

DECLARE @Ident INT

 

INSERT INTO Orders

(CustomerID, OrderDate)

VALUES

('ALFKI', DATEADD(day, -1, GETDATE()))

 

SELECT @Ident = @@IDENTITY

 

INSERT INTO OrderDetails

(OrderID, ProductID, UnitPrice, Quantity)

VALUES

(@Ident, 1, 50, 25)

 

SELECT 'The OrderID of the INSERTed row is ' + CONVERT(VARCHAR(8), @Ident)

8.1.1 USE語句

USE語句用於設置當前數據庫。USE語句會影響在完全限定對象名的數據庫部分使用默認值的任何地方。

8.1.2 聲明變量

DECLARE語句具有相當簡單的語法:

DECLARE @<variable name> <variable type> [, …]

可以一次僅僅聲明一個變量,也可以一次聲明幾個變量。變量開始的值將總是爲NULL,直到顯示地將變量設置爲一些其他的值。

 

1. 爲變量設置值

目前在變量中設置值的方法有兩種。可以使用SELECT語句或者SET語句。從功能上看,它們的作用幾乎是相同的,不同的是SELECT語句具有使得源值來自SELECT語句中的某一列的能力。

 

使用SET設置變量

SET通常用於以更加程序化的語言中所使用的方式來設置變量。經典的使用示例是:

SET @TotalCost = 10

SET @TotalCost = @UnitCost * 1.1

使用SET,不能將查詢得到的值賦給變量——必須將查詢和SET分開。例如:

USE Northwind

 

DECLARE @Test MONEY

 

SET @Test = (SELECT MAX(UnitPrice) FROM OrderDetails)

SELECT @Test

注意:

儘管這個語法可以起作用,但習慣上,從來不採用這種方法實現代碼。

 

使用SELECT設置變量

當變量中存儲的信息來源於查詢的時候,經常用SELECT給變量賦值。例如,上面最後的示例中使用SELECT是更加常用的做法:

USE Northwind

 

DECLARE @Test MONEY

 

SELECT @Test = MAX(UnitPrice) FROM OrderDetails

SELECT @Test

 

2. 系統函數概述

注意這些系統函數常被人們認爲是"系統常量",但在SQL Server中更規範的名稱爲"系統函數"。其中最值得關心的如下所示:

系統函數

用途

註釋

@@DATEFIRST 

返回當前設置的每個星期的第一天(比如星期日或者星期一)

是一個系統範圍的設置——如果有人改變了這個設置,就不能得到所期望的結果。

@@ERROR 

返回在當前連接上的最近的T-SQL語句錯誤的數目。如果沒有錯誤,返回0

在每個新的語句下重新設置。如果需要保存這個值,應該立刻把這個值移動到一個局部變量中。

@@IDENTITY 

返回插入的最近的標識值,作爲最近的INSERT或者SELECT INTO語句的結果

如果沒有標識值產生,那麼設置爲NULL。即使缺少標識值是由於一個運行的語句的失敗,也是如此。如果通過一個語句執行多個插入,那麼只返回最後的標識值。

@@REMSERVER 

僅僅在存儲過程中使用。返回稱爲存儲過程的服務器的數值

在希望sproc根據遠程服務器不同表現出不同的行爲時,這個選項是很方便的。

@@ROWCOUNT 

一個最經常使用的系統函數。返回最近的語句所影響的行的數目。

一般在非運行時錯誤檢查時使用。例如,如果嘗試通過使用一個WHERE字句刪除一行,並且沒有行被影響,那麼那將意味着一些不期望的事情發生了。

8.1.3 使用@@IDENTITY

@@IDENTITY是所有的系統函數中最重要的一個。標識列是這樣的列,在那裏沒有提供一個值,而是SQL Server自動地插入一個值。任何的INSERT或者INSERT INTO語句都會更新這個函數的返回值。如果沒有新的標識列被插入,將返回NULL。如果插入了多個行,生成了多個標識值,則@@IDENTITY將返回最後生成的標識值。如果語句觸發了一個或多個觸發器,該觸發器又執行了生成標識值的插入操作,那麼,在語句執行後立即調用@@IDENTITY將返回觸發器生成的最後一個標識值。如果對包含標識列的表執行插入操作後觸發了觸發器,並且觸發器對另一個沒有標識列的表執行了插入操作,則@@IDENTITY將返回第一次插入的標識值。出現INSERTSELECT INTO語句失敗或大容量複製失敗,或者事務被回滾的情況時,@@IDENTITY值不會恢復爲以前的設置。

8.1.4 使用@@ROWCOUNT

@@ROWCOUNT說明上一個SQL語句(SELECTUPDATEINSERTDELETE等)影響了多少行。示例:

USE Northwind

GO

DECLARE @RowCount INT

SELECT * FROM Categories

SELECT @RowCount = @@ROWCOUNT

PRINT 'The value of @@ROWCOUNT was ' + CAST(@RowCount AS VARCHAR(5))

則最後一行顯示:

The value of @@ROWCOUNT was 8

8.2 批處理

批處理是進入一個邏輯單元的T-SQL語句組。一個批處理中的所有語句被組合爲一個執行計劃,因此對所有語句一起進行語法分析,並且必須通過語法驗證,否則將沒有一個語句會執行。但是,這並不能防止運行時錯誤的發生。如果發生運行時錯誤,那麼任何在發生運行時錯誤之前執行的語句將仍然是有效的。簡言之,如果一個語句不能通過語法分析,那麼不會運行任何語句。如果一個語句在運行時失敗,那麼產生錯誤語句之前的所有語句都已經運行了。

可以將一個腳本分開爲多個批處理,方法是使用GO語句。GO語句:

  • 必須自成一行(只有註釋可以在相同的行上)。
  • 使得從腳本或者上一個GO語句開始的所有語句編譯成一個執行計劃併發送到服務器,與任何其他批處理無關。
  • 不是T-SQL命令,而是由各種SQL Server命令實用程序識別的命令。

代碼示例:

USE AdventureWorks

 

DECLARE @MyVarchar VARCHAR(50) – This DECLARE only lasts for this batch!

SELECT @MyVarchar = 'Honey, I''m home…'

PRINT 'Done with first batch…'

GO

 

PRINT @MyVarchar – This generates an error since @MyVarchar isn't declared in this batch

PRINT 'Done with second batch'

GO

 

PRINT 'Done with third batch' – Notice that this still gets executed even after the error

GO

結果如下所示:

Done with first batch…

Msg 137, Level 15, State 2, Line 2

Must declare the scalar variable "@MyVarchar"

Done with third batch

8.2.1 批處理中的錯誤

批處理中的錯誤分成兩類:

  • 語法錯誤
  • 運行時錯誤

如果查詢分析器發現一個語法錯誤,那麼批處理的處理過程會立即取消。因爲語法檢查發生在批處理編譯或者執行之前,所以在語法檢查期間的失敗意味着還沒有批處理被執行。

運行時錯誤的工作方式則不同。因爲任何在遇到運行時錯誤之前執行的語句已經完成了,所以除非是未提交的事務的一部分,否則這些語句所做的任何事情的影響將保留下來。一般而言,運行時錯誤將終止從錯誤發生地方到批處理末端的批處理的執行。

8.2.2 什麼時候使用批處理

批處理有幾個目的,但是所有的批處理具有一個共同點——在腳本中當一些事情必須發生在另外一件事之前或者分開發生時,使用批處理。

 

1. 要求有自己的批處理的語句

有一些命令必須完全是它們自己的批處理的一部分。這些命令包括:

  • CREATE DEFAULT
  • CREATE PROCEDUER
  • CREATE RULE
  • CREATE TRIGGER
  • CREATE VIEW

如果你想在一個腳本中將這些語句中的任意一些和其他的語句進行組合,那麼需要通過使用GO語句將它們分開爲各自的批處理。

注意:

注意,如果DROP一個對象,那麼應該將DROP語句放在它自己的批處理中或者至少和其他DROP語句在一個批處理中。

 

2. 使用批處理建立優先權

使用批處理語句的最可能如果在下一個任務開始之前,需要全部完成上一個任務。例如,在嘗試使用新數據庫時,需要先完成CREATE DATABASE語句:

CREATE DATABASE Test

GO

    

USE Test

CREATE TABLE TestTable

(

col1 INT,

col2 INT

)

另外,當使用ALTER TABLE語句顯著地修改一個列的類型或者添加列時,直到執行修改任務的批處理已經完成時,才能利用這些變化。

USE Test

 

ALTER TABLE TestTable

ADD col3 INT

GO

 

INSERT INTO TestTable(col1, col2, col3)

VALUES (1, 1, 1)

第9章 存儲過程和流控制語句

存儲過程(stored procedure)有時也稱爲sproc。存儲過程存儲於數據庫中而不是在單獨的文件中,有輸入參數、輸出參數以及返回值等。

9.1 創建存儲過程:基本語法

在數據庫中,創建存儲過程和創建其他對象的過程一樣,除了它使用的AS關鍵字外。存儲過程的基本語法如下:

CREATE PROCDUER|PROC <sproc name>

    [<parameter_name>[schema.]<data_type> [VARYING][=<default_value>][OUT [PUT]][,

    [<parameter_name>[schema.]<data_type> [VARYING][=<default_value>][OUT [PUT]][,

    ...]]

AS

    <code>

示例:

USE Northwind

GO

CRREATE PROC spShippers

AS

    SELECT * FROM Shippers

執行這個存儲過程:

EXEC spShippers

9.2 使用ALTER改變存儲過程

當使用T-SQL編輯存儲過程的時候,需要記住的是它完全取代了現存的存儲過程。使用ALTER PROCCREATE PROC的區別在於:

  • ALTER PROC期望找到現存的存儲過程,而CREATE則不是。
  • ALTER PROC保留了已經建立的存儲過程的任何權限。它在系統對象中保留了相同的對象ID並允許保留依賴關係。
  • ALTER PROC在其他對象上保留了任何依賴關係的信息,這些對象可以調用修改的存儲過程。

注意:

如果執行DROP,然後使用CREATE,這和使用ALTER PROC語句一樣,幾乎都能得到相同的效果,除了一個很重要的區別——如果使用DROPCREATE,則需要完全重新建立權限,權限規定了可以使用以及不能使用存儲過程的用戶。

9.3 刪除存儲過程

這個過程非常簡單:

DROP PROC|PROCEDURE <sproc name>

9.4 參數化(Parameterization)

聲明參數

聲明參數需要以下24部分信息:

  • 名稱
  • 數據類型
  • 默認值
  • 方向

語法如下:

@parameter_name [AS] datatype[= default|NULL] [VARYING] [OUTPUT|OUT]

名稱有一個簡單的規則集合。首先,它必須以@開始。此外,命名規則除了不能有嵌套的空格外,它和SQL的命令規則是相同的。

數據類型可以使用SQL Server內置的或用戶自定義的類型。

注意:

  • 聲明CURSOR類型參數的時候,必須也使用VARYING和OUTPUT選項。
  • OUTPUT可以簡寫爲OUT。

示例:

USE Northwind

GO

CREATE PROC spInsertShipper

    @CompanyName NVARCHAR(40),

    @Phone NVARCHAR(24)

AS

    INSERT INTO Shippers

    VALUES

        (@CompanyName, @Phone)

可以使用這個新的存儲過程來插入新的數據:

EXEC spInstertShipper 'Speedy Shippers, Inc.', '(503)555-5566'

因爲並沒有爲任何參數提供默認值,所以需要提供兩個參數。這意味着爲了成功運行該存儲工程,則必須提供兩個參數。

 

1. 提供默認值

示例:

USE Northwind

GO

 

CREATE PROC spInsertShipperOptionalPhone

    @CompanyName NVARCHAR(40),

    @Phone NVARCHAR(24) = NULL

AS

    INSERT INTO Shippers

    VALUES (@CompanyName, @Phone)

重新發出命令,但是使用新的存儲過程:

EXEC spInsertShipperOptionalPhone 'Speedy Shippers, Inc'

這次一切順利,成功插入了新的紀錄。

 

2. 創建輸出參數

示例:

USE Northwind

GO

 

CREATE PROC spInsertOrder

    @CustomerID NVARCHAR(5),

    @EmployeeID INT,

    @OrderDate DATETIME = NULL,

    @RequiredDate DATETIME = NULL,

    @ShippedDate DATETIME = NULL,

    @ShipVia INT,

    @Freight MONEY,

    @ShipName NVARCHAR(40) = NULL,

    @ShipAddress NVARCHAR(60) = NULL,

    @ShipCity NVARCHAR(15) = NULL,

    @ShipRegion NVARCHAR(15) = NULL,

    @ShipPostalCode NVARCHAR(10) = NULL,

    @ShipCountry NVARCHAR(15) = NULL,

    @OrderID INT OUTPUT

AS

    INSERT INTO Orders

    VALUES

    (

        @CustomerID,

        @EmployeeID,

        @OrderDate,

        @RequiredDate,

        @ShippedDate,

        @ShipVia,

        @Freight,

        @ShipName,

        @ShipAddress,

        @ShipCity,

        @ShipRegion,

        @ShipPostalCode,

        @ShipCountry

    )

SELECT @OrderID = @@IDENTITY

執行該存儲過程的代碼如下:

USE Northwind

GO

 

DECLARE @MyIdent INT

 

EXEC spInsertOrder

    @CustomerID = 'ALFKI',

    @EmployeeID = 5,

    @OrderDate = '5/1/1999'

    @ShipVia = 3,

    @Freight = 5.00,

    @OrderID = @MyIdenty OUTPUT

 

SELECT @MyIdent AS IdentityValue

 

SELECT OrderID, CustomerID, EmployeeID, OrderDate, ShipName

FROM Orders

WHERE OrderID = @MyIdent

需要注意以下幾點:

  • 在存儲過程聲明中,輸出參數需要使用OUTPUT關鍵字。
  • 調用存儲過程的時候也必須使用OUTPUT關鍵字,才能保證參數被正確的輸出。注意如果沒有使用OUTPUT關鍵字,不會產生任何錯誤,但是此時輸出參數的值將是無法保證的。
  • 賦給輸出變量的變量不需要和存儲過程中的內部參數擁有相同的名稱。例如在本例中,內部參數叫做@OrderID,而傳給值的變量叫做@MyIdent
  • 需要使用EXEC(或EXECUTE)關鍵字來調用存儲過程。

9.5 流控制語句

T-SQL提供了大多數流控制語句的典型的選擇,同樣也有CASE語句,但是它沒有像其他語言中預期的那種流控制級的能力。

9.5.1 IF...ELSE語句

IF...ELSE語句的實現方式和C語言是接近相同的。基本的語法如下:

IF <Boolean Expression>

    <SQL statement> | BEGIN <code series> END

[ELSE

    <SQL statement> | BEGIN <code series> END]

其中的表達式可以是取布爾值的任意表達式。

提示:

不恰當的使用NULL值是個常見的陷阱。例如經常會有如下錯誤出現:

IF @MyVar = NULL

在大多數系統上(遵循ANSI標準)這樣的表達式永遠都不會爲真,並且爲繞過所有的NULL值結束。想要判斷一個值是否爲空應該這樣來寫:

IF @MyVar IS NULL

不要忘記了NULL不等於任何值——甚至是NULL。不要使用"="而要使用"IS"。

 

1. ELSE子句

注意:

結果返回值爲NULL的表達式會被當作FALSE從而進入ELSE子句。也就是說,如果IF子句中的語句返回值爲FALSE或者NULL,則執行ELSE子句中的語句。

 

示例:

USE Northwind

GO

 

ALTER PROC spInsertOrder

    @CustomerID NVARCHAR(5),

    @EmployeeID INT,

    @OrderDate DATETIME = NULL,

    @RequiredDate DATETIME = NULL,

    @ShippedDate DATETIME = NULL,

    @ShipVia INT,

    @Freight MONEY,

    @ShipName NVARCHAR(40) = NULL,

    @ShipAddress NVARCHAR(60) = NULL,

    @ShipCity NVARCHAR(15) = NULL,

    @ShipRegion NVARCHAR(15) = NULL,

    @ShipPostalCode NVARCHAR(10) = NULL,

    @ShipCountry NVARCHAR(15) = NULL,

    @OrderID INT OUTPUT

AS

    DECLARE @InsertedOrderDate SMALLDATETIME

    

    IF DATEDIFF(dd, @OrderDate, GETDATE()) > 7

        SELECT @InsertedOrderDate = NULL

    ELSE

        SELECT @InsertedOrderDate =

            CONVERT(DATETIME, CONVERT(VARCHAR, @OrderDate, 112))

    

    INSERT INTO Orders

    VALUES

    (

        @CustomerID,

        @EmployeeID,

        @OrderDate,

        @RequiredDate,

        @ShippedDate,

        @ShipVia,

        @Freight,

        @ShipName,

        @ShipAddress,

        @ShipCity,

        @ShipRegion,

        @ShipPostalCode,

        @ShipCountry

    )

    

    SELECT @OrderID = @@IDENTITY

 

2. 把代碼分組爲塊

SQL Server提供了把代碼分組爲塊的方法,可以認爲這個塊是屬於一起的。這個塊以BEGIN語句開始,然後直到END語句結束。

現在可以修改訂單插入的存儲過程如下:

USE Northwind

GO

 

ALTER PROC spInsertOrder

    @CustomerID NVARCHAR(5),

    @EmployeeID INT,

    @OrderDate DATETIME = NULL,

    @RequiredDate DATETIME = NULL,

    @ShippedDate DATETIME = NULL,

    @ShipVia INT,

    @Freight MONEY,

    @ShipName NVARCHAR(40) = NULL,

    @ShipAddress NVARCHAR(60) = NULL,

    @ShipCity NVARCHAR(15) = NULL,

    @ShipRegion NVARCHAR(15) = NULL,

    @ShipPostalCode NVARCHAR(10) = NULL,

    @ShipCountry NVARCHAR(15) = NULL,

    @OrderID INT OUTPUT

AS

    DECLARE @InsertedOrderDate SMALLDATETIME

    

    IF DATEDIFF(dd, @OrderDate, GETDATE()) > 7

    BEGIN

        SELECT @InsertedOrderDate = NULL

        PRINT 'Invalid Order Date'

        PRINT 'Supplied Order Date was greater than 7 days old.'

        PRINT 'The value has been reset to NULL'

    END

    ELSE

    BEGIN

        SELECT @InsertedOrderDate =

            CONVERT(DATETIME, CONVERT(VARCHAR, @OrderDate, 112))

        PRINT 'The time of Day in Order Date was truncated'

    END

    

    INSERT INTO Orders

    VALUES

    (

        @CustomerID,

        @EmployeeID,

        @OrderDate,

        @RequiredDate,

        @ShippedDate,

        @ShipVia,

        @Freight,

        @ShipName,

        @ShipAddress,

        @ShipCity,

        @ShipRegion,

        @ShipPostalCode,

        @ShipCountry

    )

    

    SELECT @OrderID = @@IDENTITY

9.5.2 CASE語句

CASE語句在某種程度上與一些編程語言中的一些不同語句是等價的。例如:

  • C、C++、Delphi中的switch
  • Visual Basic中的select case
  • COBOL中的evaluate

在T-SQL中使用CASE語句的一個很大的缺點是:在很多方面,它更像替換運算符而非流控制語句。

編寫CASE語句的方式不只一種——可以使用輸入表達式或者布爾表達式。第一種方法是使用一個輸入表達式來與每個WHEN子句中用到的值進行比較。SQL Server文檔把這種方法稱爲簡單CASE:

CASE <input expression>

WHEN <when expression> THEN <result expression>

[...n]

[ELSE <result expression>]

END

第二種方法將提供一個表達式,其中每個WHEN子句的值將爲TRUE或者FALSE。相關文檔把它稱爲搜索CASE:

CASE

WHEN <Boolean expression> THEN <result expression>

[...n]

[ELSE <result expression>]

END

可以使用CASE語句最好的方式是把它與SELECT語句放一起使用。

 

1. 簡單CASE

簡單CASE使用結果等於布爾值的表達式。示例:

USE Northwind

GO

 

SELECT TOP 10 OrderID, OrderID % 10 AS 'Last Digit', Position =

CASE OrderID % 10

    WHEN 1 THEN 'First'

    WHEN 2 THEN 'Second'

    WHEN 3 THEN 'Third'

    WHEN 4 THEN 'Fourth'

    ELSE 'Something Else'

END

FROM Orders

 

2. 搜索CASE

搜索CASE語句和簡單CASE語句非常相同,它只有兩個很細微的不同點:

  • 沒有輸入表達式。
  • WHEN表達式必須爲布爾值。

示例:

USE Northwind

GO

 

SELECT TOP 10 OrderID % 10 AS "Last Digit", ProductID, "How Close?" =

    CASE

        WHEN (OrderID % 10) < 3 THEN 'Ends with less than three'

        WHEN ProductID = 6 THEN 'ProductID is 6'

        WHEN ABS(OrderID % 10 - ProductID) <= 1 THEN 'Within 1'

        ELSE 'More than one apart'

    END

FROM OrderDetails

WHERE ProductID < 10

ORDER BY OrderID DESC

 

注意SQL Server求值的工作方式:

  • 即使兩個條件都爲真,但只使用第一個條件。
  • 不需要使用"break"語句,在一個條件滿足後自動終止。
  • 可以在條件表達式中混合和匹配正在使用的字段。
  • 只要最後等於布爾值的結果,則可以執行任何表達式。

9.5.3 使用WHILE語句循環

語法如下:

WHILE <boolean expression>

    <sql statement> |

[BEGIN

    <statement block>

    [BREAK]

    <sql statement>|<statement block>

    [CONTINUE[

END]

在WHILE語句中必須跟上BEGIN...END,其中包含整個語句塊。

9.6 通過返回值確認成功或失敗

返回值指示了存儲過程的成功或者失敗,甚至是成功或失敗的範圍或屬性。

 

RETURN的工作方式

不管是否提供返回值,程序都會收到一個返回值。SQL Server默認地會在完成存儲過程時自動返回0。

爲了從存儲過程向調用代碼返回值,只需要使用RETURN語句:

RETURN [<integer value to return>]

注意:

  • 返回值必須是整數。
  • RETURN語句是無條件地從存儲過程中退出的。

示例:

USE Northwind

GO

 

CREATE PROC spTestReturns

AS

    DECLARE @MyMessage VARCHAR(50)

    DECLARE @MyOtherMessage VARCHAR(50)

    

    SELECT @MyMessage = 'Hi, it''s that line before the RETURN'

    PRINT @MyMessage

    RETURN

    SELECT @MyOtherMessage = 'Sorry, but we won''t get this far'

    PRINT @MyOtherMessage

    RETURN

爲了能獲取RETURN語句的值,需要在EXEC語句中把值賦給變量。

DECLARE @Return INT

 

EXEC @Return = spTestReturns

SELECT @Return

直接RETURN會默認返回0,需要返回其他整數可以直接寫RETURN <integer>。

第10章 用戶自定義函數

用戶自定義函數和存儲過程非常相似,但它們也有一些行爲和能力的區別。

10.1用戶自定義函數的定義

用戶自定義函數(UDF)是有序的T-SQL語句集合,該語句集合能夠預先優化和編譯,並且可以作爲一個單元來調用。它和存儲過程的主要區別在於返回結果的方式。爲了能支持多種不同的返回值,UDF比存儲過程有更多地限制。

可以在使用存儲過程的時候傳入參數,也可以以參數的形式得到返回值。存儲過程可以返回值,不過該值是爲了指示成功或失敗的,而非返回數據。

然而,可以在使用UDF的時候傳入參數,但是可以不傳出任何值。UDF還可以返回標量(scalar)值,這個值可以是大部分SQL Server的數據類型。UDF還可以返回表。

按照返回值的類型,UDF有兩種類型:

  • 返回標量的UDF
  • 返回表的UDF

10.2 返回標量值的UDF

這種類型的UDF和大多數SQL Server內建的函數一樣,會向調用腳本或存儲過程返回標量值,例如GETDATE()USER()函數就會返回標量值。

UDF可以返回除了BLOBCURSORTIMESTAMP以外的任何SQL Server中有效的數據類型(包含用戶自定義類型)。如果想返回整數,UDF也和存儲過程不同的是:

  • UDF返回值的目的是提供有意義的數據,而不是說明成功或失敗。
  • 在查詢中可以內聯地執行函數,而使用存儲過程則不行。

示例——返回去掉時分秒的日期:

CREATE FUNCTION DayOnly(@Date DATETIME)

RETURNS VARCHAR(12)

AS

BEGIN

    RETURN CONVERT(VARCHAR(12), @Date, 101)

END

函數的使用方法如下:

SELECT *

FROM Orders

WHERE DayOnly(OrderDate) = DayOnly(GETDATE())

在一個UDF中調用另一個UDF

CREATE FUNCTION AveragePrice()

RETURNS MONEY

WITH SCHEMABINDING

AS

BEGIN

    RETURN (SELECT AVG(Price) FROM Titles)

END

GO

 

CREATE FUNCTION PriceDifference(@Price MONEY)

RETURN MONEY

AS

BEGIN

    RETURN @Price – AveragePrice()

END

使用UDF可以大大增加查詢語句的可讀性,並實現了代碼重用:

USE pubs

SELECT Title,

    Price,

    AveragePrice() AS Average,

    PriceDifference(Price) AS Difference

FROM Titles

WHERE Type = 'popular_comp'

10.3 返回表的UDF

可以對UDF返回的表執行JOIN,甚至對結果應用WHERE條件。相對簡單的函數示例如下:

USE pubs

GO

 

CREATE FUNCTION fnAuthorList()

RETURN TABLE

AS

RETURN (

SELECT au_id,

    au_lname + ', ' + au_fname AS au_name

    address AS address1,

    city + ', ' + state + ', ' + zip AS address2

FROM authors

)

GO

這樣的話,使用這個函數就像使用表一樣:

SELECT *

FROM fnAuthorList()

使用返回表的UDF比使用視圖的好處在於可以在UDF中將條件參數化,而視圖不得不包含不想要的數據,然後再通過WHERE子句過濾。例如:

USE pubs

GO

 

CREATE FUNCTION fnSalesCount(@SalesQty BIGINT)

RETURNS TABLE

AS

RETURN (

SELECT au.au_id,

    au.aulname + ', ' + au.au_fname AS au_name,

    au.address AS address1,

    city + ', ' + state + ', ' + zip AS address2,

    SUM(s.qty) AS SalesCount

FROM authors au

INNER JOIN titleauthor ta

    ON au.au_id = ta.au_id

INNER JOIN sales s

    ON ta.title_id = s.title_id

GROUP BY au.au_id,

    au.au_lname + ', ' + au.au_fname,

    au.address,

    au.city + ', ' + au.state + ', ' + zip

HAVING SUM(qty) > @SalesQty

)

爲了執行該函數,只需要調用它並提供參數:

SELECT *

FROM fnSalesCount(25)

再進一步,如果需要查詢每一個銷售超過25本書以上的作者和出版社的信息,這需要連接UDF返回的表:

SELECT DISTINCT p.pub_name, a.au_name

FROM dbo.fnSalesCount(25) AS a

INNER JOIN titleauthor AS ta

    ON a.au_id = ta.au_id

INNER JOIN titles AS t

    ON ta.title_id = t.title_id

INNER JOIN publishers AS p

    ON t.pub_id = p.pub_id

這裏對函數進行了連接,就好像它是表或視圖一樣。唯一的區別在於可以對它進行參數化。

再進一步,UDF也可以遞歸調用,並同樣存在最深32層的限制。

第11章 事務和鎖

11.1 事務

事務是關於原子性(atomicity)的。原子性的概念是指可以把一些東西當作一個單元來看待。

事務要有非常明確的開始和結束點。事實上,在SQL Server中發出的每一個SELECTINSERTUPDATEDELETE語句都是隱式事務的一部分。即使只發出一條語句,也會把這條語句當作一個事務——要麼執行語句中的所有內容,要麼什麼都不執行。確實,這一條語句默認地將作爲事務的長度。

關於事務的操作有:

  • BEGIN事務:設置起始點。
  • COMMIT事務:使得事務成爲數據庫中永久的、不可撤回的一部分。
  • ROLLBACK事務:本質上說想要忘記它曾經發生過。
  • SAVE事務:創建一個特有的標記符,從而可以做部分的回滾工作。

11.1.1 BEGIN TRAN

語法如下:

BEGIN TRAN|TRANSACTION [<transaction name>|<@transaction variable>]

11.1.2 COMMIT TRAN

事務的提交是完成事務的終點。COMMIT的語法類似於BEGIN

COMMIT TRAN|TRANSACTION [<transaction name>|<@transaction variable>]

11.1.3 ROLLBACK TRAN

ROLLBACK可以回到開始的地方或者其中的任何一個保存點。ROLLBACK的語法如下:

ROLLBACK TRAN|TRANSACTION [<transaction name> | <save point name> | <@transaction variable> | <@savepoint variable>]

11.1.4 SAVE TRAN

保存事務從本質上說是創建書籤。在建立"書籤"之後,可以在回滾中引用它。它的好處是可以回滾到代碼中想要的點上。SAVE的語法如下:

SAVE TRAN|TRANSACTION [<save point name>|<@savepoint variable>]

關於保存點需要記住的是ROLLBACK會清除它們——執行ROLLBACK後之前保存過的保存點都會消失。

11.2 SQL Server記錄日誌的工作方式

在數據庫的正常操作中,大多數執行的活動都是"記錄"在事務日誌上,而非直接寫入數據庫中。檢查點是指強制地把數據庫現在所使用的髒頁寫入磁盤的週期性操作。髒頁是指日誌或數據頁,它們在讀入到緩存後已經被修改,但是所進行的修改還沒有寫入到磁盤。

11.2.1 失敗和恢復

恢復發生在SQL Server每次啓動的時候。SQL Server獲得數據庫文件,並且在最後的檢查點以後應用日誌中的任何提交的改變。日誌中任何沒有對應提交的改變都會回滾。

第12章 觸發器

一些常見的使用觸發器的情況包括:

  • 實施參照完整性,例如數據庫或服務器中的參照完整性以及許多複雜的關係類型。
  • 創建審計跟蹤。
  • CHECK約束的功能相似,但是用於表、數據庫、甚至是服務器之間。
  • 用自己的語句代替用戶的操作語句(通常用於允許複雜語句中的插入操作)。

12.1 觸發器的概念

觸發器是一種特殊類型的存儲過程,響應特定的事件。有兩種類型的觸發器:數據定義語言(DDL)觸發器和數據操作語言(DML)觸發器。

DDL觸發器激活了人們以某些方式(CREATEALTERDROP等)對數據庫結構進行修改的響應。DML觸發器是一些加在特殊表或試圖上的代碼片段。只要加在觸發器上的事件在表中發生,觸發器中的代碼就會自動地運行。不能顯式地調用觸發器——唯一的做法是執行指派給表所需的操作。觸發器也沒有參數和返回值,因爲都不需要。

SQL Server中可以使用3種類型的觸發器,並可以相互混合和匹配:

  • INSERT觸發器
  • DELETE觸發器
  • UPDATE觸發器

注意,有些語句不會激活觸發器,比如TRUNCATE TABLE有與DELETE語句相似的刪除行的效果,但是不會觸發任何DELETE觸發器。

除了觸發器需要加在一個表上外,創建觸發器的語法類似於其他CREATE語法:

CREATE TRIGGER <trigger name>

    ON [<schema name>.]<table or view name>

    [WITH ENCRYPTION]

    {{{FOR | ALTER} [DELETE] [,] [INSERT} [,] [UPDATE]}}

AS

    <sql statements>

12.1.1 ON子句

對創建觸發器的對象進行命名。注意如果觸發器的類型是AFTER(或FOR)觸發器,那麼ON字句的目標就必須是一個表(而不能是視圖),視圖只接受INSTEAD OF觸發器。

12.1.2 WITH ENCRYPTION子句

加密觸發器代碼。注意ALTER語句不會自動加密,如需加密需要再次指明WITH ENCRYPTION選項。

12.1 3 FOR|AFTER子句

還需要對激活觸發器的定時時間做出選擇。雖然可以使用長期接觸的FOR觸發器(也可以使用關鍵字ATFER來替換),而且這也是人們經常考慮的一種觸發器,但是也可以使用INSTEAD OF觸發器。對這兩種觸發器的選擇將影響到是在修改數據之前還是之後來輸入觸發器。

SQL Server會將兩張表放在一起——其中的INSERTED表保存插入記錄的副本,另一張DELETED表保存刪除的任何記錄的副本。使用INSTEAD OF觸發器,創建這兩張工作表是發生在檢查任何約束之前,而使用FOR觸發器,這些表的創建是發生在檢查約束之後。使用INSTEAD OF觸發器的重點在於可以在視圖中清除任何不確定的插入問題。這也意味着在檢查約束之前可以採取運動清除違反約束的情況。

使用FORATFER聲明的觸發器,與INSTEAD OF觸發器最大的區別在於它們是在檢查完約束之後建立工作表的。

FORAFTER)子句指明瞭想要在哪種動作下激活觸發器。例如:

FOR INSERT, DELETE

注意之前提到過,使用FORAFTER子句聲明的觸發器只能加在表上,而不允許加在視圖上。

 

1. INSERT觸發器

每當有人向表中插入全新的數據行的時候,都會執行在代碼中通過FOR INSERT標記聲明的觸發器的代碼。對於插入的每一行來說,SQL Server會創建該新行的副本並把它插入到稱爲INSERTED的表中,該表只在觸發器的作用域內存在。

 

2. DELETE觸發器

每個刪除的額記錄的副本將插入到成爲DELETED表中,該表同樣只在觸發器的作用域內存在。

 

3. UPDATE觸發器

SQL Server會把每一行當作先刪除了現有的記錄,並插入了全新的行,所以INSERTEDDELETED表均存在。當然,這兩個表會有完全相同數量的數據行。而DELETED表中的爲改變前的數據,INSERTED表中爲改變後的數據。

12.2 爲了數據完整性規則使用觸發器

觸發器可以完成CHECK約束和DEFAULT約束一樣的功能,但可以使用CHECK約束和DEFAULT約束完成的功能不應該再設置觸發器。但觸發器還是可以完成更多的功能:

  • 業務規則需要引用單個表中的數據。
  • 業務規則需要檢查更新的增量(更新前後的區別)。
  • 需要一個定製的錯誤信息。

12.2.1 處理來自於其他表的需求

例如,客戶支持部門的人員不斷髮出已經停止供應的產品的訂單,應該在訂單進入系統之前拒絕這些訂單的錄入。

CREATE TRIGGER OrderDetailNotDiscontinued

    ON OrderDetails

    FOR INSERT, UPDATE

AS

    IF EXISTS (

        SELECT 'TRUE'

        FROM Inserted i

        INNER JOIN Products p

            ON i.ProductID = p.ProductID

        WHERE p.Discontinued = 1)

    BEGIN

        PAISERROR('Order Item is discontinued. Transaction Failed.', 16, 1)

        ROLLBACK TRAN

    END

12.2.2 使用觸發器來檢查更新的增量

例如,Northwind的存貨部門要求不能發出任何銷售某個產品超過其一般庫存單位的訂單。

CREATE TRIGGER ProductIsRationed

    ON Products

    FOR UPDATE

AS

    IF EXISTS (

        SELECT 'TRUE'

        FROM Inserted i

        INNER JOIN Deleted d

            ON i.ProductID = d.ProductID

        WHERE (d.UnitsInStock – i.UnitsInStock) > d.UnitsInStock / 2

            AND d.UnitsInStock – i.UnitsInStock > 0

    )

    BEGIN

        RAISERROR('Cannot reduce stock by more than 50%% at onece.', 16, 1)

        ROLLBACK TRAN

    END

12.3 可以關閉觸發器而不刪除它

可以使用ALTER TABLE語句來打開或關閉觸發器,語法如下:

ALTER TABLE <table name>

{ENABLE|DISABLE} TRIGGER {ALL|<trigger name>}

12.4 刪除觸發器

和刪除其他對象一樣:

DROP TRIGGER <trigger name>

轉載地址爲http://www.cnblogs.com/qwertWZ/archive/2013/05/05/3061743.html
發佈了16 篇原創文章 · 獲贊 5 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章