操作數(shù)據(jù):中級SQL
發(fā)布時(shí)間:2008-05-10 閱讀數(shù): 次 來源:網(wǎng)樂原科技
本章內(nèi)容
■ 創(chuàng)建索引
■ SQL核心語句
■ 集合函數(shù)
■ 其它常用的SQL表達(dá)式,函數(shù),和過程
第十章“SQL基礎(chǔ)”向你初步介紹了SQL。你學(xué)會了如何用SELECT語句進(jìn)行查詢,你還學(xué)會了如何建立自己的表。在這一章里,你將加深你的SQL知識。你將學(xué)習(xí)如何建立索引來加快查詢速度。你還將學(xué)會如果用更多的SQL語句和函數(shù)來操作表中的數(shù)據(jù)。
建立索引
假設(shè)你想找到本書中的某一個(gè)句子。你可以一頁一頁地逐頁搜索,但這會花很多時(shí)間。而通過使用本書的索引,你可以很快地找到你要搜索的主題。
表的索引與附在一本書后面的索引非常相似。它可以極大地提高查詢的速度。對一個(gè)較大的表來說,通過加索引,一個(gè)通常要花費(fèi)幾個(gè)小時(shí)來完成的查詢只要幾分鐘就可以完成。因此沒有理由對需要頻繁查詢的表增加索引。
注意:
當(dāng)你的內(nèi)存容量或硬盤空間不足時(shí),也許你不想給一個(gè)表增加索引。對于包含索引的數(shù)據(jù)庫,SQL Sever需要一個(gè)可觀的額外空間。例如,要建立一個(gè)聚簇索引,需要大約1.2倍于數(shù)據(jù)大小的空間。要看一看一個(gè)表的索引在數(shù)據(jù)庫中所占的空間大小,你可以使用系統(tǒng)存儲過程sp_spaceused,對象名指定為被索引的表名。
聚簇索引和非聚簇索引
假設(shè)你已經(jīng)通過本書的索引找到了一個(gè)句子所在的頁碼。一旦已經(jīng)知道了頁碼后,你很可能漫無目的翻尋這本書,直至找到正確的頁碼。通過隨機(jī)的翻尋,你最終可以到達(dá)正確的頁碼。但是,有一種找到頁碼的更有效的方法。
首先,把書翻到大概一半的地方,如果要找的頁碼比半本書處的頁碼小,就書翻到四分之一處,否則,就把書翻到四分之三的地方。通過這種方法,你可以繼續(xù)把書分成更小的部分,直至找到正確的頁碼附近。這是找到書頁的非常有效的一種方法。
SQL Sever的表索引以類似的方式工作。一個(gè)表索引由一組頁組成,這些頁構(gòu)成了一個(gè)樹形結(jié)構(gòu)。根頁通過指向另外兩個(gè)頁,把一個(gè)表的記錄從邏輯上分成和兩個(gè)部分。而根頁所指向的兩個(gè)頁又分別把記錄分割成更小的部分。每個(gè)頁都把記錄分成更小的分割,直至到達(dá)葉級頁。
索引有兩種類型:聚簇索引和非聚簇索引。在聚簇索引中,索引樹的葉級頁包含實(shí)際的數(shù)據(jù):記錄的索引順序與物理順序相同。在非聚簇索引中,葉級頁指向表中的記錄:記錄的物理順序與邏輯順序沒有必然的聯(lián)系。
聚簇索引非常象目錄表,目錄表的順序與實(shí)際的頁碼順序是一致的。非聚簇索引則更象書的標(biāo)準(zhǔn)索引表,索引表中的順序通常與實(shí)際的頁碼順序是不一致的。一本書也許有多個(gè)索引。例如,它也許同時(shí)有主題索引和作者索引。同樣,一個(gè)表可以有多個(gè)非聚簇索引。
通常情況下,你使用的是聚簇索引,但是你應(yīng)該對兩種類型索引的優(yōu)缺點(diǎn)都有所理解。
每個(gè)表只能有一個(gè)聚簇索引,因?yàn)橐粋€(gè)表中的記錄只能以一種物理順序存放。通常你要對一個(gè)表按照標(biāo)識字段建立聚簇索引。但是,你也可以對其它類型的字段建立聚簇索引,如字符型,數(shù)值型和日期時(shí)間型字段。
從建立了聚簇索引的表中取出數(shù)據(jù)要比建立了非聚簇索引的表快。當(dāng)你需要取出一定范圍內(nèi)的數(shù)據(jù)時(shí),用聚簇索引也比用非聚簇索引好。例如,假設(shè)你用一個(gè)表來記錄訪問者在你網(wǎng)點(diǎn)上的活動。如果你想取出在一定時(shí)間段內(nèi)的登錄信息,你應(yīng)該對這個(gè)表的DATETIME型字段建立聚簇索引。
對聚簇索引的主要限制是每個(gè)表只能建立一個(gè)聚簇索引。但是,一個(gè)表可以有不止一個(gè)非聚簇索引。實(shí)際上,對每個(gè)表你最多可以建立249個(gè)非聚簇索引。你也可以對一個(gè)表同時(shí)建立聚簇索引和非聚簇索引。
假如你不僅想根據(jù)日期,而且想根據(jù)用戶名從你的網(wǎng)點(diǎn)活動日志中取數(shù)據(jù)。在這種情況下,同時(shí)建立一個(gè)聚簇索引和非聚簇索引是有效的。你可以對日期時(shí)間字段建立聚簇索引,對用戶名字段建立非聚簇索引。如果你發(fā)現(xiàn)你需要更多的索引方式,你可以增加更多的非聚簇索引。
非聚簇索引需要大量的硬盤空間和內(nèi)存。另外,雖然非聚簇索引可以提高從表中 取數(shù)據(jù)的速度,它也會降低向表中插入和更新數(shù)據(jù)的速度。每當(dāng)你改變了一個(gè)建立了非聚簇索引的表中的數(shù)據(jù)時(shí),必須同時(shí)更新索引。因此你對一個(gè)表建立非聚簇索引時(shí)要慎重考慮。如果你預(yù)計(jì)一個(gè)表需要頻繁地更新數(shù)據(jù),那么不要對它建立太多非聚簇索引。另外,如果硬盤和內(nèi)存空間有限,也應(yīng)該限制使用非聚簇索引的數(shù)量。
索引屬性
這兩種類型的索引都有兩個(gè)重要屬性:你可以用兩者中任一種類型同時(shí)對多個(gè)字段建立索引(復(fù)合索引);兩種類型的索引都可以指定為唯一索引。
你可以對多個(gè)字段建立一個(gè)復(fù)合索引,甚至是復(fù)合的聚簇索引。假如有一個(gè)表記錄了你的網(wǎng)點(diǎn)訪問者的姓和名字。如果你希望根據(jù)完整姓名從表中取數(shù)據(jù),你需要建立一個(gè)同時(shí)對姓字段和名字字段進(jìn)行的索引。這和分別對兩個(gè)字段建立單獨(dú)的索引是不同的。當(dāng)你希望同時(shí)對不止一個(gè)字段進(jìn)行查詢時(shí),你應(yīng)該建立一個(gè)對多個(gè)字段的索引。如果你希望對各個(gè)字段進(jìn)行分別查詢,你應(yīng)該對各字段建立獨(dú)立的索引。
兩種類型的索引都可以被指定為唯一索引。如果對一個(gè)字段建立了唯一索引,你將不能向這個(gè)字段輸入重復(fù)的值。一個(gè)標(biāo)識字段會自動成為唯一值字段,但你也可以對其它類型的字段建立唯一索引。假設(shè)你用一個(gè)表來保存你的網(wǎng)點(diǎn)的用戶密碼,你當(dāng)然不希望兩個(gè)用戶有相同的密碼。通過強(qiáng)制一個(gè)字段成為唯一值字段,你可以防止這種情況的發(fā)生。
用SQL建立索引
為了給一個(gè)表建立索引,啟動任務(wù)欄SQL Sever程序組中的ISQL/w程序。進(jìn)入查詢窗口后,輸入下面的語句:
CREATE INDEX mycolumn_index ON mytable (myclumn)
這個(gè)語句建立了一個(gè)名為mycolumn_index的索引。你可以給一個(gè)索引起任何名字,但你應(yīng)該在索引名中包含所索引的字段名,這對你將來弄清楚建立該索引的意圖是有幫助的。
注意:
在本書中你執(zhí)行任何SQL語句,都會收到如下的信息:
This command did not return data,and it did not return any rows
這說明該語句執(zhí)行成功了。
索引mycolumn_index對表mytable的mycolumn字段進(jìn)行。這是個(gè)非聚簇索引,也是個(gè)非唯一索引。(這是一個(gè)索引的缺省屬性)
如果你需要改變一個(gè)索引的類型,你必須刪除原來的索引并重建 一個(gè)。建立了一個(gè)索引后,你可以用下面的SQL語句刪除它:
DROP INDEX mytable.mycolumn_index
注意在DROP INDEX 語句中你要包含表的名字。在這個(gè)例子中,你刪除的索引是mycolumn_index,它是表mytable的索引。
要建立一個(gè)聚簇索引,可以使用關(guān)鍵字CLUSTERED。)記住一個(gè)表只能有一個(gè)聚簇索引。(這里有一個(gè)如何對一個(gè)表建立聚簇索引的例子:
CREATE CLUSTERED INDEX mycolumn_clust_index ON mytable(mycolumn)
如果表中有重復(fù)的記錄,當(dāng)你試圖用這個(gè)語句建立索引時(shí),會出現(xiàn)錯(cuò)誤。但是有重復(fù)記錄的表也可以建立索引;你只要使用關(guān)鍵字ALLOW_DUP_ROW把這一點(diǎn)告訴SQL Sever即可:
CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)
WITH ALLOW_DUP_ROW
這個(gè)語句建立了一個(gè)允許重復(fù)記錄的聚簇索引。你應(yīng)該盡量避免在一個(gè)表中出現(xiàn)重復(fù)記錄,但是,如果已經(jīng)出現(xiàn)了,你可以使用這種方法。
要對一個(gè)表建立唯一索引,可以使用關(guān)鍵字UNIQUE。對聚簇索引和非聚簇索引都可以使用這個(gè)關(guān)鍵字。這里有一個(gè)例子:
CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
這是你將經(jīng)常使用的索引建立語句。無論何時(shí),只要可以,你應(yīng)該盡量對一個(gè)對一個(gè)表建立唯一聚簇索引來增強(qiáng)查詢操作。
最后,要建立一個(gè)對多個(gè)字段的索引──復(fù)合索引──在索引建立語句中同時(shí)包含多個(gè)字段名。下面的例子對firstname和lastname兩個(gè)字段建立索引:
CREATE INDEX name_index ON username(firstname,lastname)
這個(gè)例子對兩個(gè)字段建立了單個(gè)索引。在一個(gè)復(fù)合索引中,你最多可以對16個(gè)字段進(jìn)行索引。
用事務(wù)管理器建立索引
用事務(wù)管理器建立索引比用SQL語句容易的多。使用事務(wù)管理器,你可以看到已經(jīng)建立的索引的列表,并可以通過圖形界面選擇索引選項(xiàng)。
使用事務(wù)管理器你可以用兩種方式建立索引:使用Manage Tables窗口或使用Manage Indexes窗口。
要用Manage Tables 窗口建立一個(gè)新索引,單擊按鈕Advanced Options(它看起來象一個(gè)前面有一加號的表)。這樣就打開了Advanced Options對話框。這個(gè)對話框有一部分標(biāo)名為Primary Key(見圖11.1)。
圖11。1
要建立一個(gè)新索引,從下拉列表中選擇你想對之建立索引的字段名。如果你想建立一個(gè)對多字段的索引,你可以選擇多個(gè)字段名。你還可以選擇索引是聚簇的還是非聚簇的。在保存表信息后,索引會自動被建立。在Manage Tables窗口中的字段名旁邊,會出現(xiàn)一把鑰匙。
你已經(jīng)為你的表建立了“主索引”。主索引必須對不包含空值的字段建立。另外,主索引強(qiáng)制一個(gè)字段成為唯一值字段。
要建立沒有這些限制的索引,你需要使用Manage Indexes窗口。從菜單中選擇Manage|Indexes,打開Manage Indexes 窗口。在Manage Indexes 窗口中,你可以通過下拉框選擇表和特定的索引。(見圖11.2)。要建立一個(gè)新索引,從Index下拉框中選擇New Index.,然后就可以選擇要對之建立索引的字段。單擊按鈕Add,把字段加人到索引中。
圖11。2
你可以為你的索引選擇許多不同的選項(xiàng)。例如,你可以選擇該索引是聚簇的還是非聚簇的。你還可以指定該索引為唯一索引。設(shè)計(jì)好索引后,單擊按鈕Build,建立該索引。
注意:
唯一索引是指該字段不能有重復(fù)的值,而不是只能建立這一個(gè)索引。
SQL核心語句
在第十章,你學(xué)會了如何用SQL SELECT 語句從一個(gè)表中取數(shù)據(jù)。但是,到現(xiàn)在為止,還沒有討論如何添加,修改或刪除表中的數(shù)據(jù)。在這一節(jié)中,你將學(xué)習(xí)這些內(nèi)容。
插入數(shù)據(jù)
向表中添加一個(gè)新記錄,你要使用SQL INSERT 語句。這里有一個(gè)如何使用這種語句的例子:
INSERT mytable (mycolumn) VALUES (‘some data’)
這個(gè)語句把字符串’some data’插入表mytable的mycolumn字段中。將要被插入數(shù)據(jù)的字段的名字在第一個(gè)括號中指定,實(shí)際的數(shù)據(jù)在第二個(gè)括號中給出。
INSERT 語句的完整句法如下:
INSERT [INTO] {table_name|view_name} [(column_list)] {DEFAULT VALUES |
Values_list | select_statement}
如果一個(gè)表有多個(gè)字段,通過把字段名和字段值用逗號隔開,你可以向所有的字段中插入數(shù)據(jù)。假設(shè)表mytable有三個(gè)字段first_column,second_column,和third_column。下面的INSERT語句添加了一條三個(gè)字段都有值的完整記錄:
INSERT mytable (first_column,second_column,third_column)
VALUES (‘some data’,’some more data’,’yet more data’)
注意:
你可以使用INSERT語句向文本型字段中插入數(shù)據(jù)。但是,如果你需要輸入很長的字符串,你應(yīng)該使用WRITETEXT語句。這部分內(nèi)容對本書來說太高級了,因此不加討論。要了解更多的信息,請參考Microsoft SQL Sever 的文檔。
如果你在INSERT 語句中只指定兩個(gè)字段和數(shù)據(jù)會怎么樣呢?換句話說,你向一個(gè)表中插入一條新記錄,但有一個(gè)字段沒有提供數(shù)據(jù)。在這種情況下,有下面的四種可能:
■ 如果該字段有一個(gè)缺省值,該值會被使用。例如,假設(shè)你插入新記錄時(shí)沒有給字段third_column提供數(shù)據(jù),而這個(gè)字段有一個(gè)缺省值’some value’。在這種情況下,當(dāng)新記錄建立時(shí)會插入值’some value’。
■ 如果該字段可以接受空值,而且沒有缺省值,則會被插入空值。
■ 如果該字段不能接受空值,而且沒有缺省值,就會出現(xiàn)錯(cuò)誤。你會收到錯(cuò)誤信息:
The column in table mytable may not be null.
■ 最后,如果該字段是一個(gè)標(biāo)識字段,那么它會自動產(chǎn)生一個(gè)新值。當(dāng)你向一個(gè)有標(biāo)識字段的表中插入新記錄時(shí),只要忽略該字段,標(biāo)識字段會給自己賦一個(gè)新值。
注意:
向一個(gè)有標(biāo)識字段的表中插入新記錄后,你可以用SQL變量@@identity來訪問新記錄
的標(biāo)識字段的值??紤]如下的SQL語句:
INSERT mytable (first_column) VALUES(‘some value’)
INSERT anothertable(another_first,another_second)
VALUES(@@identity,’some value’)
如果表mytable有一個(gè)標(biāo)識字段,該字段的值會被插入表anothertable的another_first字段。這是因?yàn)樽兞緻@identity總是保存最后一次插入標(biāo)識字段的值。
字段another_first應(yīng)該與字段first_column有相同的數(shù)據(jù)類型。但是,字段another_first不能是應(yīng)該標(biāo)識字段。Another_first字段用來保存字段first_column的值。
刪除記錄
要從表中刪除一個(gè)或多個(gè)記錄,需要使用SQL DELETE語句。你可以給DELETE 語句提供WHERE 子句。WHERE子句用來選擇要?jiǎng)h除的記錄。例如,下面的這個(gè)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子句 中使用。例如,下面的這個(gè)DELETE語句只刪除那些first_column字段的值為’goodbye’或second_column字段的值為’so long’的記錄:
DELETE mytable WHERE first_column=’goodby’ OR second_column=’so long’
如果你不給DELETE 語句提供WHERE 子句,表中的所有記錄都將被刪除。你不應(yīng)該有這種想法。如果你想刪除應(yīng)該表中的所有記錄,應(yīng)使用第十章所講的TRUNCATE TABLE語句。
注意:
為什么要用TRUNCATE TABLE 語句代替DELETE語句?當(dāng)你使用TRUNCATE TABLE語句時(shí),記錄的刪除是不作記錄的。也就是說,這意味著TRUNCATE TABLE 要比DELETE快得多。
更新記錄
要修改表中已經(jīng)存在的一條或多條記錄,應(yīng)使用SQL UPDATE語句。同DELETE語句一樣,UPDATE語句可以使用WHERE子句來選擇更新特定的記錄。請看這個(gè)例子:
UPDATE mytable SET first_column=’Updated!’ WHERE second_column=’Update Me!’
這個(gè)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語句。但是,如果你需要更新很長的字符串,應(yīng)使用UPDATETEXT語句。這部分內(nèi)容對本書來說太高級了,因此不加討論。要了解更多的信息,請參考Microsoft SQL Sever 的文檔。
如果你不提供WHERE子句,表中的所有記錄都將被更新。有時(shí)這是有用的。例如,如果你想把表titles中的所有書的價(jià)格加倍,你可以使用如下的UPDATE 語句:
你也可以同時(shí)更新多個(gè)字段。例如,下面的UPDATE語句同時(shí)更新first_column,second_column,和third_column這三個(gè)字段:
UPDATE mytable SET first_column=’Updated!’
Second_column=’Updated!’
Third_column=’Updated!’
WHERE first_column=’Update Me1’
技巧:
SQL忽略語句中多余的空格。你可以把SQL語句寫成任何你最容易讀的格式。
用SELECT 創(chuàng)建記錄和表
你也許已經(jīng)注意到,INSERT 語句與DELETE語句和UPDATE語句有一點(diǎn)不同,它一次只操作一個(gè)記錄。然而,有一個(gè)方法可以使INSERT 語句一次添加多個(gè)記錄。要作到這一點(diǎn),你需要把INSERT 語句與SELECT 語句結(jié)合起來,象這樣:
INSERT mytable (first_column,second_column)
SELECT another_first,another_second
FROM anothertable
WHERE another_first=’Copy Me!’
這個(gè)語句從anothertable拷貝記錄到mytable.只有表anothertable中字段another_first的值為’Copy Me!’的記錄才被拷貝。
當(dāng)為一個(gè)表中的記錄建立備份時(shí),這種形式的INSERT 語句是非常有用的。在刪除一個(gè)表中的記錄之前,你可以先用這種方法把它們拷貝到另一個(gè)表中。
如果你需要拷貝整個(gè)表,你可以使用SELECT INTO 語句。例如,下面的語句創(chuàng)建了一個(gè)名為newtable的新表,該表包含表mytable的所有數(shù)據(jù):
SELECT * INTO newtable FROM mytable
你也可以指定只有特定的字段被用來創(chuàng)建這個(gè)新表。要做到這一點(diǎn),只需在字段列表中指定你想要拷貝的字段。另外,你可以使用WHERE 子句來限制拷貝到新表中的記錄。下面的例子只拷貝字段second_columnd的值等于’Copy Me!’的記錄的first_column字段。
SELECT first_column INTO newtable
FROM mytable
WHERE second_column=’Copy Me!’
使用SQL修改已經(jīng)建立的表是很困難的。例如,如果你向一個(gè)表中添加了一個(gè)字段,沒有容易的辦法來去除它。另外,如果你不小心把一個(gè)字段的數(shù)據(jù)類型給錯(cuò)了,你將沒有辦法改變它。但是,使用本節(jié)中講述的SQL語句,你可以繞過這兩個(gè)問題。
例如,假設(shè)你想從一個(gè)表中刪除一個(gè)字段。使用SELECT INTO 語句,你可以創(chuàng)建該表的一個(gè)拷貝,但不包含要?jiǎng)h除的字段。這使你既刪除了該字段,又保留了不想刪除的數(shù)據(jù)。
如果你想改變一個(gè)字段的數(shù)據(jù)類型,你可以創(chuàng)建一個(gè)包含正確數(shù)據(jù)類型字段的新表。創(chuàng)建好該表后,你就可以結(jié)合使用UPDATE語句和SELECT 語句,把原來表中的所有數(shù)據(jù)拷貝到新表中。通過這種方法,你既可以修改表的結(jié)構(gòu),又能保存原有的數(shù)據(jù)。
集合函數(shù)
到現(xiàn)在為止,你只學(xué)習(xí)了如何根據(jù)特定的條件從表中取出一條或多條記錄。但是,假如你想對一個(gè)表中的記錄進(jìn)行數(shù)據(jù)統(tǒng)計(jì)。例如,如果你想統(tǒng)計(jì)存儲在表中的一次民意測驗(yàn)的投票結(jié)果?;蛘吣阆胫酪粋€(gè)訪問者在你的站點(diǎn)上平均花費(fèi)了多少時(shí)間。要對表中的任何類型的數(shù)據(jù)進(jìn)行統(tǒng)計(jì),都需要使用集合函數(shù)。
Microsoft SQL 支持五種類型的集合函數(shù)。你可以統(tǒng)計(jì)記錄數(shù)目,平均值,最小值,最大值,或者求和。當(dāng)你使用一個(gè)集合函數(shù)時(shí),它只返回一個(gè)數(shù),該數(shù)值代表這幾個(gè)統(tǒng)計(jì)值之一。
注意:
要在你的ASP網(wǎng)頁中使用集合函數(shù)的返回值,你需要給該值起一個(gè)名字。要作到這一點(diǎn),你可以在SELECT語句中,在集合函數(shù)后面緊跟一個(gè)字段名,如下例所示:
SELECT AVG(vote) ‘the_average’ FROM opinion
在這個(gè)例子中,vote的平均值被命名為 the_average?,F(xiàn)在你可以在你的ASP網(wǎng)頁的數(shù)據(jù)庫方法中使用這個(gè)名字。
統(tǒng)計(jì)字段值的數(shù)目
函數(shù)COUNT()也許是最有用的集合函數(shù)。你可以用這個(gè)函數(shù)來統(tǒng)計(jì)一個(gè)表中有多少條記錄。這里有一個(gè)例子:
SELECT COUNT(au_lname) FROM authors
這個(gè)例子計(jì)算表authors中名字(last name)的數(shù)目。如果相同的名字出現(xiàn)了不止一次,該名字將會被計(jì)算多次。如果你想知道名字為某個(gè)特定值的作者有多少個(gè),你可以使用WHERE子句,如下例所示:
SELECT COUNT(au_lname) FROM authors WHERE au_lname=’Ringer’
這個(gè)例子返回名字為’Ringer’的作者的數(shù)目。如果這個(gè)名字在表authors中出現(xiàn)了兩次,則次函數(shù)的返回值是2。
假如你想知道有不同名字的作者的數(shù)目。你可以通過使用關(guān)鍵字DISTINCT來得到該數(shù)目。如下例所示:
SELECT COUNT(DISTINCT au_lname) FROM authors
如果名字’Ringer’出現(xiàn)了不止一次,它將只被計(jì)算一次。關(guān)鍵字DISTINCT 決定了只有互不相同的值才被計(jì)算。
通常,當(dāng)你使用COUNT()時(shí),字段中的空值將被忽略。一般來說,這正是你所希望的。但是,如果你僅僅想知道表中記錄的數(shù)目,那么你需要計(jì)算表中所有的記錄─不管它是否包含空值。下面是一個(gè)如何做到這一點(diǎn)的例子:
SELECT COUNT(*) FROM authors
注意函數(shù)COUNT()沒有指定任何字段。這個(gè)語句計(jì)算表中所有記錄所數(shù)目,包括有空值的記錄。因此,你不需要指定要被計(jì)算的特定字段。
函數(shù)COUNT()在很多不同情況下是有用的。例如,假設(shè)有一個(gè)表保存了對你站點(diǎn)的質(zhì)量進(jìn)行民意調(diào)查的結(jié)果。這個(gè)表有一個(gè)名為vote的字段,該字段的值要么是0,要么是1。0表示反對票,1表示贊成票。要確定贊成票的數(shù)量,你可以所有下面的SELECT 語句:
SELECT COUNT(vote) FROM opinion_table WHERE vote=1
計(jì)算字段的平均值
使用函數(shù)COUNT(),你可以統(tǒng)計(jì)一個(gè)字段中有多少個(gè)值。但有時(shí)你需要計(jì)算這些值的平均值。使用函數(shù)AVG(),你可以返回一個(gè)字段中所有值的平均值。
假如你對你的站點(diǎn)進(jìn)行一次較為復(fù)雜的民意調(diào)查。訪問者可以在1到10之間投票,表示他們喜歡你站點(diǎn)的程度。你把投票結(jié)果保存在名為vote的INT型字段中。要計(jì)算你的用戶投票的平均值,你需要使用函數(shù)AVG():
SELECT AVG(vote) FROM opinion
這個(gè)SELECT語句的返回值代表用戶對你站點(diǎn)的平均喜歡程度。函數(shù)AVG()只能對數(shù)值型字段使用。這個(gè)函數(shù)在計(jì)算平均值時(shí)也忽略空值。
計(jì)算字段值的和
假設(shè)你的站點(diǎn)被用來出售卡片,已經(jīng)運(yùn)行了兩個(gè)月,是該計(jì)算賺了多少錢的時(shí)候了。假設(shè)有一個(gè)名為orders的表用來記錄所有訪問者的定購信息。要計(jì)算所有定購量的總和,你可以使用函數(shù)SUM():
SELECT SUM(purchase_amount) FROM orders
函數(shù)SUM()的返回值代表字段purchase_amount中所有值的平均值。字段purchase_amount的數(shù)據(jù)類型也許是MONEY型,但你也可以對其它數(shù)值型字段使用函數(shù)SUM()。
返回最大值或最小值
再一次假設(shè)你有一個(gè)表用來保存對你的站點(diǎn)進(jìn)行民意調(diào)查的結(jié)果。訪問者可以選擇從1到10 的值來表示他們對你站點(diǎn)的評價(jià)。如果你想知道訪問者對你站點(diǎn)的最高評價(jià),你可以使用如下的語句:
SELECT MAX(vote) FROM opinion
你也許希望有人對你的站點(diǎn)給予了很高的評價(jià)。通過函數(shù)MAX(),你可以知道一個(gè)數(shù)值型字段的所有值中的最大值。如果有人對你的站點(diǎn)投了數(shù)字10,函數(shù)MAX()將返回該值。
另一方面,假如你想知道訪問者對你站點(diǎn)的的最低評價(jià),你可以使用函數(shù)MIN(),如下例所示:
SELECT MIN(vote) FROM opinion
函數(shù)MIN()返回一個(gè)字段的所有值中的最小值。如果字段是空的,函數(shù)MIN()返回空值。
其它常用的SQL表達(dá)式,函數(shù),和過程
這一節(jié)將介紹一些其它的SQL技術(shù)。你將學(xué)習(xí)如何從表中取出數(shù)據(jù),其某個(gè)字段的值處在一定的范圍,你還將學(xué)習(xí)如何把字段值從一種類型轉(zhuǎn)換成另一種類型,如何操作字符串和日期時(shí)間數(shù)據(jù)。最后,你將學(xué)會一個(gè)發(fā)送郵件的簡單方法。
通過匹配一定范圍的值來取出數(shù)據(jù)
假設(shè)你有一個(gè)表用來保存對你的站點(diǎn)進(jìn)行民意調(diào)查的結(jié)果?,F(xiàn)在你想向所有對你的站點(diǎn)的評價(jià)在7到10之間的訪問者發(fā)送書面的感謝信。要得到這些人的名字,你可以使用如下的SELECT 語句:
SELECT username FROM opinion WHERE vote>6 and vote<11
這個(gè)SELECT 語句會實(shí)現(xiàn)你的要求。你使用下面的SELECT 語句也可以得到同樣的結(jié)果:
SELECT username FROM opinion WHERE vote BETWEEN 7 AND 10
這個(gè)SELECT 語句與上一個(gè)語句是等價(jià)的。使用哪一種語句是編程風(fēng)格的問題,但你會發(fā)現(xiàn)使用表達(dá)式BETWEEN 的語句更易讀。
現(xiàn)在假設(shè)你只想取出對你的站點(diǎn)投了1或者10的訪問者的名字。要從表opinion中取出這些名字,你可以使用如下的SELECT 語句:
SELECT username FROM opinion WHERE vote=1 or vote
這個(gè)SELECT語句會返回正確的結(jié)果,沒有理由不使用它。但是,存在一種等價(jià)的方式。使用如下的SELECT可以得到相同的結(jié)果:
SELECT username FROM opinion WHERE vote IN (1,10)
注意表達(dá)式IN 的使用。這個(gè)SELECT 語句只取出vote的值等于括號中的值之一的記錄。
你也可以使用IN來匹配字符數(shù)據(jù)。例如,假設(shè)你只想取出Bill Gates或President Clinton的投票值。你可以使用如下的SELECT 語句:
SELECT vote FROM opinion WHERE username IN (‘Bill Gates’,’President Clinton’)
最后,你可以在使用BETWEEN或IN的同時(shí)使用表達(dá)式NOT。例如,要取出那些投票值不在7到10之間的人的名字,你可以使用如下的SELECT 語句:
SELECT username FROM opinion WHERE vote NOT BETWEEN 7 and 10
要選取那些某個(gè)字段的值不在一列值之中的記錄,你可以同時(shí)使用NOT 和IN,如下例所示:
SELECT vote FROM opinion
WHERE username NOT IN (‘Bill Gates’,’President Clinton’)
你不是必須在SQL語句中使用BETWEEN或IN,但是,要使你的查詢更接近自然語言,這兩個(gè)表達(dá)式是有幫助的。
轉(zhuǎn)換數(shù)據(jù)
SQL Sever足夠強(qiáng)大,可以在需要的時(shí)候把大部分?jǐn)?shù)值從一種類型轉(zhuǎn)換為另一種類型。例如,要比較SMALLINT型和INT型數(shù)據(jù)的大小,你不需要進(jìn)行顯式的類型轉(zhuǎn)換。SQL Sever會為你完成這項(xiàng)工作。但是,當(dāng)你想在字符型數(shù)據(jù)和其它類型的數(shù)據(jù)之間進(jìn)行轉(zhuǎn)換時(shí),你的確需要自己進(jìn)行轉(zhuǎn)換操作。例如,假設(shè)你想從一個(gè)MONEY型字段中取出所有的值,并在結(jié)果后面加上字符串“US Dollars”。你需要使用函數(shù)CONVERT(),如下例所示:
SELECT CONVERT(CHAR(8),price)+’US Dollars’ FROM orders
函數(shù)CONVERT()帶有兩個(gè)變量。第一個(gè)變量指定了數(shù)據(jù)類型和長度。第二個(gè)變量指定了要進(jìn)行轉(zhuǎn)換的字段。在這個(gè)例子中,字段price被轉(zhuǎn)換成長度為8個(gè)字符的CHAR型字段。字段price要被轉(zhuǎn)換成字符型,才可以在它后面連接上字符串’US Dollars’。
當(dāng)向BIT型,DATETIME型,INT型,或者NUMERIC型字段添加字符串時(shí),你需要進(jìn)行同樣的轉(zhuǎn)換操作。例如,下面的語句在一個(gè)SELECT語句的查詢結(jié)果中加入字符串’The vote is’,該SELECT語句返回一個(gè)BIT型字段的值:
SELECT ‘The vote is’+CONVERT(CHAR(1),vote) FROM opinion
下面是這個(gè)語句的結(jié)果示例:
The vote is 1
The vote is 1
The vote is 0
(3 row(s) affected)
如果你不進(jìn)行顯式的轉(zhuǎn)換,你會收到如下的錯(cuò)誤信息:
Implicit conversion from datatype ‘varchar’ to ‘bit’ is not allowec.
Use the CONVERT function to run this query.
操作字符串?dāng)?shù)據(jù)
SQL Sever有許多函數(shù)和表達(dá)式,使你能對字符串進(jìn)行有趣的操作,包括各種各樣的模式匹配和字符轉(zhuǎn)換。在這一節(jié)中,你將學(xué)習(xí)如何使用最重要的字符函數(shù)和表達(dá)式。
匹配通配符
假設(shè)你想建立一個(gè)與Yahoo功能相似的Internet目錄。你可以建立一個(gè)表用來保存一系列的站點(diǎn)名稱,統(tǒng)一資源定位器(URL),描述,和類別,并允許訪問者通過在HTML form中輸入關(guān)鍵字來檢索這些內(nèi)容。
假如有一個(gè)訪問者想從這個(gè)目錄中得到其描述中包含關(guān)鍵字trading card的站點(diǎn)的列表。要取出正確的站點(diǎn)列表,你也許試圖使用這樣的查詢:
SELECT site_name FROM site_directory WHERE site_desc=’trading card’
這個(gè)查詢可以工作。但是,它只能返回那些其描述中只有trading card這個(gè)字符串的站點(diǎn)。例如,一個(gè)描述為We have the greatest collection of trading cards in the world!的站點(diǎn)不會被返回。
要把一個(gè)字符串與另一個(gè)字符串的一部分相匹配,你需要使用通配符。你使用通配符和關(guān)鍵字LIKE來實(shí)現(xiàn)模式匹配。下面的語句使用通配符和關(guān)鍵字LIKE重寫了上面的查詢,以返回所有正確站點(diǎn)的名字:
SELECT SITE_name FROM site_directory
WHERE site_desc LIKE ‘%trading cark%’
在這個(gè)例子中,所有其描述中包含表達(dá)式trading card的站點(diǎn)都被返回。描述為We have the greatest collection of trading cards in the world!的站點(diǎn)也被返回。當(dāng)然,如果一個(gè)站點(diǎn)的描述中包含I am trading cardboard boxes online ,該站點(diǎn)的名字也被返回。
注意本例中百分號的使用。百分號是通配符的例子之一。它代表0個(gè)或多個(gè)字符。通過把trading card括在百分號中,所有其中嵌有字符串trading card的字符串都被匹配。
現(xiàn)在,假設(shè)你的站點(diǎn)目錄變得太大而不能在一頁中完全顯示。你決定把目錄分成兩部分。在第一頁,你想顯示所有首字母在A到M之間的站點(diǎn)。在第二頁,你想顯示所有首字母在N到Z之間的站點(diǎn)。要得到第一頁的站點(diǎn)列表,你可以使用如下的SQL語句:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[A-M]%’
在這個(gè)例子中使用了表達(dá)式[A-M],只取出那些首字母在A到M之間的站點(diǎn)。中括號([])用來匹配處在指定范圍內(nèi)的單個(gè)字符。要得到第二頁中顯示的站點(diǎn),應(yīng)使用這個(gè)語句:
SELECT site_name FROM site_directory
WHERE site_name LIKE ‘[N-Z]%’
在這個(gè)例子中,括號中的表達(dá)式代表任何處在N到Z之間的單個(gè)字符。
假設(shè)你的站點(diǎn)目錄變得更大了,你現(xiàn)在需要把目錄分成更多頁。如果你想顯示那些以A,B或C開頭的站點(diǎn),你可以用下面的查詢來實(shí)現(xiàn):
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[ABC]%’
在這個(gè)例子中,括號中的表達(dá)式不再指定一個(gè)范圍,而是給出了一些字符。任何一個(gè)其名字以這些字符中的任一個(gè)開頭的站點(diǎn)都將被返回。
通過在括號內(nèi)的表達(dá)式中同時(shí)包含一個(gè)范圍和一些指定的字符,你可以把這兩種方法結(jié)合起來。例如,用下面的這個(gè)查詢,你可以取出那些首字母在C到F之間,或者以字母Y開頭的站點(diǎn):
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[C-FY]%’
在這個(gè)例子中,名字為Collegescape和Yahoo的站點(diǎn)會被選取,而名字為Magicw3的站點(diǎn)則不會被選取。
你也可以使用脫字符(^)來排除特定的字符。例如,要得到那些名字不以Y開頭的站點(diǎn),你可以使用如下的查詢:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[^Y]%’
對給定的字符或字符范圍均可以使用脫字符。
最后,通過使用下劃線字符(_),你可以匹配任何單個(gè)字符。例如,下面這個(gè)查詢返回每一個(gè)其名字的第二個(gè)字符為任何字母的站點(diǎn):
SELECT site_name FROM site_directory WHERE site_name LIKE ‘M_crosoft’
這個(gè)例子既返回名為Microsoft的站點(diǎn),也返回名為Macrosoft的站點(diǎn)。但是,名字為Moocrosoft的站點(diǎn)則不被返回。與通配符’%’不同,下劃線只代表單個(gè)字符。
注意:
如果你想匹配百分號或下劃線字符本身,你需要把它們括在方括號中。如果你想匹配連字符(-),應(yīng)把它指定為方括號中的第一個(gè)字符。如果你想匹配方括號,應(yīng)把它們也括在方括號中。例如,下面的語句返回所有其描述中包含百分號的站點(diǎn):
SELECT site_name FROM site_directory WHERE site_desc LIKE ‘%[%]%’
匹配發(fā)音
Microsoft SQL 有兩個(gè)允許你按照發(fā)音來匹配字符串的函數(shù)。函數(shù)SOUNDEX()給一個(gè)字符串分配一個(gè)音標(biāo)碼,函數(shù)DIFFERENCE()按照發(fā)音比較兩個(gè)字符串。當(dāng)你不知道一個(gè)名字的確切拼寫,但多少知道一點(diǎn)它的發(fā)音時(shí),使用這兩個(gè)函數(shù)將有助于你取出該記錄。
例如,如果你建立一個(gè)Internet目錄,你也許想增加一個(gè)選項(xiàng),允許訪問者按照站點(diǎn)名的發(fā)音來搜索站點(diǎn),而不是按名字的拼寫??紤]如下的語句:
SELECT site_name FROM site_directory
WHERE DIFFERENCE(site_name , ‘Microsoft’>3
這個(gè)語句使用函數(shù)DEFFERENCE()來取得其名字的發(fā)音與Microsoft非常相似的站點(diǎn)。函數(shù)DIFFERENCE()返回一個(gè)0到4之間的數(shù)字。如果該函數(shù)返回4,表示發(fā)音非常相近;如果該函數(shù)返回0,說明這兩個(gè)字符串的發(fā)音相差很大。
例如,上面的語句將返回站點(diǎn)名Microsoft和Macrosoft。這兩個(gè)名字的發(fā)音與Microsoft都很相似。如果你把上一語句中的大于3改為大于2,那么名為Zicrosoft和Megasoft的站點(diǎn)也將被返回。最后,如果你只需要差別等級大于1即可,則名為Picosoft和Minisoft的站點(diǎn)也將被匹配。
要深入了解函數(shù)DIFFERENCE()是如何工作的,你可以用函數(shù)SOUNDEX()來返回函數(shù)DIFFERENCE()所使用的音標(biāo)碼。這里有一個(gè)例子:
SELECT site_name ‘site name’,SOUNDEX(site_name) ‘sounds like’
這個(gè)語句選取字段site_name的所有數(shù)據(jù)及其音標(biāo)碼。下面是這個(gè)查詢的結(jié)果:
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)
如果你仔細(xì)看一下音標(biāo)碼,你會注意到音標(biāo)碼的第一個(gè)字母與字段值的第一個(gè)字母相同。例如,Yahoo和Mahoo的音標(biāo)碼只有第一個(gè)字母不同。你還可以發(fā)現(xiàn)Microsoft和Macrosoft的音標(biāo)碼完全相同。
函數(shù)DIFFERENDE()比較兩個(gè)字符串的第一個(gè)字母和所有的輔音字母。該函數(shù)忽略任何元音字母(包括y),除非一個(gè)元音字母是一個(gè)字符串的第一個(gè)字母。
不幸的是,使用SOUNDEX()和DIFFERENCE()有一個(gè)欠缺。WHERE子句中包含這兩個(gè)函數(shù)的查詢執(zhí)行起來效果不好。因此,你應(yīng)該小心使用這兩個(gè)函數(shù)。
刪除空格
有兩個(gè)函數(shù),TTRIM()和LTRIM(),可以用來從字符串中剪掉空格。函數(shù)LTRIM()去除應(yīng)該字符串前面的所有空格;函數(shù)RTRIM()去除一個(gè)字符串尾部的所有空格。這里有一個(gè)任何使用函數(shù)RTRIM()的例子:
SELECT RTRIM(site_name) FROM site_directory
在這個(gè)例子中,如果任何一個(gè)站點(diǎn)的名字尾部有多余的空格,多余的空格將從查詢結(jié)果中刪去。
你可以嵌套使用這兩個(gè)函數(shù),把一個(gè)字符串前后的空格同時(shí)刪去:
SELECT LTRIM(RTRIM(site_name) FROM site_directory
你會發(fā)現(xiàn),在從CHAR型字段中剪掉多余的空格時(shí),這兩個(gè)函數(shù)非常有用。記住,如果你把一個(gè)字符串保存在CHAR型字段中,該字符串會被追加多余的空格,以匹配該字段的長度。用這兩個(gè)函數(shù),你可以去掉無用的空格,從而解決這個(gè)問題。
操作日期和時(shí)間
日期和時(shí)間函數(shù)對建立一個(gè)站點(diǎn)是非常有用的。站點(diǎn)的主人往往對一個(gè)表中的數(shù)據(jù)何時(shí)被更新感興趣。通過日期和時(shí)間函數(shù),你可以在毫秒級跟蹤一個(gè)表的改變。
返回當(dāng)前日期和時(shí)間
通過函數(shù)GETDATE(),你可以獲得當(dāng)前的日期和時(shí)間。例如,語句SELECT GETDATE()返回如下的結(jié)果:
……………………………..
NOV 30 1997 3:29AM
(1 row(s) affected)
顯然,如果你將來使用這個(gè)函數(shù),你得到的日期將比這個(gè)時(shí)間晚,或者梗早。
函數(shù)GETDATE()可以用來作為DATEDIME()型字段的缺省值。這對插入記錄時(shí)保存當(dāng)時(shí)的時(shí)間是有用的。例如,假設(shè)有一個(gè)表用來保存你站點(diǎn)上的活動日志。每當(dāng)有一個(gè)訪問者訪問到你的站點(diǎn)時(shí),就在表中添加一條新記錄,記下訪問者的名字,活動,和進(jìn)行訪問的時(shí)間。要建立一個(gè)表,其中的記錄包含有當(dāng)前的日期和時(shí)間,可以添加一個(gè)DATETIME型字段,指定其缺省值為函數(shù)GETDATE()的返回值,就象這樣:
CREATE TABLE site_log (
username VARCHAR(40),
useractivity VARCHAR(100),
entrydate DATETIME DEFAULT GETDATE())
轉(zhuǎn)換日期和時(shí)間
你也許已經(jīng)注意到,在上一節(jié)的例子中,函數(shù)GETDATE()的返回值在顯示時(shí)只顯示到秒。實(shí)際上,SQL Sever內(nèi)部時(shí)間可以精確到毫秒級(確切地說,可以精確到3.33毫秒)。
要得到不同格式的日期和時(shí)間,你需要使用函數(shù)CONVERT()。例如,當(dāng)下面的這個(gè)語句執(zhí)行時(shí),顯示的時(shí)間將包括毫秒:
SELECT CONVERT(VARCHAR(30),GETDATE(),9)
注意例子中數(shù)字9的使用。這個(gè)數(shù)字指明了在顯示日期和時(shí)間時(shí)使用哪種日期和時(shí)間格式。當(dāng)這個(gè)語句執(zhí)行時(shí),將顯示如下的日期和時(shí)間:
…………………………………..
Nov 30 1997 3:29:55:170AM
(1 row(s) affected)
在函數(shù)CONVERT()中你可以使用許多種不同風(fēng)格的日期和時(shí)間格式。表11.1顯示了所有的格式。
表11.1 日期和時(shí)間的類型
類型值 標(biāo)準(zhǔn) 輸出
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總是返回四位的年。對其它類型,要顯示世紀(jì),把style值加上100。類型13和14返回24小時(shí)時(shí)鐘的時(shí)間。類型0,7,和13返回的月份用三位字符表示(用Nov代表November).
對表11.1中所列的每一種格式,你可以把類型值加上100來顯示有世紀(jì)的年(例如,00年將顯示為2000年)。例如,要按日本標(biāo)準(zhǔn)顯示日期,包括世紀(jì),你應(yīng)使用如下的語句:
SELECT CONVERT(VARCHAR(30),GETDATE(),111)
在這個(gè)例子中,函數(shù)CONVERT()把日期格式進(jìn)行轉(zhuǎn)換,顯示為1997/11/30
抽取日期和時(shí)間
在許多情況下,你也許只想得到日期和時(shí)間的一部分,而不是完整的日期和時(shí)間。例如,假設(shè)你想列出你的站點(diǎn)目錄中每個(gè)站點(diǎn)被查詢的月份。這時(shí)你不希望完整的日期和時(shí)間把網(wǎng)頁弄亂。為了抽取日期的特定部分,你可以使用函數(shù)DATEPART(),象這樣:
SELECT site_name ‘Site Name’,
DATEPART(mm,site_entrydate) ‘Month Posted’ FROM site_directory
函數(shù)DATEPART()的參數(shù)是兩個(gè)變量。第一個(gè)變量指定要抽取日期的哪一部分;第二個(gè)變量是實(shí)際的數(shù)據(jù)。在這個(gè)例子中,函數(shù)DATEPART()抽取月份,因?yàn)閙m代表月份。下面是這個(gè)SELECT 語句的輸出結(jié)果:
Site Name Month Posted
………