第三部分 操作資料:SQL
本部分內容
n SQL基礎
n 中級SQL
n 進階SQL
第10章 SQL基礎
本章內容
SQL介紹
使用SELECT 語句從表中取資料
建立新表
欄位屬性
向表中加入資料
刪除和修改表
為了建立互動網站,你需要使用資料庫來存儲來自存取者的資訊。例如,你要建立一個職業介紹服務的網站,你就需要存儲諸如個人簡曆,所感興趣的工作等等這樣的資訊。建立動態網葉也需要使用資料庫,如果你想顯示符合來訪者要求的最好的工作,你就需要從資料庫中取出這份工作的資訊。你將會發現,在許多情況下需要使用資料庫。
在這一章�堙A你將學會怎樣使用“結構化查詢語言”(SQL〕來操作資料庫。SQL語言是資料庫的標準語言。在Active Sever
Pages 中,無論何時你要存取一個資料庫,你就要使用SQL語言。因此,掌握好SQL對ASP程式設計是非常重要的。
注意:
你可以把“SQL”讀作“sequel”,也可以按單個字母的讀音讀作S-Q-L。 兩種發音都是正確的,每種發音各有大量的支援者。在本書�堙A認為“SQL”讀作“sequel”。
通過這一章的學習,你將理解怎樣用SQL實現資料庫查詢,你將學會怎樣使用這種查詢從資料表中取出資訊,最後,你將學會怎樣設計和建立自己的資料庫。
注意:
通過下面幾章對SQL的介紹,你將對SQL有足夠的了解,從而可以有效地使用Active Sever
Pages。但是,SQL是一種複雜的語言,本書不可能包括它的全部細節。要全面掌握SQL語言,你需要學習在Microsoft SQL
Sever 中使用SQL。你可以到附近的書店去買一本Microsoft SQL
Sever 6.5。
SQL介紹:
本書假設你是在SQL操作Microsoft SQL
Sever 的資料庫。你也可以用SQL操作許多其它型式的資料庫。SQL是操作資料庫的標準語言。(事實上,關於SQL語言有一個專門的ANSI標準〕
注意:
不要在你的網站上試圖用Microsoft
Access代替Microsoft SQL
Sever。SQL Sever可以同時服務於許多用戶,如果你希望你的網站有較高的存取率,MS
Access是不能勝任的。
在學習SQL的細節之前,你需要理解它的兩大特點。一個特點容易掌握,另一個掌握起來有點困難。
第一個特點是所有SQL資料庫中的資料都存儲在表中。一個表由行和列群群組成。例如,下面這個簡單的表包括name
和e-mail address:
Name
Email Address
................................................................
Bill
Gates billg@microsoft.com
president Clinton
president@whitehouse.com
Stephen
Walther swalther@somewhere.com
這個表有兩列(列也稱為欄位,欄位〕:Name和Email Address。有三行,每一行包含一群群組資料。一行中的資料群群組合在一起稱為一條記錄。
無論何時你向表中加入新資料,你就加入了一條新記錄。一個資料表可以有幾十個記錄,也可以有幾千甚至幾十億個記錄。雖然你也許永遠不需要存儲十億個Email位址,但知道你能這樣做總是好的,也許有一天你會有這樣的需要。
你的資料庫很有可能包含幾十個表,所有存儲在你資料庫中的資訊都被存儲在這些表中。當你考慮怎樣把資訊存儲在資料庫中時,你應該考慮怎樣把它們存儲在表中。
SQL的第二個特點有些難於掌握。這種語言被設計為不允許你按照某種特定的順序來取出記錄,因為這樣做會降低SQL Sever取記錄的效率。使用SQL,你只能按查詢條件來讀取記錄。
當考慮如何從表中取出記錄時,自然會想到按記錄的位置讀取它們。例如,也許你會嘗試通過一個回圈,逐個記錄地掃描,來選出特定的記錄。在使用SQL時,你必須訓練自己,不要有這種思路。
假如你想選出所有的名字是“Bill Gates”的記錄,如果使用傳統的程式設計語言,你也許會構造一個回圈,逐個查看表中的記錄,看名字欄位是否是“Bill Gates”。
這種選擇記錄的方法是可行的,但是效率不高。使用SQL,你只要說,“選擇所有名字欄位等於Bill Gates的記錄”,SQL就會為你選出所有符合條件的記錄。SQL會確定實現查詢的最佳方法。
建設你想取出表中的前十個記錄。使用傳統的程式設計語言,你可以做一個回圈,取出前十個記錄後結束回圈。但使用標準的SQL查詢,這是不可能實現的。從SQL的角度來說,在一個表中不存在前十個記錄這種概念。
開始時,當你知道你不能用SQL實現某些你感覺應該能實現的功能,你會受到挫折。你也許會以頭撞牆甚至想寫惡毒的信件給SQL的設計者們。但後來你會認識到,SQL的這個特點不僅不是個限制,反而是其長處。因為SQL不根據位置來讀取記錄,它讀取記錄可以很快。
綜上所述,SQL有兩個特點:所有資料存儲在表中,從SQL的角度來說,表中的記錄沒有順序。在下一節,你將學會怎樣用SQL從表中選擇特殊的記錄。
使用SQL從表中取記錄。
SQL的主要功能之一是實現資料庫查詢。如果你熟悉Internet 引擎,那麼你已經熟悉查詢了。你使用查詢來取得滿足特定條件的資訊。例如,如果你想找到有ASP資訊的全部網站,你可以連接到
Yahoo!並執行一個對Active Sever
Pages的搜索。在你輸入這個查詢後,你會收到一個清單,表中包括所有其描述中包含搜索表達式的網站。
多數Internet 引擎允許邏輯查詢。在邏輯查詢中,你可以包括特殊的運算符如AND、OR和NOT,你使用這些運算符來選擇特定的記錄。例如,你可以用AND來限制查詢結果。如果你執行一個對Active Sever
Pages AND SQL的搜索。你將得到其描述中同時包含Active Sever
Pages 和SQL的記錄。當你需要限制查詢結果時,你可以使用AND。
如果你需要擴展查詢的結果,你可以使用邏輯操作符OR。例如,如果你執行一個搜索,搜索所有的其描述中包含Active Sever
Pages OR SQL的網站,你收到的清單中將包括所有其描述中同時包含兩個表達式或其中任何一個表達式的網站。
如果你想從搜索結果中排除特定的網站,你可以使用NOT。例如,查詢“Active Sever
Pages ”AND NOT “SQL”將返回一個清單,清單中的網站包含Active Sever
Pages,但不包含SQL。當必須排除特定的記錄時,你可以使用NOT。
用SQL執行的查詢與用Internet搜索引擎執行的搜索非常相似。 當你執行一個SQL查詢時,通過使用包括邏輯運算符的查詢條件,你可以得到一個記錄清單。此時查詢結果是來自一個或多個表。
SQL查詢的句法非常簡單。假設有一個名為email_table
的表,包含名字和位址兩個欄位,要得到Bill Gates
的e_mail位址,你可以使用下面的查詢:
SELECT
email from email_table WHERE name="Bill Gates"
當這個查詢執行時,就從名為email_table的表中讀取Bill
Gates的e_mail 位址。這個簡單的語句包括三部分:
■ SELECT語句的第一部分指名要選取的列。在此例中,只有email列被選取。當執行 時,只顯示email列的值 billg@microsoft.com。
■ SELECTT語句的第二部份指明要從哪個(些)表中查詢資料。在此例中,要查詢的表名為email_table
。
■
最後,SELECT語句的WHERE子句指明要選擇滿足什麼條件的記錄。在此例中,查詢條件為只有name列的值為Bill Gates
的記錄才被選取。
Bill
Gates很有可能擁有不止一個email位址。如果表中包含Bill
Gates的多個email位址。用上述的SELECT語句可以讀取他所有的email位址。SELECT語句從表中取出所有name欄位值為Bill Gates
的記錄的email 欄位的值。
前面說過,查詢可以在查詢條件中包含邏輯運算符。假如你想讀取Bill Gates
或Clinton總統的所有email位址,你可以使用下面的查詢語句:
SELECT
email FROM email_table WHERE name="Bill Gates" OR
name="president
Clinton"
此例中的查詢條件比前一個複雜了一點。這個語句從表email_table中選出所有name列為Bill
Gates或president
Clinton的記錄。如果表中含有Bill
Gates或president
Clinton的多個位址,所有的位址都被讀取。
SELECT語句的結構看起來很直觀。如果你請一個朋友從一個表中為你選擇一群群組記錄,你也許以非常相似的方式提出你的要求。在SQL SELECT語句中,你“SELECT特定的列FROM一個表WHERE某些列滿足一個特定的條件”。
下一節將介紹怎樣執行SQL查詢來選取記錄。這將輔助敘述你熟悉用SELECT語句從表中取資料的各種不同方法。
使用ISQL執行SELECT查詢
當你裝設SQL Sever時,你同時裝設了一個叫作ISQL/w的應用程式。ISQL/w允許你執行互動的SQL查詢。在把查詢包括到你的ASP網頁中之前,用ISQL/w對其進行測試是非常有用的。
注意:
在這本書的第一部份,你學習了怎樣裝設和組態Microsoft SQL
Sever 。如果沒有裝設SQL
Sever或者SQL Sever不能執行,請參閱第三章“裝設和使用SQL
Sever”。
選擇任務上SQL Sever程式群群組中的ISQL_w以啟動該程式。程式啟動時,首先會出現一個對話方塊,要求輸入伺服器資訊和登入資訊(見圖10.1)。在Sever框中,輸入你的SQL伺服器的名字。如果伺服器正執行在本地電腦上,伺服器名字就是你電腦的名字。在登入資訊框中,輸入一個登入帳號和密碼或選擇使用“可信連接”,然後按一下Connect按鈕。
圖10。1
注意:
如果你將SQL Sever組態為使用完整安全或混合安全,那麼你可以使用可信連接。如果你使用標準安全,你則需要提供用戶帳號和密碼。要了解更多資訊,參見第三章。
如果一切正常,在你按一下連接按鈕後會出現一個查詢視窗,如圖10.2所示。(如果有異常,請參考第三章)
圖10.2
在執行查詢之前,你需要選擇資料庫。裝設 SQL
Sever時你已為自己建立了一個資料庫,SQL
Sever還有許多系統資料庫,如master,model,msdb,和tempdb。
方便的是,SQL
Sever帶有一個特殊的名為pubs的例子資料庫。庫 pubs中包含供一個虛擬的出版商使用的各個表。文檔中所有的例子程式都是針對這個庫來設計的。本書中的許多例子也使用這個資料庫。
在查詢視窗頂部的DB下拉框中選擇資料庫pubs,這樣你就選擇了資料庫。你所有的查詢都將針對這個庫中的各個表來執行。現在你可以執行你的第一個查詢了。這真讓人興奮!
你的第一個查詢將針對一個名為autrors的表,表中包含所有為某個虛擬出版商工作的作者的相關資料。按一下查詢視窗並輸入以下的語句:
SELECT
phone FROM authors WHERE au_name="Ringer"
輸入完成後,按一下執行查詢按鈕(一個綠色三角形,看起來像VCR播放鍵)。按一下此按鈕後,任何出現在查詢視窗中的語句均會被執行。查詢視窗會自動變成結果顯示視窗,你可以看到查詢的結果(見圖10.3)。
你看到的查詢結果也許與圖10.3所示的不同。在SQL Sever的不同版本中,庫pubs中的資料會有所不同。對SQL Sever 6.5來說,將會找到兩條記錄。結果顯示視窗中應顯示如下內容:
phone
……………….
801
826_0752
801
826_0752
(2
row(s) affected)
圖10.3
你所執行的SELECT語句從表authors中取出所有名字為Ringer的作者的電話號碼。你通過在WHERE子句中使用特殊的選擇條件來限制查詢的結果。你也可以忽略選擇條件,從表中取出所有作者的電話號碼。要做到這一點,按一下Query標籤,返回到查詢視窗,輸入以下的SELECT語句:
SELECT
Phone FROM authors
這個查詢執行後,會取出表authors中的所有電話號碼(沒有特定的順序)。如果表authors中包含一百個電話號碼,會有一百個記錄被取出,如果表中有十億個電話號碼,這十億條記錄都會被取出(這也許需要一些時間)。
表authrs的欄位包括姓,名字,電話號碼,位址,城市,州和郵政編碼。通過在SELECT語句的第一部份指定它們,你可以從表中取出任何一個欄位。你可以在一個SELECT語句中一次取出多個欄位,比如:
SELECT
au_fname ,au_lname, phone FROM authors
這個SELECT語句執行後,將取出這三個列的所有值。下面是這個查詢的結果的一個示例(為了節省紙張,只顯示查詢結果的一部分,其餘記錄用省略號代替):
au_fname au_lname phone
………………………………………………………………………….
Johnson
White 408 496_7223
Marjorie Green 415
986_7020
Cheryl
Carson 415 548_7723
Michael
O’Leary 408
286_2428
…
(23
row(s) affected)
在SELECT語句中,你需要列出多少個欄位,你就可以列出多少。不要忘了把欄位名用逗號隔開。你也可以用星號(*)從一個表中取出所有的欄位。這�埵酗@個使用星號的例子:
SELECT
* FROM authors
這個SELECT語句執行後,表中的所有欄位的值都被取出。你會發現你將在SQL查詢中頻繁使用星號。
技巧:
你可以使用星號來查看一個表的所有列的名字。要做到這一點,只需要在執行完SELECT語句後看一下查詢結果的列標題。
操作多個表
到現在為止,你只嘗試了用一句SQL查詢從一個表中取出資料。你也可以用一個SELECT語句同時從多個表中取出資料,只需在SELECT語句的FROM從句中列出要從中取出資料的表標簽即可:
SELECT
au_lname ,title FROM authors, titles
這個SELECT語句執行時,同時從表authors和表titles中取出資料。從表authors中取出所有的作者名字,從表titles中取出所有的書名。在ISQL/w程式中執行這個查詢,看一下查詢結果。你會發現一些奇怪的出乎意料的情況:作者的名字並沒有和它們所??的書相匹配,而是出現了作者名字和書名的所有可能的群群組合,這也許不是你所希望見到的。
出了什麼差錯?問題在於你沒有指明這兩個表之間的關係。你沒有通過任何方式告訴SQL如何把表和表關聯在一起。由於不知道如何關聯兩個表,伺服器只能簡單地返回取自兩個表中的記錄的所有可能群群組合。
要從兩個表中選出有意義的記錄群群組合,你需要通過建立兩表中欄位的關係來關聯兩個表。要做到這一點的途徑之一是建立第三個表,專門用來描述另外兩個表的欄位之間的關係。
表authors有一個名為au_id的欄位,包含有每個作者的唯一標識。表titles有一個名為title_id的欄位,包含每個書名的唯一標識。如果你能在欄位au_id和欄位title_id 之間建立一個關係,你就可以關聯這兩個表。資料庫pubs中有一個名為titleauthor的表,正是用來完成這個工作。表中的每個記錄包括兩個欄位,用來把表titles和表authors關聯在一起。下面的SELECT語句使用了這三個表以得到正確的結果:
SELECT
au_name,title FROM authors,titles,titleauthor
WHERE
authors.au_id=titleauthor.au_id
AND
titles.title_id=titleauthor.title_id
當這個SELECT語句執行時,每個作者都將與正確的書名相匹配。表titleauthor指明了表authors和表titles的關係,它通過包含分別來自兩個表的各一個欄位實現這一點。第三個表的唯一目的是在另外兩個表的欄位之間建立關係。它本身不包含任何附加資料。
注意在這個例子中欄位名是如何書寫的。為了區別表authors和表titles中相同的欄位名au_id,每個欄位名前面都加上了表名前綴和一個句號。名為author.au_id
的欄位屬於表authors,名為titleauthor.au_id的欄位屬於表titleauthor,兩者不會混淆。
通過使用第三個表,你可以在兩個表的欄位之間建立各種型式的關係。例如,一個作者也許寫了許多不同的書,或者一本書也許由許多不同的作者共同完成。當兩個表的欄位之間有這種“多對多”的關係時,你需要使用第三個表來指明這種關係。
但是,在許多情況下,兩個表之間的關係並不複雜。比如你需要指明表titles和表publishers之間的關係。因為一個書名不可能與多個出版商相匹配,你不需要通過第三個表來指明這兩個表之間的關係。要指明表titles和表publishers之間的關係,你只要讓這兩個表有一個公共的欄位就可以了。在資料庫pubs中,表titles和表publishers都有一個名為pub_id的欄位。如果你想得到書名及其出版商的一個清單,你可以使用如下的語句:
SELECT
title,pub_name FROM titles,publishers
WHERE
titles.pub_id=publishers.pub_id
當然,如果一本書是由兩個出版商聯合出版的,那麼你需要第三個表來代表這種關係。
通常,當你予先知道兩個表的欄位間存在“多對多”關係時,就使用第三個表來關聯這兩個表。反之,如果兩個表的欄位間只有“一對一”或“一對多”關係,你可以使用公共欄位來關聯它門。
操作欄位
通常,當你從一個表中取出欄位值時,該值與建立該表時所定義的欄位名聯繫在一起。如果你從表authors中選擇所有的作者名字,所有的值將會與欄位名au_lname相聯繫。但是在某些情況下,你需要對欄位名進行操作。在SELECT語句中,你可以在內定欄位名後面僅跟一個新名字來取代它。例如,可以用一個更直觀易讀的名字Author Last
Name來代替欄位名au_lname:
SELECT
au_lname "Author Last Name" FROM authors
當這個SELECT語句執行時,來自欄位au_lname的值會與“Author Last
Name”相聯繫。查詢結果可能是這樣:
Author
Last Name
……………………………………………………………………..
White
Green
Carson
O’Leary
Straight
…
(23
row(s) affected)
注意欄位標題不再是au_lname,而是被Author Last
Name所取代。
你也可以通過執行運算,來操作從一個表返回的欄位值。例如,如果你想把表titles中的所有書的價格加倍,你可以使用下面的SELECT語句:
SELECT
price*2 FROM titles
當這個查詢執行時,每本書的價格從表中取出時都會加倍。但是,通過這種途徑操作欄位不會改變存儲在表中的書價。對欄位的運算只會影響SELECT語句的輸出,而不會影響表中的資料。為了同時顯示書的原始價格和漲價後的新價格,你可以使用下面的查詢:
SELECT
price "Original price", price*2 "New price" FROM titles
當資料從表titles中取出時,原始價格顯示在標題Original
price下面,加倍後的價格顯示在標題New
price下面。結果可能是這樣:
original price new
price
……………………………………………………………….
99. 39.98
11.95 23.90
99. 5.98
99. 39.98
…
(18 row(s)
affected)
你可以使用大多數標準的數學運算符來操作欄位值,如加(+),減(-),乘(*)和除(/)。你也可以一次對多個欄位進行運算,例如:
SELECT
price*ytd_sales "total revenue" FROM titles
在這個例子中,通過把價格與銷售量相乘,計算出了每種書的總銷售額。這個SELECT語句的結果將是這樣的:
total
revenue
……………………………………………..
81,859,05
46,318,20
55,978,78
81,859,05
40,619,68
…
(18
row(s) affected)
最後,你還可以使用連接運算符(它看起來像個加號)來連接兩個字元型欄位:
SELECT
au_fname+" "+au_lname "author name" FROM authors
在這個例子中,你把欄位au_fname和欄位au_lname貼上去在一起,中間用一個逗號 隔開,並把查詢結果的標題指定為author name。這個語句的執行結果將是這樣的:
author
names
…………………………………………………………
Johnson
White
Marjorie
Green
Cheryl
Carson
Michael
O’Leary
Dean
Straight
…
(23
row(s) affected)
可以看到,SQL為你提供了對查詢結果的許多控制。你應該在ASP程式設計過程中充分利用這些優點。使用SQL來操作查詢結果幾乎總是比使用有同樣作用的腳本效率更高。
排序查詢結果
本章的介紹中曾強調過,SQL表沒有內在的順序。例如,從一個表中取第二個記錄是沒有意義的。從SQL的角度看來,沒有一個記錄在任何其他記錄之前。
然而,你可以操縱一個SQL查詢結果的順序。在內定情況下,當記錄從表中取出時,記錄不以特定的順序出現。例如,當從表authors中取出欄位au_lname時,查詢結果顯示成這樣:
au_lname
…………………………………….
White
Green
Carson
O’Leary
Straight
…
(23
row(s) affected)
看一列沒有特定順序的名字是很不方便的。如果把這些名字按字母順序排列,讀起來就會容易得多。通過使用ORDER BY子句,你可以強制一個查詢結果按升序排列,就像這樣:
SELECT
au_lname FROM authors ORDER BY au_lname
當這個SELECT語句執行時,作者名字的顯示將按字母順序排列。ORDER
BY子句將作者名字按升序排列。
你也可以同時對多個列使用ORDER
BY子句。例如,如果你想同時按升序顯示欄位au_lname和欄位au_fname,你需要對兩個欄位都進行排序:
SELECT
au_lname,au_fname FROM authors ORDER BY au_lname ,au_fname
這個查詢首先把結果按au_lname欄位進行排序,然後按欄位au_fname排序。記錄將按如下的順序取出:
au_lname
au_fname
…………………………………………………………………….
Bennet
Abraham
Ringer
Albert
Ringer
Anne
Smith
Meander
…
(23
row(s) affected)
注意有兩個作者有相同的名字Ringer。名為Albert
Ringer的作者出現名為Anne
Ringer的作者之前,這是因為姓Albert按字母順序應排在姓Anne之前。
如果你想把查詢結果按相反的順序排列,你可以使用關鍵字DESC。關鍵字DESC把查詢結果按降序排列,如下例所示:
SELECT
au_lname,au_fname FROM authors
WHERE
au_lname=”Ringer” ORDER BY au_lname
,au_fname DESC
這個查詢從表authors中取出所有名字為Ringer的作者記錄。ORDER BY子句根據作者的名字和姓,將查詢結果按降序排列。結果是這樣的:
au_lname
au_fname
……………………………………………………………………………………….
Ringer
Anne
Ringer
Albert
(2
row(s) affectec)
注意在這個表中,姓Anne出現在姓Albert之前。作者名字按降序顯示。
你也可以按數值型欄位對一個查詢結果進行排序。例如,如果你想按降序取出所有書的價格,你可以使用如下的SQL查詢:
SELECT
price FROM titles ORDER BY price DESC
這個SELECT語句從表中取出所有書的價格,顯示結果時,價格低的書先顯示,價格高的書後顯示。
警告:
不是特別需要時,不要對查詢結果進行排序,因為伺服器完成這項工作要費些力氣。這意味著帶有ORDER BY
子句的SELECT語句執行起來比一般的SELECT語句花的時間長。
取出互不相同的記錄
一個表有可能在同一列中有重複的值。例如,資料庫pubs的表authors中有兩個作者的名字是Ringer。如果你從這個表中取出所有的名字,名字Ringer將會顯示兩次。
在特定情況下,你可能只有興趣從一個表中取出互不相同的值。如果一個欄位有重複的值,你也許希望每個值只被選取一次,你可以使用關鍵字DISTINCT來做到這一點:
SELCET
DISTINCT au_lname FROM authors WHERE au_lname="Ringer"
當這個SELECT語句執行時,只返回一個記錄。通過在SELECT語句中包含關鍵字DISTINCT,你可以刪除所有重複的值。例如,假設有一個關於新聞群群群組資訊發佈的表,你想取出所有曾在這個新聞群群群組中發佈資訊的人的名字,那麼你可以使用關鍵字DISTINCT。每個用戶的名字只取一次????儘管有的用戶發佈了不止一篇資訊。
警告:
如同ORDER BY子句一樣,強制伺服器返回互不相同的值也會增加執行開銷。福氣不得不花費一些時間來完成這項工作。因此,不是必須的時候不要使用關鍵字DISTINCT。
建立新表
前面說過,資料庫中的所有資料存儲在表中。資料表包括行和列。列決定了表中資料的型式。行包含了實際的資料。
例如,資料庫pubs中的表authors有九個欄位。其中的一個欄位名為為au_lname,這個欄位被用來存儲作者的名字資訊。每次向這個表中加入新作者時,作者名字就被加入到這個欄位,產生一條新記錄。
通過定義欄位,你可以建立一個新表。每個欄位有一個名字和一個特定的資料型式(資料型式在後面的“欄位型式”一節中講述),例如欄位au_lname存儲的是字元型資料。一個欄位也可以存儲其它型式的資料。
使用SQL Sever,建立一個新表的方法是很多的。你可以可執行一個SQL語句或使用SQL事務管理器(SQL Enterprise
Manager)來建立一個新表。在下一節�堙A你將學會如何用SQL語句來建立一個新表。
用SQL建立新表
注意:
如果你還沒有建立自己的資料庫,現在就跳回到第三章建立這個庫。你絕不能向master,tempdb或任何其他任何系統資料庫中加入資料。
從SQL Sever程式群群組(在工作列中)中啟動ISQL/w程式。出現查詢視窗後,從視窗頂部的下拉清單中選擇你在第三章所建立的資料庫。下一步,在查詢視窗中鍵入下面的SQL語句,按一下執行查詢按鈕,執行這個語句:
CREATE
TABLE guestbook (visitor VARCHAR(40),comments TEXT,entrydate
DATETIME)
如果一切正常,你會在結果視窗中看到如下的文字(如果出現異常,請參閱第三章):
This
command dit not return data ,and it did not return any rows
祝賀你,你已經建立了你的第一個表!
你所建立的表名為guestbook,你可以使用這個表來存儲來字你網站存取者的資訊。你是用REEATE TABLE語句建立的這個表,這個語句有兩部分:第一部份指定表的名子;第二部份是括在括號中的各欄位的標簽和屬性,相互之間用逗號隔開。
表guestbook有三個欄位:visitor,comments
和entrydate。visitor欄位存儲存取者的名字,comments欄位存儲存取者對你網站的意見,entrydate欄位存儲存取者存取你網站的日期和時間。
注意每個欄位名後面都跟有一個專門的表達式。例如,欄位名comments後面跟有表達式TEXT。這個表達式指定了欄位的資料型式。資料型式決定了一個欄位可以存儲什麼樣的資料。因為欄位comments包含文本資訊,其資料型式定義為文本型。
欄位有許多不同的資料型式。下一小節講述SQL所支援的一些重要的資料型式。
欄位型式
不同的欄位型式用來存放不同型式的資料。建立和使用表時,更你應該理解五種常用的欄位型式:字元型,文本型,數值型,邏輯性和日期型。
字元型資料
字元型資料非常有用。當你需要存儲短的字元串資訊時,你總是要用到字元型資料。例如,你可以把從HTML form的文字方塊中蒐集到的資訊放在字元型欄位中。
要建立一個欄位用來存放可變長度的字元串資訊,你可以使用表達式 VARCHAR。考慮你前面建立的表guestbook:
CREATE
TABLE guestbook (visitor VARCHAR(40),comments TEXT,entrydate
DATETIME)
在這個例子中,欄位visitor的資料型式為VARCHAR。注意跟在資料型式後面的括號中的數字。這個數字指定了這個欄位所允許存放的字元串的最大長度。在這個例子中,欄位visitor能存放的字元串最長為四十個字元。如果名字太長,字元串會被截斷,只保留四十個字元。
VARCHAR型式可以存儲的字元串最長為255個字元。要存儲更長的字元串資料,可以使用文本型資料(下一節中講述)。
另一種字元型資料用來存儲固定長度的字元資料。下面是一個使用這種資料型式的例子:
CREATE
TABLE guestbook (visitor CHAR(40),comments TEXT,entrydate
DATETIME)
在這個例子中,欄位visitor被用來存儲四十個字元的固定長度字元串。表達式CHAR指定了這個欄位應該是固定長度的字元串。
VARCHAR型和CHAR型資料的這個差別是細微的,但是非常重要。假如你向一個長度為四十個字元的VARCHAR型欄位中輸入資料Bill Gates。當你以後從這個欄位中取出此資料時,你取出的資料其長度為十個字元的字元串Bill Gates的長度。
現在假如你把字元串輸入一個長度為四十個字元的CHAR型欄位中,那麼當你取出資料時,所取出的資料長度將是四十個字元。字元串的後面會被附加多餘的空格。
當你建立自己的網站時,你會發現使用VARCHAR型欄位要比CHAR型欄位方便的多。使用VARCHAR型欄位時,你不需要為剪掉你資料中多餘的空格而操心。
VARCHAR型欄位的另一個突出的好處是它可以比CHAR型欄位佔用更少的記憶體和硬碟空間。當你的資料庫很大時,這種記憶體和磁碟空間的節省會變得非常重要。
文本型資料
字元型資料限制了字元串的長度不能超過255個字元。而使用文本型資料,你可以存放超過二十億個字元的字元串。當你需要存儲大串的字元時,應該使用文本型資料。
這�埵酗@個使用文本型資料的例子:
CREATE
TABLE guestbook (visitor VARCHAR(40),comments TEXT,entrydate
DATETIME)
在這個例子中,欄位comments被用來存放存取者對你網站的意見。注意文本型資料沒有長度,而上一節中所講的字元型資料是有長度的。一個文本型欄位中的資料通常要麼為空,要麼很大。
當你從HTML form的多行文本編輯框(TEXTAREA)中收集資料時,你應該把收集的資訊存儲於文本型欄位中。但是,無論何時,只要你能避免使用文本型欄位,你就應該不適用它。文本型欄位既大且慢,濫用文本型欄位會使伺服器速度變慢。文本型欄位還會吃掉大量的磁碟空間。
警告:
一旦你向文本型欄位中輸入了任何資料(甚至是空值),就會有2K的空間被自動分配給該資料。除非刪除該記錄,否則你無法收回這部分存儲空間。
數值型資料
SQL Sever支援許多種不同的數值型資料。你可以存儲整數、小數、和錢數。
通常,當你需要在表中的存放數字時,你要使用整型(INT)資料。INT型資料的表數範圍是從-2,147,483,647到2,147,483,647的整數。下面是一個如何使用INT型資料的例子:
CREATE
TABLE visitlog (visitor VARCHAR(40),numvisits INT)
這個表可以用來記錄你網站被存取的次數。只要沒有人存取你的網站超過2,147,483,647次,nubvisits欄位就可以存儲存取次數。
為了節省記憶體空間,你可以使用SMALLINT型資料。SMALLINT 型資料可以存儲從-32768到32768的整數。這種資料型式的使用方法與INT型完全相同。
最後,如果你實在需要節省空間,你可以使用TINYINT型資料。同樣,這種型式的使用方法也與INT型相同,不同的是這種型式的欄位只能存儲從0到255的整數。TINYINT型欄位不能用來存儲負數。
通常,為了節省空間,應該盡可能的使用最小的整型資料。一個TINYINT型資料只佔用一個位元群組;一個INT型資料佔用四個位元群組。這看起來似乎差別不大,但是在比較大的表中,位元群組數的增長是很快的。另一方面,一旦你已經建立了一個欄位,要修改它是很困難的。因此,為安全起見,你應該預測以下,一個欄位所需要存儲的數值最大有可能是多大,然後選擇適當的資料型式。
為了能對欄位所存放的資料有更多的控制,你可以使用NUMERIC型資料來同時表示一個數的整數部分和小數部分。NUMERIC型資料使你能表示非常大的數????比INT型資料要大得多。一個NUMERIC型欄位可以存儲從-1038到1038範圍內的數。NUMERIC型資料還使你能表示有小數部分的數。例如,你可以在NUMERIC型欄位中存儲小數3.14。
當定義一個NUMERIC型欄位時,你需要同時指定整數部分的大小和小數部分的大小。這�埵酗@個使用這種資料型式的例子:
CREATE
TABLE numeric_data (bignumber NUMERIC(28,0),
fraction NUMERIC (5,4)
)
當這個語句執行時,將建立一個名為numeric_data的包含兩個欄位的表。欄位bignumber可以存儲直到28位的整數。欄位fraction可以存儲有五位整數部分和四位小數部分的小數。
一個NUMERIC型資料的整數部分最大只能有28位,小數部分的位數必須小於或等於整數部分的位數,小數部分可以是零。
你可以使用INT型或NUMERIC型資料來存儲錢數。但是,專門有另外兩種資料型式用於此目的。如果你希望你的網點能掙很多錢,你可以使用MONEY型資料。如果你的野心不大,你可以使用SMALLMONEY型資料。MONEY型資料可以存儲從-922,337,203,685,477.5808到922,337,203,685,477.5807的錢數。如果你需要存儲比這還大的金額,你可以使用NUMERIC型資料。
SMALLMONEY型資料只能存儲從-214,748.3648到214,748.3647 的錢數。同樣,如果可以的話,你應該用SMALLMONEY型來代替MONEY型資料,以節省空間。下面的例子顯示了如何使用這兩種表示錢的資料型式:
CREATE
TABLE products (product VARCHAR(40),price MONEY,
Discount_price SMALLMONEY)
這個表可以用來存儲商品的折扣和普通售價。欄位price
的資料型式是MONEY,欄位discount_price的資料型式是SMALLMONEY。
存儲邏輯值
如果你使用核對方塊(CHECKBOX)從網頁中蒐集資訊,你可以把此資訊存儲在BIT型欄位中。BIT型欄位只能取兩個值:0或1。這�埵酗@個如何使用這種欄位的例子:
CREATE
TABLE opinion (visitor VARCHAR(40),good BIT)
這個表可以用來存放對你的網點進行民意調查所得的資訊。存取者可以投票表示他們是否喜歡你的網點。如果他們投YES,就在BIT型欄位中存入1。反之,如果他們投NO,就在欄位中存入0(在下一章�堙A你將學會如何計算投票)。
當心,在你建立好一個表之後,你不能向表中加入BIT型欄位。如果你打算在一個表中包含BIT型欄位,你必須在建立表時完成。
存儲日期和時間
當你建立一個網點時,你也許需要記錄在一段時間內的存取者數量。為了能夠存儲日期和時間,你需要使用DATETIME型資料,如下例所示:
CREATE
TABL visitorlog( visitor VARCHAR (40), arrivaltime DATETIME
,
departuretime
DATETIME)
這個表可以用來記錄存取者進入和離開你網點的時間和日期。一個DATETIME型的欄位可以存儲的日期範圍是從1753年1月1日第一毫秒到9999年12月31日最後一毫秒。
如果你不需要覆蓋這麼大範圍的日期和時間,你可以使用SMALLDATETIME型資料。它與DATETIME型資料同樣使用,只不過它能表示的日期和時間範圍比DATETIME型資料小,而且不如DATETIME型資料精確。一個SMALLDATETIME型的欄位能夠存儲從1900年1月1日到2079年6月6日的日期,它只能精確到秒。
DATETIME型欄位在你輸入日期和時間之前並不包含實際的資料,認識這一點是重要的。在下一章,你將學習怎樣使用大量的SQL函數來讀取和操作日期和時間(參見下面的“內定值”一節)。你也可以在VBScript和JScript 中使用日期和時間函數來向一個DATETIME型欄位中輸入日期和時間。
欄位屬性
上一節介紹了如何建立包含不同型式欄位的表。在這一節中,你將學會如何使用欄位的三個屬性。這些屬性允許你控制空值,內定值和標識值。
允許和禁止空值
大多數欄位可以接受空值(NULL)。當一個欄位接受了空值後,如果你不改變它,它將一直保持空值。空值(NULL)和零是不同的,嚴格的說,空值表示沒有任何值。
為了允許一個欄位接受空值,你要在欄位定義的後面使用表達式NULL。例如,下面的表中兩個欄位都允許接受空值:
CREATE
TABLE empty (empty1 CHAR (40) NULL,empty2 INT NULL(
注意:
BIT型資料不能是空值。一個這種型式的欄位必須取0或者1。
有時你需要禁止一個欄位使用空值。例如,假設有一個表存儲著信用卡號碼和信用卡有效日期,你不會希望有人輸入一個信用卡號碼但不輸入有效日期。為了強制兩個欄位都輸入資料,你可以用下面的方法建立這個表:
CREATE
TABLE creditcards (creditcard_number CHAR(20) NOT NULL,
Creditcard_expire DATETIME
NOT NULL)
注意欄位定義的後面跟有表達式NOT NULL。通過包含表達式NOT NULL,你可以禁止任何人只在一個欄位中插入資料,而不輸入另一個欄位的資料。
你將會發現,在你建設自己的網點過程中,這種禁止空值的能力是非常有用的。如果你指定一個欄位不能接受空值,那麼當你試圖輸入一個空值時,會有錯誤警告。這些錯誤警告可以為程式偵錯提供有價值的線索。
內定值
假設有一個存儲位址資訊的表,這個表的欄位包括街道、城市、州、郵政編碼和國家。如果你預計位址的大部分是在美國,你可以把這個值作為country欄位的內定值。
為了在建立一個表時指定內定值,你可以使用表達式DEFAULT。請看下面這個在建立表時使用內定值的例子:
CREATE
TABLE addresses (street VARCHAR(60) NULL,
city
VARCHAR(40) NULL,
state
VARCHAR(20) NULL
zip
VARCHAR(20) NULL,
country
VARCHAR(30) DEFAULT ‘USA’)
在這個例子中,欄位country的內定值被指定為美國。注意單引號的使用,引號指明這是字元型資料。為了給非字元型的欄位指定內定值,不要把該值擴在引號中:
CREATE
TABLE orders(price MONEY DEFAULT $38.00,
quantity INT DEFAULT
50,
entrydate DATETIME DEFAULT
GETDATE())
在這個CREATE TABLE語句中,每個欄位都指定了一個內定值。注意DATETIME型欄位entrydate所指定的內定值,該內定值是函數Getdate()的返回值,該函數返回目前的日期和時間。
主鍵
每個表可以有一個也只能有一個主鍵。一個主鍵是唯一標識表中每條記錄的特殊欄位。例如,資料庫pubs中的表jobs包含了一個唯一標識每個工作主鍵:
job_id
job_desc
…………………………………………………………….
1.
New Hire Job not
specified
2.
Chief Executive
officer
3.
Bushness Operations
Manager
4.
Chief Financial
Officier
5.
Publisher
欄位job_id為每個工作提供了唯一的一個數字。如果你決定增加一個新工作,新增記錄的job_id欄位會被自動賦給一個新的唯一值。
為了建立一個主鍵,你只需在欄位定義後面加上表達式IDENTITY即可。你只能把NUMERIC型或INT型欄位設為主鍵,這�埵酗@個例子:
CREATE
TABLE visitorID (theID NUBERIC(18) IDENTITY,name
VARCHAR(40))
這個語句所建立的表包含一個名為theid的主鍵。每當一個新的存取者名字加入到這個表中時,這個欄位就被自動賦給一個新值。你可以用這個表為你的網站的每一個用戶提供唯一標識。
技巧:
建立一個標示欄位時,注意使用足夠大的資料型式。例如你使用TINYINT型資料,那麼你只能向表中加入255個記錄。如果你預計一個表可能會變得很大,你應該使用NUMERIC型資料。
主鍵的存在會使你想嘗試許多不可能的事情。例如,你也許想利用主鍵來對記錄進行基於它們在表中位置的運算。你應該拋棄這種意圖。每個記錄的主鍵的值是互不相同的,但是,這並不禁止一個主鍵的標識數字之間存在間隔。例如,你永遠不要試圖利用一個表的主鍵來取出表中的前十個記錄。這種操作會導致失敗,比如說6號記錄和7號記錄根本不存在。
使用SQL事務管理器建立新表
你可以使用前面幾節所講的方法建立新表。但是,使用事務管理器建立新表會更容易。這一節介紹如何使用這個程式建立新表。
從工作列的SQL
Sever程式群群組中選擇SQL Enterprise
Manager,啟動該程式,你會看到如圖10.4所示的視窗。瀏覽服務管理器視窗中的樹形結構,選擇名為Database的文件夾。打開文件夾Database後,選擇你在第三章中所建立的資料庫。
注意:
如果你還沒有建立自己的資料庫,回到第三章建立它。你決不要向master,tempdb或任何其它系統資料庫中加入資料。
在選擇了資料庫之後,你會看到一個名為Group/users的文件夾和一個名為objects的文件夾。打開文件夾objects,你會看到許多文件夾,其中一個名為Tables。用右鍵按一下文件夾Tables並選擇New table,就會出現如圖10.5所示的視窗。
你可以使用Manager
Tables視窗來建立一個新表。Manager
Tables視窗有7個列:Key,Column,Name,Datatype,Size,Nulls和Default。Manager
Tables視窗中的每一行標明表中一個欄位的資訊。
圖10.4
10.5
要建立一個新表,你至少要輸入一行資訊。在名為Column Name的列下面鍵入mycolumn。下一步,選擇Datatype列,並從下拉清單中選擇CHAR。當你在這兩個列中輸入資訊後,視窗將是如圖10.6所示的樣子。
圖10.6
你已經建立了一個只有一個欄位的簡單的表。按一下存檔按扭存檔這個新表。當要求你輸入新表的名字時,輸入mytable並按一下OK。現在這個表已經存檔到了你的資料庫中。
如果你打開服務管理器視窗中的文件夾Tables,你會看到你所建立的新表被列出。你可以連續按兩下該表的圖表來編輯它,這時Manager Tables視窗會重新出現,你可以增加新的欄位並重新存檔。
用SQL事務管理器可以做的工作,你都可以用SQL語句來實現。但是,事務管理器使得建表過程變得更加簡單。
向表中加入資料
下一章將討論如何使用SQL向一個表中插入資料。但是,如果你需要向一個表中加入許多條記錄,使用SQL語句輸入資料是很不方便的。幸運的是,Microsoft SQL
Sever帶有一個稱為Microsoft Query
的用戶端應用程式,這個程式使得向表中加入資料變得容易了。
啟動位於工作列SQL
Sever程式群群組中的Microsoft
Query程式。從視窗頂部的選擇表中選擇File|New
Query。這時會顯示一個Select Data Source對話方塊(見圖10.7)。選擇你的資料源名字並按一下Use。
圖10。7
輸入你的登入帳號和密碼後,程式要求你選擇一個表和一個資料庫。選擇你在上一節中所建立的表(mytable ),按一下按鈕Add,然後按一下按鈕Close關閉該對話方塊。
在視窗的左上角會出現一個對話方塊,框中是取自表mytable的一列欄位名。你可以連續按兩下任何一個欄位,把它加入到主視窗中。如果你連續按兩下星號(*)字元,所有的欄位都會被加入到主視窗中。
如果你的表中有記錄,它們現在已經出現在主視窗的欄位標題下面了。但是,因為你剛剛建立了這個表,表還是空的。要加入新記錄,選擇Records|Allow Editing,主視窗中就會出現一條新記錄。輸入一行資料完成這個記錄,就向表中加入了一條新記錄。
圖10。8
當你轉到下一條新記錄時,你向上一條記錄中輸入的值會自動被存檔。如果你需要,你可以用Microsoft Query 向表中輸入幾百條記錄。
刪除和修改表
你應該在建立表之前仔細設計它們,因為你在改變一個已經存在的表時會受到很大的限制。例如,一旦已經建立了一個表,你就不能刪除表中的欄位或者改變欄位的資料型式。在這種情況你所能做的是刪除這個表,然後重頭開始(參見第十一章“中級SQL”中的“使用SQL建立記錄和表”一節)。
要刪除一個表,你可以使用SQL語句DROP
TABLE。例如,又從資料庫中徹底刪除表mytable,你要使用如下的語句:
DROP
TABLE mytable
警告:
使用DROP TABLE命令時一定要小心。一旦一個表被刪除之後,你將無法恢復它。
當你建設一個網站時,你很可能需要向資料庫中輸入測試資料。而當你準備向世界提供你的網點時,你會想清空表中的這些測試資訊。如果你想清除表中的所有資料但不刪除這個表,你可以使用TRUNCATE TABLE語句。例如,下面的這個SQL語句從表mytable中刪除所有資料:
TRUNCATE TABLE
mytable
雖然你不能刪除和修改已經存在的欄位,但你可以增加新欄位。最容易的實現方法是使用SQL事務管理器中的Manager
Tables視窗。你也可以使用SQL語句ALTER
TABLE。下面是一個如何使用這種語句的例子:
ALTER
TABLE mytable ADD mynewcolumn INT NULL
這個語句向表mytable中增加了一個新欄位mynewcolumn。當你增加新欄位時,你必須允許它接受空值,因為表中原來可能已經有了許多記錄。
總結
這一章向你介紹了SQL。使用SQL,你可以操作Microsoft SQL
Sever資料庫。你已經學會了使用SELECT語句從資料庫中取出資料,你還學會了怎樣使用CREATE TABLE語句和SQL事務管理器來建立新表。最後,你學會了如何指明一系列重要的欄位屬性。
下一章將介紹如何使用索引來增強SQL查詢的操作。還將通過許多其它的SQL語句和函數,使你的SQL知識得到進一步擴充。
第十一章 中級SQL
本章內容
■建立索引
■SQL核心語句
■群組函數
o
其它常用的SQL表達式,
函數,和過程
第十章“SQL基礎”向你初步介紹了SQL。你學會了如何用SELECT語句進行查詢,你還學會了如何建立自己的表。在這一章�堙A你將加深你的SQL知識。你將學習如何建立索引來加快查詢速度。你還將學會如果用更多的SQL語句和函數來操作表中的資料。
建立索引
假設你想找到本書中的某一個句子。你可以一頁一頁地逐頁搜索,但這會花很多時間。而通過使用本書的索引,你可以很快地找到你要搜索的主題。
表的索引與附在一本書後面的索引非常相似。它可以極大地提高查詢的速度。對一個較大的表來說,通過加索引,一個通常要花費幾個小時來完成的查詢只要幾分鐘就可以完成。因此沒有理由對需要頻繁查詢的表增加索引。
注意:
當你的記憶體容量或硬碟空間不足時,也許你不想給一個表增加索引。對於包含索引的資料庫,SQL Sever需要一個可觀的額外空間。例如,要建立一個聚叢集索引,需要大約1.2倍於資料大小的空間。要看一看一個表的索引在資料庫中所佔的空間大小,你可以使用系統存儲過程sp_spaceused,物件名指定為被索引的表名。
聚叢集索引和非聚叢集索引
假設你已經通過本書的索引找到了一個句子所在的頁碼。一旦已經知道了頁碼後,你很可能漫無目的翻尋這本書,直至找到正確的頁碼。通過隨機的翻尋,你最終可以到達正確的頁碼。但是,有一種找到頁碼的更有效的方法。
首先,把書翻到大概一半的地方,如果要找的頁碼比半本書處的頁碼小,就書翻到四分之一處,否則,就把書翻到四分之三的地方。通過這種方法,你可以繼續把書分成更小的部分,直至找到正確的頁碼附近。這是找到書頁的非常有效的一種方法。
SQL Sever的表索引以類似的方式工作。一個表索引由一群群組頁群群組成,這些頁構成了一個樹形結構。根頁通過指向另外兩個頁,把一個表的記錄從邏輯上分成和兩個部分。而根頁所指向的兩個頁又分別把記錄分割成更小的部分。每個頁都把記錄分成更小的分割,直至到達葉級頁。
索引有兩種型式:聚叢集索引和非聚叢集索引。在聚叢集索引中,索引樹的葉級頁包含實際的資料:記錄的索引順序與物理順序相同。在非聚叢集索引中,葉級頁指向表中的記錄:記錄的物理順序與邏輯順序沒有必然的聯繫。
聚叢集索引非常象目錄表,目錄表的順序與實際的頁碼順序是一致的。非聚叢集索引則更象書的標準索引表,索引表中的順序通常與實際的頁碼順序是不一致的。一本書也許有多個索引。例如,它也許同時有主題索引和作者索引。同樣,一個表可以有多個非聚叢集索引。
通常情況下,你使用的是聚叢集索引,但是你應該對兩種型式索引的優缺點都有所理解。
每個表只能有一個聚叢集索引,因為一個表中的記錄只能以一種物理順序存放。通常你要對一個表按照主鍵建立聚叢集索引。但是,你也可以對其它型式的欄位建立聚叢集索引,如字元型,數值型和日期時間型欄位。
從建立了聚叢集索引的表中取出資料要比建立了非聚叢集索引的表快。當你需要取出一定範圍內的資料時,用聚叢集索引也比用非聚叢集索引好。例如,假設你用一個表來記錄存取者在你網點上的活動。如果你想取出在一定時間段內的登入資訊,你應該對這個表的DATETIME型欄位建立聚叢集索引。
對聚叢集索引的主要限制是每個表只能建立一個聚叢集索引。但是,一個表可以有不止一個非聚叢集索引。實際上,對每個表你最多可以建立249個非聚叢集索引。你也可以對一個表同時建立聚叢集索引和非聚叢集索引。
假如你不僅想根據日期,而且想根據用戶名從你的網點活動日誌中取資料。在這種情況下,同時建立一個聚叢集索引和非聚叢集索引是有效的。你可以對日期時間欄位建立聚叢集索引,對用戶名欄位建立非聚叢集索引。如果你發現你需要更多的索引方式,你可以增加更多的非聚叢集索引。
非聚叢集索引需要大量的硬碟空間和記憶體。另外,雖然非聚叢集索引可以提高從表中 取資料的速度,它也會降低向表中插入和更新資料的速度。每當你改變了一個建立了非聚叢集索引的表中的資料時,必須同時更新索引。因此你對一個表建立非聚叢集索引時要慎重考慮。如果你預計一個表需要頻繁地更新資料,那麼不要對它建立太多非聚叢集索引。另外,如果硬碟和記憶體空間有限,也應該限制使用非聚叢集索引的數量。
索引屬性
這兩種型式的索引都有兩個重要屬性:你可以用兩者中任一種型式同時對多個欄位建立索引(復合索引);兩種型式的索引都可以指定為唯一索引。
你可以對多個欄位建立一個復合索引,甚至是復合的聚叢集索引。假如有一個表記錄了你的網點存取者的姓和名字。如果你希望根據完整姓名從表中取資料,你需要建立一個同時對姓欄位和名字欄位進行的索引。這和分別對兩個欄位建立單獨的索引是不同的。當你希望同時對不止一個欄位進行查詢時,你應該建立一個對多個欄位的索引。如果你希望對各個欄位進行分別查詢,你應該對各欄位建立獨立的索引。
兩種型式的索引都可以被指定為唯一索引。如果對一個欄位建立了唯一索引,你將不能向這個欄位輸入重複的值。一個主鍵會自動成為唯一值欄位,但你也可以對其它型式的欄位建立唯一索引。假設你用一個表來存檔你的網點的用戶密碼,你當然不希望兩個用戶有相同的密碼。通過強制一個欄位成為唯一值欄位,你可以防止這種情況的發生。
用SQL建立索引
為了給一個表建立索引,啟動工作列SQL Sever程式群群組中的ISQL/w程式。進入查詢視窗後,輸入下面的語句:
CREATE
INDEX mycolumn_index ON mytable (myclumn)
這個語句建立了一個名為mycolumn_index的索引。你可以給一個索引起任何名字,但你應該在索引名中包含所索引的欄位名,這對你將來弄清楚建立該索引的意圖是有輔助敘述的。
注意:
在本書中你執行任何SQL語句,都會收到如下的資訊:
This
command did not return data,and it did not return any rows
這敘述該語句執行成功了。
索引mycolumn_index對表mytable的mycolumn欄位進行。這是個非聚叢集索引,也是個非唯一索引。(這是一個索引的內定屬性)
如果你需要改變一個索引的型式,你必須刪除原來的索引並重建 一個。建立了一個索引後,你可以用下面的SQL語句刪除它:
DROP
INDEX mytable.mycolumn_index
注意在DROP INDEX 語句中你要包含表的名字。在這個例子中,你刪除的索引是mycolumn_index,它是表mytable的索引。
要建立一個聚叢集索引,可以使用關鍵字CLUSTERED。)記住一個表只能有一個聚叢集索引。(這�埵酗@個如何對一個表建立聚叢集索引的例子:
CREATE
CLUSTERED INDEX mycolumn_clust_index ON mytable(mycolumn)
如果表中有重複的記錄,當你試圖用這個語句建立索引時,會出現錯誤。但是有重複記錄的表也可以建立索引;你只要使用關鍵字ALLOW_DUP_ROW把這一點告訴SQL
Sever即可:
CREATE
CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)
WITH
ALLOW_DUP_ROW
這個語句建立了一個允許重複記錄的聚叢集索引。你應該盡量避免在一個表中出現重複記錄,但是,如果已經出現了,你可以使用這種方法。
要對一個表建立唯一索引,可以使用關鍵字UNIQUE。對聚叢集索引和非聚叢集索引都可以使用這個關鍵字。這�埵酗@個例子:
CREATE
UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
這是你將經常使用的索引建立語句。無論何時,只要可以,你應該盡量對一個對一個表建立唯一聚叢集索引來增強查詢操作。
最後,要建立一個對多個欄位的索引——復合索引——在索引建立語句中同時包含多個欄位名。下面的例子對firstname和lastname兩個欄位建立索引:
CREATE
INDEX name_index ON username(firstname,lastname)
這個例子對兩個欄位建立了單個索引。在一個復合索引中,你最多可以對16個欄位進行索引。
用事務管理器建立索引
用事務管理器建立索引比用SQL語句容易的多。使用事務管理器,你可以看到已經建立的索引的清單,並可以通過圖形介面選擇索引選項。
使用事務管理器你可以用兩種方式建立索引:使用Manage
Tables視窗或使用Manage
Indexes視窗。
要用Manage Tables 視窗建立一個新索引,按一下按鈕Advanced
Options(它看起來象一個前面有一加號的表)。這樣就打開了Advanced
Options對話方塊。這個對話方塊有一部分標名為Primary Key(見圖11.1)。
圖11。1
要建立一個新索引,從下拉清單中選擇你想對之建立索引的欄位名。如果你想建立一個對多欄位的索引,你可以選擇多個欄位名。你還可以選擇索引是聚叢集的還是非聚叢集的。在存檔表資訊後,索引會自動被建立。在Manage Tables視窗中的欄位名旁邊,會出現一把鑰匙。
你已經為你的表建立了“主索引”。主索引必須對不包含空值的欄位建立。另外,主索引強制一個欄位成為唯一值欄位。
要建立沒有這些限制的索引,你需要使用Manage
Indexes視窗。從選擇表中選擇Manage|Indexes,打開Manage Indexes
視窗。在Manage Indexes 視窗中,你可以通過下拉框選擇表和特定的索引。(見圖11.2)。要建立一個新索引,從Index下拉框中選擇New Index.,然後就可以選擇要對之建立索引的欄位。按一下按鈕Add,把欄位加人到索引中。
圖11。2
你可以為你的索引選擇許多不同的選項。例如,你可以選擇該索引是聚叢集的還是非聚叢集的。你還可以指定該索引為唯一索引。設計好索引後,按一下按鈕Build,建立該索引。
注意:
唯一索引是指該欄位不能有重複的值,而不是只能建立這一個索引。
SQL核心語句
在第十章,你學會了如何用SQL SELECT 語句從一個表中取資料。但是,到現在為止,還沒有討論如何加入,修改或刪除表中的資料。在這一節中,你將學習這些內容。
插入資料
向表中加入一個新記錄,你要使用SQL INSERT 語句。這�埵酗@個如何使用這種語句的例子:
INSERT
mytable (mycolumn) VALUES (‘some data’)
這個語句把字元串’some
data’插入表mytable的mycolumn欄位中。將要被插入資料的欄位的名字在第一個括號中指定,實際的資料在第二個括號中給出。
INSERT
語句的完整句法如下:
INSERT
[INTO] {table_name|view_name} [(column_list)] {DEFAULT VALUES
|
Values_list |
select_statement}
如果一個表有多個欄位,通過把欄位名和欄位值用逗號隔開,你可以向所有的欄位中插入資料。假設表mytable有三個欄位first_column,second_column,和third_column。下面的INSERT語句加入了一條三個欄位都有值的完整記錄:
INSERT
mytable (first_column,second_column,third_column)
VALUES
(‘some data’,’some more data’,’yet more data’)
注意:
你可以使用INSERT語句向文本型欄位中插入資料。但是,如果你需要輸入很長的字元串,你應該使用WRITETEXT語句。這部分內容對本書來說太進階了,因此不加討論。要了解更多的資訊,請參考Microsoft SQL Sever 的文檔。
如果你在INSERT 語句中只指定兩個欄位和資料會怎麼樣呢?換句話說,你向一個表中插入一條新記錄,但有一個欄位沒有提供資料。在這種情況下,有下面的四種可能:
·
如果該欄位有一個內定值,該值會被使用。例如,假設你插入新記錄時沒有給欄位third_column提供資料,而這個欄位有一個內定值’some
value’。在這種情況下,當新記錄建立時會插入值’some
value’。
·
如果該欄位可以接受空值,而且沒有內定值,則會被插入空值。
·
如果該欄位不能接受空值,而且沒有內定值,就會出現錯誤。你會收到錯誤資訊:
The column in table mytable
may not be null.
·
最後,如果該欄位是一個主鍵,那麼它會自動產生一個新值。當你向一個有主鍵的表中插入新記錄時,只要忽略該欄位,主鍵會給自己賦一個新值。
注意:
向一個有主鍵的表中插入新記錄後,你可以用SQL變數@@identity來存取新記錄
的主鍵的值。考慮如下的SQL語句:
INSERT
mytable (first_column) VALUES(‘some value’)
INSERT
anothertable(another_first,another_second)
VALUES(@@identity,’some value’)
如果表mytable有一個主鍵,該欄位的值會被插入表anothertable的another_first欄位。這是因為變數@@identity總是存檔最後一次插入主鍵的值。
欄位another_first應該與欄位first_column有相同的資料型式。但是,欄位another_first不能是應該主鍵。Another_first欄位用來存檔欄位first_column的值。
刪除記錄
要從表中刪除一個或多個記錄,需要使用SQL DELETE語句。你可以給DELETE 語句提供WHERE 子句。WHERE子句用來選擇要刪除的記錄。例如,下面的這個DELETE語句只刪除欄位first_column的值等於’Delete Me’的記錄:
DELETE mytable WHERE first_column=’Deltet Me’
DELETE
語句的完整句法如下:
DELETE
[FROM] {table_name|view_name} [WHERE clause]
在SQL SELECT 語句中可以使用的任何條件都可以在DELECT
語句的WHERE子句
中使用。例如,下面的這個DELETE語句只刪除那些first_column欄位的值為’goodbye’或second_column欄位的值為’so long’的記錄:
DELETE
mytable WHERE first_column=’goodby’ OR
second_column=’so long’
如果你不給DELETE 語句提供WHERE 子句,表中的所有記錄都將被刪除。你不應該有這種想法。如果你想刪除應該表中的所有記錄,應使用第十章所講的TRUNCATE
TABLE語句。
注意:
為什麼要用TRUNCATE TABLE
語句代替DELETE語句?當你使用TRUNCATE
TABLE語句時,記錄的刪除是不作記錄的。也就是說,這意味著TRUNCATE TABLE
要比DELETE快得多。
更新記錄
要修改表中已經存在的一條或多條記錄,應使用SQL
UPDATE語句。同DELETE語句一樣,UPDATE語句可以使用WHERE子句來選擇更新特定的記錄。請看這個例子:
UPDATE mytable SET first_column=’Updated!’ WHERE second_column=’Update Me!’
這個UPDATE 語句更新所有second_column欄位的值為’Update Me!’的記錄。對所有被選中的記錄,欄位first_column的值被置為’Updated!’。
下面是UPDATE語句的完整句法:
UPDATE
{table_name|view_name} SET [{table_name|view_name}]
{column_list|variable_list|variable_and_column_list}
[,{column_list2|variable_list2|variable_and_column_list2}…
[,{column_listN|variable_listN|variable_and_column_listN}]]
[WHERE
clause]
注意:
你可以對文本型欄位使用UPDATE語句。但是,如果你需要更新很長的字元串,應使用UPDATETEXT語句。這部分內容對本書來說太進階了,因此不加討論。要了解更多的資訊,請參考Microsoft SQL
Sever 的文檔。
如果你不提供WHERE子句,表中的所有記錄都將被更新。有時這是有用的。例如,如果你想把表titles中的所有書的價格加倍,你可以使用如下的UPDATE
語句:
你也可以同時更新多個欄位。例如,下面的UPDATE語句同時更新first_column,second_column,和third_column這三個欄位:
UPDATE
mytable SET first_column=’Updated!’
Second_column=’Updated!’
Third_column=’Updated!’
WHERE first_column=’Update Me1’
技巧:
SQL忽略語句中多餘的空格。你可以把SQL語句寫成任何你最容易讀的格式。
用SELECT 建立記錄和表
你也許已經注意到,INSERT 語句與DELETE語句和UPDATE語句有一點不同,它一次只操作一個記錄。然而,有一個方法可以使INSERT 語句一次加入多個記錄。要作到這一點,你需要把INSERT 語句與SELECT 語句結合起來,象這樣:
INSERT
mytable (first_column,second_column)
SELECT
another_first,another_second
FROM
anothertable
WHERE another_first=’Copy Me!’
這個語句從anothertable拷貝記錄到mytable.只有表anothertable中欄位another_first的值為’Copy Me!’的記錄才被拷貝。
當為一個表中的記錄建立備份時,這種形式的INSERT
語句是非常有用的。在刪除一個表中的記錄之前,你可以先用這種方法把它們拷貝到另一個表中。
如果你需要拷貝整個表,你可以使用SELECT INTO
語句。例如,下面的語句建立了一個名為newtable的新表,該表包含表mytable的所有資料:
SELECT
* INTO newtable FROM mytable
你也可以指定只有特定的欄位被用來建立這個新表。要做到這一點,只需在欄位清單中指定你想要拷貝的欄位。另外,你可以使用WHERE 子句來限制拷貝到新表中的記錄。下面的例子只拷貝欄位second_columnd的值等於’Copy Me!’的記錄的first_column欄位。
SELECT
first_column INTO newtable
FROM
mytable
WHERE
second_column=’Copy Me!’
使用SQL修改已經建立的表是很困難的。例如,如果你向一個表中加入了一個欄位,沒有容易的辦法來去除它。另外,如果你不小心把一個欄位的資料型式給錯了,你將沒有辦法改變它。但是,使用本節中講述的SQL語句,你可以繞過這兩個問題。
例如,假設你想從一個表中刪除一個欄位。使用SELECT INTO 語句,你可以建立該表的一個拷貝,但不包含要刪除的欄位。這使你既刪除了該欄位,又保留了不想刪除的資料。
如果你想改變一個欄位的資料型式,你可以建立一個包含正確資料型式欄位的新表。建立好該表後,你就可以結合使用UPDATE語句和SELECT 語句,把原來表中的所有資料拷貝到新表中。通過這種方法,你既可以修改表的結構,又能存檔原有的資料。
群組函數
到現在為止,你只學習了如何根據特定的條件從表中取出一條或多條記錄。但是,假如你想對一個表中的記錄進行資料統計。例如,如果你想統計存儲在表中的一次民意測驗的投票結果。或者你想知道一個存取者在你的網站上平均花費了多少時間。要對表中的任何型式的資料進行統計,都需要使用群組函數。
Microsoft SQL 支援五種型式的群組函數。你可以統計記錄數目,平均值,最小值,最大值,或者求和。當你使用一個群組函數時,它只返回一個數,該數值代表這幾個統計值之一。
注意:
要在你的ASP網頁中使用群組函數的返回值,你需要給該值起一個名字。要作到這一點,你可以在SELECT語句中,在群組函數後面緊跟一個欄位名,如下例所示:
SELECT
AVG(vote) ‘the_average’ FROM
opinion
在這個例子中,vote的平均值被命名為 the_average。現在你可以在你的ASP網頁的資料庫方法中使用這個名字。
統計欄位值的數目
函數COUNT()也許是最有用的群組函數。你可以用這個函數來統計一個表中有多少條記錄。這�埵酗@個例子:
SELECT
COUNT(au_lname) FROM authors
這個例子計算表authors中名字(last
name)的數目。如果相同的名字出現了不止一次,該名字將會被計算多次。如果你想知道名字為某個特定值的作者有多少個,你可以使用WHERE子句,如下例所示:
SELECT COUNT(au_lname) FROM authors WHERE au_lname=’Ringer’
這個例子返回名字為’Ringer’的作者的數目。如果這個名字在表authors中出現了兩次,則次函數的返回值是2。
假如你想知道有不同名字的作者的數目。你可以通過使用關鍵字DISTINCT來得到該數目。如下例所示:
SELECT
COUNT(DISTINCT au_lname) FROM authors
如果名字’Ringer’出現了不止一次,它將只被計算一次。關鍵字DISTINCT 決定了只有互不相同的值才被計算。
通常,當你使用COUNT()時,欄位中的空值將被忽略。一般來說,這正是你所希望的。但是,如果你僅僅想知道表中記錄的數目,那麼你需要計算表中所有的記錄—不管它是否包含空值。下面是一個如何做到這一點的例子:
SELECT
COUNT(*) FROM authors
注意函數COUNT()沒有指定任何欄位。這個語句計算表中所有記錄所數目,包括有空值的記錄。因此,你不需要指定要被計算的特定欄位。
函數COUNT()在很多不同情況下是有用的。例如,假設有一個表存檔了對你網站的品質進行民意調查的結果。這個表有一個名為vote的欄位,該欄位的值要麼是0,要麼是1。0表示反對票,1表示贊成票。要確定贊成票的數量,你可以所有下面的SELECT
語句:
SELECT
COUNT(vote) FROM opinion_table WHERE vote=1
計算欄位的平均值
使用函數COUNT(),你可以統計一個欄位中有多少個值。但有時你需要計算這些值的平均值。使用函數AVG(),你可以返回一個欄位中所有值的平均值。
假如你對你的網站進行一次較為複雜的民意調查。存取者可以在1到10之間投票,表示他們喜歡你網站的程度。你把投票結果存檔在名為vote的INT型欄位中。要計算你的用戶投票的平均值,你需要使用函數AVG():
SELECT
AVG(vote) FROM opinion
這個SELECT語句的返回值代表用戶對你網站的平均喜歡程度。函數AVG()只能對數值型欄位使用。這個函數在計算平均值時也忽略空值。
計算欄位值的和
假設你的網站被用來出售卡片,已經執行了兩個月,是該計算賺了多少錢的時候了。假設有一個名為orders的表用來記錄所有存取者的定購資訊。要計算所有定購量的總和,你可以使用函數SUM():
SELECT
SUM(purchase_amount) FROM orders
函數SUM()的返回值代表欄位purchase_amount中所有值的平均值。欄位purchase_amount的資料型式也許是MONEY型,但你也可以對其它數值型欄位使用函數SUM()。
返回最大值或最小值
再一次假設你有一個表用來存檔對你的網站進行民意調查的結果。存取者可以選擇從1到10 的值來表示他們對你網站的評價。如果你想知道存取者對你網站的最高評價,你可以使用如下的語句:
SELECT
MAX(vote) FROM opinion
你也許希望有人對你的網站給予了很高的評價。通過函數MAX(),你可以知道一個數值型欄位的所有值中的最大值。如果有人對你的網站投了數字10,函數MAX()將返回該值。
另一方面,假如你想知道存取者對你網站的的最低評價,你可以使用函數MIN(),如下例所示:
SELECT
MIN(vote) FROM opinion
函數MIN()返回一個欄位的所有值中的最小值。如果欄位是空的,函數MIN()返回空值。
其它常用的SQL表達式,函數,和過程
這一節將介紹一些其它的SQL技術。你將學習如何從表中取出資料,其某個欄位的值處在一定的範圍,你還將學習如何把欄位值從一種型式轉換成另一種型式,如何操作字元串和日期時間資料。最後,你將學會一個發送郵件的簡單方法。
通過匹配一定範圍的值來取出資料
假設你有一個表用來存檔對你的網站進行民意調查的結果。現在你想向所有對你的網站的評價在7到10之間的存取者發送書面的感謝信。要得到這些人的名字,你可以使用如下的SELECT
語句:
SELECT
username FROM opinion WHERE vote>6 and vote<11
這個SELECT 語句會實現你的要求。你使用下面的SELECT
語句也可以得到同樣的結果:
SELECT
username FROM opinion WHERE vote BETWEEN 7 AND 10
這個SELECT 語句與上一個語句是等價的。使用哪一種語句是程式設計風格的問題,但你會發現使用表達式BETWEEN 的語句更易讀。
現在假設你只想取出對你的網站投了1或者10的存取者的名字。要從表opinion中取出這些名字,你可以使用如下的SELECT
語句:
SELECT
username FROM opinion WHERE vote=1 or vote
這個SELECT語句會返回正確的結果,沒有理由不使用它。但是,存在一種等價的方式。使用如下的SELECT可以得到相同的結果:
SELECT
username FROM opinion WHERE vote IN (1,10)
注意表達式IN 的使用。這個SELECT
語句只取出vote的值等於括號中的值之一的記錄。
你也可以使用IN來匹配字元資料。例如,假設你只想取出Bill
Gates或President
Clinton的投票值。你可以使用如下的SELECT
語句:
SELECT
vote FROM opinion WHERE username IN (‘Bill Gates’,’President
Clinton’)
最後,你可以在使用BETWEEN或IN的同時使用表達式NOT。例如,要取出那些投票值不在7到10之間的人的名字,你可以使用如下的SELECT
語句:
SELECT
username FROM opinion WHERE vote NOT BETWEEN 7 and 10
要選取那些某個欄位的值不在一列值之中的記錄,你可以同時使用NOT 和IN,如下例所示:
SELECT
vote FROM opinion
WHERE
username NOT IN (‘Bill Gates’,’President
Clinton’)
你不是必須在SQL語句中使用BETWEEN或IN,但是,要使你的查詢更接近自然語言,這兩個表達式是有輔助敘述的。
轉換資料
SQL Sever足夠強大,可以在需要的時候把大部分數值從一種型式轉換為另一種型式。例如,要比較SMALLINT型和INT型資料的大小,你不需要進行顯式的型式轉換。SQL Sever會為你完成這項工作。但是,當你想在字元型資料和其它型式的資料之間進行轉換時,你的確需要自己進行轉換操作。例如,假設你想從一個MONEY型欄位中取出所有的值,並在結果後面加上字元串“US
Dollars”。你需要使用函數CONVERT(),如下例所示:
SELECT
CONVERT(CHAR(8),price)+’US Dollars’ FROM
orders
函數CONVERT()帶有兩個變數。第一個變數指定了資料型式和長度。第二個變數指定了要進行轉換的欄位。在這個例子中,欄位price被轉換成長度為8個字元的CHAR型欄位。欄位price要被轉換成字元型,才可以在它後面連接上字元串’US Dollars’。
當向BIT型,DATETIME型,INT型,或者NUMERIC型欄位加入字元串時,你需要進行同樣的轉換操作。例如,下面的語句在一個SELECT語句的查詢結果中加入字元串’The vote is’,該SELECT語句返回一個BIT型欄位的值:
SELECT
‘The vote
is’+CONVERT(CHAR(1),vote) FROM
opinion
下面是這個語句的結果示例:
The
vote is 1
The
vote is 1
The
vote is 0
(3
row(s) affected)
如果你不進行顯式的轉換,你會收到如下的錯誤資訊:
Implicit conversion from
datatype ‘varchar’ to ‘bit’ is not
allowec.
Use the
CONVERT function to run this query.
操作字元串資料
SQL Sever有許多函數和表達式,使你能對字元串進行有趣的操作,包括各種各樣的模式匹配和字元轉換。在這一節中,你將學習如何使用最重要的字元函數和表達式。
匹配萬用字元符號
假設你想建立一個與Yahoo功能相似的Internet目錄。你可以建立一個表用來存檔一系列的網站標簽,統一資源定位器(URL),描述,和類別,並允許存取者通過在HTML form中輸入關鍵字來檢索這些內容。
假如有一個存取者想從這個目錄中得到其描述中包含關鍵字trading card的網站的清單。要取出正確的網站清單,你也許試圖使用這樣的查詢:
SELECT site_name FROM site_directory WHERE site_desc=’trading card’
這個查詢可以工作。但是,它只能返回那些其描述中只有trading
card這個字元串的網站。例如,一個描述為We have the
greatest collection of trading cards in the world!的網站不會被返回。
要把一個字元串與另一個字元串的一部分相匹配,你需要使用萬用字元符號。你使用萬用字元符號和關鍵字LIKE來實現模式匹配。下面的語句使用萬用字元符號和關鍵字LIKE重寫了上面的查詢,以返回所有正確網站的名字:
SELECT
SITE_name FROM site_directory
WHERE site_desc LIKE ‘%trading cark%’
在這個例子中,所有其描述中包含表達式trading
card的網站都被返回。描述為We have the
greatest collection of trading cards in the world!的網站也被返回。當然,如果一個網站的描述中包含I am trading
cardboard boxes online ,該網站的名字也被返回。
注意本例中百分號的使用。百分號是萬用字元符號的例子之一。它代表0個或多個字元。通過把trading
card括在百分號中,所有其中嵌有字元串trading
card的字元串都被匹配。
現在,假設你的網站目錄變得太大而不能在一頁中完全顯示。你決定把目錄分成兩部分。在第一頁,你想顯示所有首字母在A到M之間的網站。在第二頁,你想顯示所有首字母在N到Z之間的網站。要得到第一頁的網站清單,你可以使用如下的SQL語句:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[A-M]%’
在這個例子中使用了表達式[A-M],只取出那些首字母在A到M之間的網站。中括號([])用來匹配處在指定範圍內的單個字元。要得到第二頁中顯示的網站,應使用這個語句:
SELECT
site_name FROM site_directory
WHERE site_name LIKE ‘[N-Z]%’
在這個例子中,括號中的表達式代表任何處在N到Z之間的單個字元。
假設你的網站目錄變得更大了,你現在需要把目錄分成更多頁。如果你想顯示那些以A,B或C開頭的網站,你可以用下面的查詢來實現:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[ABC]%’
在這個例子中,括號中的表達式不再指定一個範圍,而是給出了一些字元。任何一個其名字以這些字元中的任一個開頭的網站都將被返回。
通過在括號內的表達式中同時包含一個範圍和一些指定的字元,你可以把這兩種方法結合起來。例如,用下面的這個查詢,你可以取出那些首字母在C到F之間,或者以字母Y開頭的網站:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[C-FY]%’
在這個例子中,名字為Collegescape和Yahoo的網站會被選取,而名字為Magicw3的網站則不會被選取。
你也可以使用脫字元(^)來排除特定的字元。例如,要得到那些名字不以Y開頭的網站,你可以使用如下的查詢:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[^Y]%’
對給定的字元或字元範圍均可以使用脫字元。
最後,通過使用底線字元(_),你可以匹配任何單個字元。例如,下面這個查詢返回每一個其名字的第二個字元為任何字母的網站:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘M_crosoft’
這個例子既返回名為Microsoft的網站,也返回名為Macrosoft的網站。但是,名字為Moocrosoft的網站則不被返回。與萬用字元符號’%’不同,底線只代表單個字元。
注意:
如果你想匹配百分號或底線字元本身,你需要把它們括在方括號中。如果你想匹配連字元(-),應把它指定為方括號中的第一個字元。如果你想匹配方括號,應把它們也括在方括號中。例如,下面的語句返回所有其描述中包含百分號的網站:
SELECT site_name FROM site_directory WHERE site_desc LIKE ‘%[%]%’
匹配發音
Microsoft SQL 有兩個允許你按照發音來匹配字元串的函數。函數SOUNDEX()給一個字元串分配一個音標碼,函數DIFFERENCE()按照發音比較兩個字元串。當你不知道一個名字的確切拼寫,但多少知道一點它的發音時,使用這兩個函數將有助於你取出該記錄。
例如,如果你建立一個Internet目錄,你也許想增加一個選項,允許存取者按照網站名的發音來搜索網站,而不是按名字的拼寫。考慮如下的語句:
SELECT
site_name FROM site_directory
WHERE
DIFFERENCE(site_name , ‘Microsoft’>3
這個語句使用函數DEFFERENCE()來取得其名字的發音與Microsoft非常相似的網站。函數DIFFERENCE()返回一個0到4之間的數字。如果該函數返回4,表示發音非常相近;如果該函數返回0,敘述這兩個字元串的發音相差很大。
例如,上面的語句將返回網站名Microsoft和Macrosoft。這兩個名字的發音與Microsoft都很相似。如果你把上一語句中的大於3改為大於2,那麼名為Zicrosoft和Megasoft的網站也將被返回。最後,如果你只需要差別等級大於1即可,則名為Picosoft和Minisoft的網站也將被匹配。
要深入了解函數DIFFERENCE()是如何工作的,你可以用函數SOUNDEX()來返回函數DIFFERENCE()所使用的音標碼。這�埵酗@個例子:
SELECT site_name ‘site name’,SOUNDEX(site_name) ‘sounds like’
這個語句選取欄位site_name的所有資料及其音標碼。下面是這個查詢的結果:
site
name sounds like
……………………………………………………………….
Yahoo
Y000
Mahoo
M000
Microsoft
M262
Macrosoft
M262
Minisoft
M521
Microshoft
M262
Zicrosoft
Z262
Zaposoft
Z121
Millisoft
M421
Nanosoft
N521
Megasoft
M221
Picosoft
P221
(12
row(s) affected)
如果你仔細看一下音標碼,你會注意到音標碼的第一個字母與欄位值的第一個字母相同。例如,Yahoo和Mahoo的音標碼只有第一個字母不同。你還可以發現Microsoft和Macrosoft的音標碼完全相同。
函數DIFFERENDE()比較兩個字元串的第一個字母和所有的輔音字母。該函數忽略任何元音字母(包括y),除非一個元音字母是一個字元串的第一個字母。
不幸的是,使用SOUNDEX()和DIFFERENCE()有一個欠缺。WHERE子句中包含這兩個函數的查詢執行起來效果不好。因此,你應該小心使用這兩個函數。
刪除空格
有兩個函數,TTRIM()和LTRIM(),可以用來從字元串中剪掉空格。函數LTRIM()去除應該字元串前面的所有空格;函數RTRIM()去除一個字元串尾部的所有空格。這�埵酗@個任何使用函數RTRIM()的例子:
SELECT
RTRIM(site_name) FROM site_directory
在這個例子中,如果任何一個網站的名字尾部有多餘的空格,多餘的空格將從查詢結果中刪去。
你可以巢狀使用這兩個函數,把一個字元串前後的空格同時刪去:
SELECT
LTRIM(RTRIM(site_name) FROM site_directory
你會發現,在從CHAR型欄位中剪掉多餘的空格時,這兩個函數非常有用。記住,如果你把一個字元串存檔在CHAR型欄位中,該字元串會被追加多餘的空格,以匹配該欄位的長度。用這兩個函數,你可以去掉無用的空格,從而解決這個問題。
操作日期和時間
日期和時間函數對建立一個網站是非常有用的。網站的主人往往對一個表中的資料何時被更新感興趣。通過日期和時間函數,你可以在毫秒級跟蹤一個表的改變。
返回目前日期和時間
通過函數GETDATE(),你可以獲得目前的日期和時間。例如,語句SELECT
GETDATE()返回如下的結果:
……………………………..
NOV 30
1997 3:29AM
(1
row(s) affected)
顯然,如果你將來使用這個函數,你得到的日期將比這個時間晚,或者梗早。
函數GETDATE()可以用來作為DATEDIME()型欄位的內定值。這對插入記錄時存檔當時的時間是有用的。例如,假設有一個表用來存檔你網站上的活動日誌。每當有一個存取者存取到你的網站時,就在表中加入一條新記錄,記下存取者的名字,活動,和進行存取的時間。要建立一個表,其中的記錄包含有目前的日期和時間,可以加入一個DATETIME型欄位,指定其?筆≒滴??????ETDATE()的返回值,就象這樣:
CREATE
TABLE site_log (
username
VARCHAR(40),
useractivity
VARCHAR(100),
entrydate DATETIME DEFAULT
GETDATE())
轉換日期和時間
你也許已經注意到,在上一節的例子中,函數GETDATE()的返回值在顯示時只顯示到秒。實際上,SQL Sever內部時間可以精確到毫秒級(確切地說,可以精確到3.33毫秒)。
要得到不同格式的日期和時間,你需要使用函數CONVERT()。例如,當下面的這個語句執行時,顯示的時間將包括毫秒:
SELECT
CONVERT(VARCHAR(30),GETDATE(),9)
注意例子中數字9的使用。這個數字指明了在顯示日期和時間時使用哪種日期和時間格式。當這個語句執行時,將顯示如下的日期和時間:
…………………………………..
Nov 30
1997 3:29:55:170AM
(1
row(s) affected)
在函數CONVERT()中你可以使用許多種不同風格的日期和時間格式。表11.1顯示了所有的格式。
表11.1 日期和時間的型式
型式值 標準 輸出
0
Default mon dd yyyy hh:miAM
1 USA
mm/dd/yy
2 ANSI
yy.mm.dd
3
British/French dd/mm/yy
4
German dd.mm.yy
5
Italian dd-mm-yy
6 - dd
mon yy
7 - mon
dd,yy
8 -
hh:mi:ss
9.
Default + milliseconds--mon
dd yyyy
hh:mi:ss:mmmAM(or
)
10 USA
mm-dd-yy
11
JAPAN yy/mm/dd
12 ISO
yymmdd
13
Europe Default + milliseconds--dd mon yyyy
hh:mi:ss:mmm(24h)
14 -
hh:mi:ss:mmm(24h)
型式0,9,和13總是返回四位的年。對其它型式,要顯示世紀,把style值加上100。型式13和14返回24小時時鐘的時間。型式0,7,和13返回的月份用三位字元表示(用Nov代表November).
對表11.1中所列的每一種格式,你可以把型式值加上100來顯示有世紀的年(例如,00年將顯示為2000年)。例如,要按日本標準顯示日期,包括世紀,你應使用如下的語句:
SELECT
CONVERT(VARCHAR(30),GETDATE(),111)
在這個例子中,函數CONVERT()把日期格式進行轉換,顯示為1997/11/30
抽取日期和時間
在許多情況下,你也許只想得到日期和時間的一部分,而不是完整的日期和時間。例如,假設你想列出你的網站目錄中每個網站被查詢的月份。這時你不希望完整的日期和時間把網頁弄亂。為了抽取日期的特定部分,你可以使用函數DATEPART(),象這樣:
SELECT
site_name ‘Site Name’,
DATEPART(mm,site_entrydate)
‘Month Posted’ FROM
site_directory
函數DATEPART()的參數是兩個變數。第一個變數指定要抽取日期的哪一部分;第二個變數是實際的資料。在這個例子中,函數DATEPART()抽取月份,因為mm代表月份。下面是這個SELECT 語句的輸出結果:
Site
Name Month Posted
………………………………………………………………
Yahoo
2
Microsoft
5
Magicw3
5
(3
row(s) affected)
Month Posted列顯示了每個網站被查詢的月份。函數DATEPART()的返回值是一個整數。你可以用這個函數抽取日期的各個不同部分,如表11.2所示。
表11.2 日期的各部分及其簡寫
日期部分 簡寫 值
year yy
1753--9999
quarter
qq 1--4
month
mm 1--12
day of
year dy 1--366
day dd
1--31
week wk
1--53
weekday
dw 1--7(Sunday--Saturday)
hour hh
0--23
minute
mi 0--59
second
ss 0--59
milisecond ms
0--999
當你需要進行日期和時間的比較時,使用函數DATEPART()返回整數是有用的。但是,上例中的查詢結果(2,5)不是十分易讀。要以更易讀的格式得到部分的日期和時間,你可以使用函數DATENAME(),如下例所示:
SELECT
site_name ‘Site Name’
DATENAME(mm,site_entrydate)
‘Month
Posted’
FROM
site_directory
函數DATENAME()和函數DATEPART()接收同樣的參數。但是,它的返回值是一個字元串,而不是一個整數。下面是上例該用DATENAME()得到的結果:
Site
Name Month Postec
………………………………………………………………….
Yahoo
February
Microsoft
June
Magicw3
June
(3
row(s) affected)
你也可以用函數DATENAE()來抽取一個星期中的某一天。下面的這個例子同時抽取一周中的某一天和日期中的月份:
SELECT
site_name ‘Site Name’,
DATENAME(dw,site_entrydate)+
‘-’ + DATENAME(mm,site_entrydate)
‘Day and Month
Posted’ FORM
site_directory
這個例子執行時,將返回如下的結果:
Site
Name Day and Month Posted
………………………………………………………………………
Yahoo
Friday - February
Microsoft Tuesday -
June
Magicw3
Monday - June
(3
row(s) affected)
返回日期和時間範圍
當你分析表中的資料時,你也許希望取出某個特定時間的資料。你也許對特定的某一天中????比如說2000年12月25日????存取者在你網站上的活動感興趣。要取出這種型式的資料,你也許會試圖使用這樣的SELECT語句:
SELECT * FROM weblog WHERE entrydate=”12/25/20000”
不要這樣做。這個SELECT語句不會返回正確的記錄????它將只返回日期和時間是12/25/2000 12:00:00:000AM的記錄。換句話說,只有剛好在午夜零點輸入的記錄才被返回。
注意:
在本節的討論中,假設欄位entrydate是DATETIME型,而不是SMALLDATETIME型。本節的討論對SMALLDATETIME型欄位也是適用的,不過SMALLDATETIME型欄位只能精確到秒。
問題是SQL Sever將用完整的日期和時間代替部分日期和時間。例如,當你輸入一個日期,但不輸入時間時,SQL Sever將加上內定的時間“12:00:00:000AM”。當你輸入一個時間,但不輸入日期時,SQL Sever將加上內定的日期“Jan 1 1900”。
要返回正確的記錄,你需要適用日期和時間範圍。有不止一種途徑可以做到這一點。例如,下面的這個SELECT 語句將能返回正確的記錄:
SELECT
* FROM weblog
WHERE entrydate>=”12/25/2000” AND entrydate<”12/26/2000”
這個語句可以完成任務,因為它選取的是表中的日期和時間大於等於12/25/2000 12:00:00:000AM並小於12/26/2000 12:00:00:000AM的記錄。換句話說,它將正確地返回2000年聖誕節這一天輸入的每一條記錄。
另一種方法是,你可以使用LIKE來返回正確的記錄。通過在日期表達式中包含萬用字元符號“%”,你可以匹配一個特定日期的所有時間。這�埵酗@個例子:
SELECT * FROM weblog WHERE entrydate LIKE ‘Dec 25 2000%’
這個語句可以匹配正確的記錄。因為萬用字元符號“%”代表了任何時間。
使用這兩種匹配日期和時間範圍的函數,你可以選擇某個月,某一天,某一年,某個小時,某一分鐘,某一秒,甚至某一毫秒內輸入的記錄。但是,如果你使用LIKE 來匹配秒或毫秒,你首先需要使用函數CONVERT()把日期和時間轉換為更精確的格式(參見前面“轉換日期和時間”一節)。
比較日期和時間
最後,還有兩個日期和時間函數對根據日期和時間取出記錄是有用的。使用函數DATEADD()和DATEDIFF(),你可以比較日期的早晚。例如,下面的SELECT語句將顯示表中的每一條記錄已經輸入了多少個小時:
SELECT entrydate ‘Time Entered’
DATEDIFF(hh,entrydate,GETDATE())
‘Hours Ago’ FROM
weblog
如果目前時間是2000年11月30號下午6點15分,則會返回如下的結果:
Time
Entered Hours Ago
…………………………………………………..
Dec 30
2000 4:09PM 2
Dec 30
2000 4:13PM 2
Dec 1
2000 4:09PM 698
(3
row(s) affected)
函數DADEDIFF()的參數是三個變數。第個變數指定日期的某一部分。在這個例子中,是按小時對日期進行比較,(要了解日期各部分的詳細內容,請參考表11.2)在日期2000年11月1日和2000年11月30日的指定時間之間有689個小時。另外兩個參數是要進行比較的時間。為了返回一個正數,較早的時間應該先給。
函數DATEADD()把兩個日期相加。當你需要計算截止日期這一類的資料時,這個函數是有用處的。例如,假設存取者必須先註冊才能使用你的網站。註冊以後,他們可以免費使用你的網站一個月。要確定什麼時候他們的免費時間會用完,你可以使用如下的SELECT語句:
SELECT
username ‘User Name’,
DATEADD(mm,1,firstvisit_date) ‘Registration Expires’
FROM
registration_table
函數DATEADD()的參數有三個變數。第一個變數代表日期的某一部分(參見表11.2),這個例子用到了代表月份的mm。第二個變數指定了時間的間隔????在本例中是一個月。最後一個變數是一個日期,在這個例子中,日期是取自DATETIME型欄位firstvisit_date.假設目前日期是June
30,2000,這個語句將返回如下的內容:
User
Name Registration Expires
……………………………………………………………………………
Bill
Gates Jul 30 2000 4:09PM
President Clinton Jul 30
2000 4:13PM
William
Shakespeare Jul 1 2000 4:09PM
(3
row(s) affected)
注意:
與你預料的相反,使用函數DATEADD()把一個日期加上一個月,它並不加上30天。這個函數只簡單地把月份值加1。這意味著在11月註冊的人將比在2月註冊的人多得到2天或3天的時間。要避免這個問題,你可以用函數DATEADD()直接增加天數,而不是月份。
發送郵件
你可以用SQL Sever發送簡單的e_mail資訊。要做到這一點,你需要在你的系統中裝設郵件伺服器,如Microsoft
Exchange Sever(參見第四章“Exchange Active
Sever,Index Sever,和NetShow”)。你還需要組態SQL Sever 以識別郵件伺服器。
要讓SQL Sever 能識別郵件伺服器,啟動事務管理器並從選擇表中選擇Sever|SQL Mail|Configue,這時會出現一個如圖11.3所示的對話方塊。輸入你在郵件伺服器中註冊的用戶名和密碼,然後按一下OK。
注意:
如果你使用Microsoft
Exchange Sever,組態SQL Sever的過程將會大大不同。你需要在同一個(欄位)用戶帳號下執行Microsoft SQL
Sever和Exchange Sever。你還需要在裝設了SQL Sever的機器上裝設Exchange Cliect並給這個帳號建立一個一個組態文件。完成這些之後,你就可以在SQL Mail
Configuration對話方塊中輸入該組態文件的名字。
圖11。3
在發送郵件之前,你要先啟動SQL
Mail。從選擇表中選擇Sever|SQL
Mail|Start。如果你的郵件伺服器組態正確,並且你輸入了正確的用戶名和密碼,則SQL Mail會成功啟動。
注意:
你可以把SQL Sever組態為自動啟動郵件服務。要做到這一點,在Set Sever
Optons對話方塊(從選擇表中選擇Sever|SQL
Sever|Configure)中選擇Auto Start Mail
Client即可。
要發送一個郵件,你可以使用名為xp_sendmail的擴展存儲過程。這�埵酗@個如何使用這個過程的例子:
master..xp_sendmail “president@whitehouse.gov”,”Hello Mr. President”
這個過程呼叫向e_mail位址president@whitehouse.gov發送一個簡單的email資訊:“Hello Mr.
President”。你可以用任何其它的email位址和資訊取代上例中相應的內容,但是,你所發送的資訊不能超過255個字元長度。
當你想隨時了解你的網站資料庫的狀態時,存儲過程xp_sendmail是有用處的。例如,你可以向一個頁面管理程式發送資訊。如果你的網站出了什麼問題,你馬上就可以知道。下一章將講述更多有關存儲過程的內容。
總結
這一章加深了你的SQL知識。你學會了如何建立索引,使你的查詢速度更快。你還學會了如何插入,刪除和更新一個表中的資料,如何使用群組函數得到一個表中資料的統計資訊。最後,你學會了許多有價值的表達式,函數和過程,用來操作字元串,日期和時間及郵件。
下一章將進一步加深你對Microsoft SQL Sever的掌握。你將學習如何用SQL來進行程式設計,如何建立存儲過程,觸發器和執行計劃。更另人興奮的是,你將學會讓SQL Sever自動建立網頁的一個簡單方法。