1. 指定檢索條件
為了限制SELECT 語句檢索出來的記錄集,可使用WHERE 子句,它給出選擇行的條件。可通過查找滿足各種條件的列值來選擇行。
可查找數(shù)字值:
也可以查找串值。(注意,一般串的比較是不區(qū)分大小寫的。)
可以查找日期值:
可搜索組合值:
WHERE 子句中的表達(dá)式可使用表1-1中的算術(shù)運(yùn)算符、表1-2 的比較運(yùn)算符和表1-3 的邏輯運(yùn)算符。還可以使用圓括號將一個(gè)表達(dá)式分成幾個(gè)部分??墒褂贸A?、表列和函數(shù)來完成運(yùn)算。在本教程的查詢中,我們有時(shí)使用幾個(gè)MySQL函數(shù),但是MySQL的函數(shù)遠(yuǎn)不止這里
給出的這些。請參閱附錄C,那里給出了所有MySQL函數(shù)的清單。
錯(cuò)了,因?yàn)檫@個(gè)查詢的意思是“選擇既出生在Vi rginia 又出生在M a r y l a n d的總統(tǒng)”,不可能有同時(shí)出生在兩個(gè)地點(diǎn)的總統(tǒng),因此這個(gè)查詢無意義。在英語中,可以用“a n d”表示這種選擇,但在SQL 中,應(yīng)該用OR 來連接兩個(gè)條件,如下所示:
這有時(shí)是可以覺察到的,不僅僅是在編寫自己的查詢時(shí)可以覺察到,而且在為他人編寫查詢時(shí)也可以知道。最好是在他人描述想要檢索什么時(shí)仔細(xì)聽,但不一定使用相同的邏輯運(yùn)算符將他人的描述轉(zhuǎn)錄成SQL 語句。對剛才所舉的例子,正確的英語等價(jià)描述為“選擇出生在Vi rginia 或者出生在Maryland 的總統(tǒng)。”
2. NULL 值
NULL 值是特殊的;因?yàn)樗?#8220;無值”。不可能以評估兩個(gè)已知值的相同方式來將它與已知值進(jìn)行評估。如果試圖與通常的算術(shù)比較運(yùn)算符一道使用NULL,其結(jié)果是未定義的:
為了進(jìn)行NULL 值的搜索,必須采用特殊的語法。不能用= 或!= 來測試等于NULL 或不等于NULL,取而代之的是使用IS NULL 或IS NOT NULL 來測試。例如,因?yàn)槲覀儗⒔≡诳偨y(tǒng)的死亡日期表示為NULL,那么可按如下語句查找健在的總統(tǒng):
3. 對查詢結(jié)果進(jìn)行排序
有時(shí)我們注意到,在一個(gè)表裝入初始數(shù)據(jù)后,對其發(fā)布一條SELECT * FROM tbl_name查詢,檢索出的行與這些行被插入的順序是相同的。但不要認(rèn)為這種情況是有規(guī)律的。如果在初始裝入表后進(jìn)行了行的刪除和插入,就會發(fā)現(xiàn)服務(wù)器返回表的行次序被改變了。(刪除記錄在表中留下了未使用的“空位”,MySQL在以后插入新記錄時(shí)將會試圖對其填補(bǔ)。)缺省時(shí),如果選擇了行,服務(wù)器對返回行的次序不作任何保證。為了對行進(jìn)行排序,可
使用ORDER BY 子句:
在ORDER BY 子句中,可在列名之后利用ASC 或DESC 關(guān)鍵字指定排序是按該列值的升序或降序進(jìn)行的。例如,為了按倒序(降序)名排列總統(tǒng)名,可如下使用DESC:
如果在ORDER BY 子句中,對某個(gè)列名既不指定ASC 又不指定DESC,則缺省的次序?yàn)樯?。在對可能包含NULL 值的列進(jìn)行排序時(shí),如果是升序排序, NULL 值出現(xiàn)在最前面,如果是按降序排序,NULL 值出現(xiàn)在最后。
4. 限制查詢結(jié)果如果一個(gè)查詢返回許多行,但您只想看其中的幾行,則可以利用LIMIT 子句,特別是與ORDER BY 子句結(jié)合時(shí)更是如此。MySQL允許限制一個(gè)查詢的輸出為前n 行。下面的查詢選擇了5 位出生日期最早的總統(tǒng):
如果利用ORDER BY birth DESC 按降序排序,將得到5 位最晚出生的總統(tǒng)。LIMIT 也可以從查詢結(jié)果中取出中間部分。為了做到這一點(diǎn),必須指定兩個(gè)值。第一個(gè)值為結(jié)果中希望看到的第一個(gè)記錄(第一個(gè)結(jié)果記錄的編號為0 而不是1)。第二個(gè)值為希望看到的記錄個(gè)數(shù)。下面的查詢類似于前面那個(gè)查詢,但只顯示從第11行開始的5 個(gè)記錄:
自MySQL3.23.2 以來,可按照一個(gè)公式來排序查詢結(jié)果。例如,利用ORDER BYRAND( ) 與LIMIT 結(jié)合,從president 表中隨機(jī)抽取一個(gè)記錄:
5. 計(jì)算并命名輸出的列值
此查詢把名和姓連接起來,中間間隔一個(gè)空格,將總統(tǒng)名形成一個(gè)單一字符串,而且將出生城市和州連接在一起,中間隔一個(gè)逗號,形成出生地。
在利用表達(dá)式來計(jì)算列值時(shí),此表達(dá)式被用作列標(biāo)題。如果表達(dá)式很長(如前面的一些查詢樣例中那樣),那么可能會出現(xiàn)一個(gè)很寬的列。為了處理這種情況,此列可利用AS name結(jié)構(gòu)來重新命名標(biāo)題。這樣的名稱為列別名。用這種方法可使上面的輸出更有意義,如下所示:
6. 使用日期
在MySQL中使用日期時(shí)要記住的是,在表示日期時(shí)首先給出年份。1999 年7 月27 日表示為“1999 - 07 - 27”,而不是像通常那樣表示為“ 07 - 27 - 1999”或“27 - 07 - 1999”。MySQL提供了幾種對日期進(jìn)行處理的方法。可以對日期進(jìn)行的一些運(yùn)算如下:
■ 按日期排序。(這點(diǎn)我們已經(jīng)看到幾次了。)
■ 查找特定的日期或日期范圍。
■ 提取日期值的組成部分,如年、月或日。
■ 計(jì)算日期的差。
■ 日期增加或減去一個(gè)間隔得出另一日期。
下面給出一些日期運(yùn)算的例子。
為了查找特定的日期,可使用精確的日期值或與其他日期值進(jìn)行比較,將一個(gè)DATE 列與有關(guān)的日期值進(jìn)行比較:
為了更詳細(xì),詳細(xì)到天,可組合測試MONTH( ) 和DAYOFMONTH( ) 以找出在筆者的生日出生的總統(tǒng):
這是一種可用來生成類似報(bào)紙上娛樂部分所刊登的那種“這些人今天過生日”清單的查詢。但是,不必按前面的查詢那樣插入一個(gè)特殊的日期。為了查找每年的今天出生的總統(tǒng),只要將他們的生日與C U R R E N T _ DATE 進(jìn)行比較即可:
可從一個(gè)日期減去另一個(gè)日期。這樣可以知道日期間的間隔,這對于確定年齡是非常有用的。例如,為了確定哪位總統(tǒng)活得最長,可將其逝世日期減去出生日期。為此,可利用函數(shù)TO _ DAYS( ) 將出生日期和逝世日期轉(zhuǎn)換為天數(shù),求出差,然后除以365 得出大概的年齡:
此查詢中所用的FLOOR( ) 函數(shù)截掉了年齡的小數(shù)部分,得到一個(gè)整數(shù)。得出日期之差,還可以確定相對于某個(gè)特定日期有多長時(shí)間。這樣可以告訴歷史同盟的會員,他們還有多久就應(yīng)該更新自己的會員資格了。計(jì)算他們的截止日期和當(dāng)前日期之差,如果小于某個(gè)閾值,則不久就需要更新了。下面的查詢是查找需要在60 天內(nèi)更新的會員:
自MySQL3.22 以來,可使用DATE_ADD( ) 或DATE_SUB( ) 從一個(gè)日期計(jì)算另一個(gè)日期。這些函數(shù)取一個(gè)日期及時(shí)間間隔并產(chǎn)生一個(gè)新日期。例如:
本節(jié)中前面給出的一個(gè)查詢選擇70 年代逝世的總統(tǒng),它對選擇范圍的端點(diǎn)使用直接的日期值。該查詢可以利用一個(gè)字符串日期和一個(gè)由開始日期和時(shí)間間隔計(jì)算出的結(jié)束日期來重寫:
會員更新查詢可根據(jù)DATE_ADD( ) 寫出如下:
本章前面給出了一個(gè)查詢?nèi)缦?,確定不久要來檢查但還沒來診所的牙科病人:
現(xiàn)在回過頭來看,讀者會更清楚這個(gè)查詢的含義了。
7. 模式匹配
MySQL允許查找與某個(gè)模式相配的值。這樣,可以選擇記錄而不用提供精確的值。為了進(jìn)行模式匹配運(yùn)算,可使用特殊的運(yùn)算符( LIKE 和NOT LIKE),并且指定一個(gè)包含通配符的串。字符“_”匹配任意單個(gè)字符,而“%”匹配任意字符序列(包括空序列)。使用L I K E或NOT LIKE 的模式匹配都是不區(qū)分大小寫的。下列模式匹配以“W”或“w”開始的姓:
MySQL還提供基于擴(kuò)展正規(guī)表達(dá)式的模式匹配。正規(guī)表達(dá)式在附錄C 的REGEXP 運(yùn)算符的介紹中描述。
8. 生成匯總
MySQL所能做的最有用的事情是濃縮大量的原始數(shù)據(jù)行并對其進(jìn)行匯總。當(dāng)學(xué)會了利用MySQL來生成匯總時(shí),它就變成了用戶強(qiáng)有力的好幫手了,因?yàn)槭止みM(jìn)行匯總是一項(xiàng)冗長的、費(fèi)時(shí)的、易出錯(cuò)的工作。匯總的一種簡單的形式是確定在一組值中哪些值是唯一值。利用DISTINCT 關(guān)鍵字來刪除結(jié)果中的重復(fù)行。例如,總統(tǒng)出生的各個(gè)州可按如下找出:
其他的匯總形式涉及計(jì)數(shù),可利用COUNT( ) 函數(shù)。如果使用COUNT (*),它將給出查詢所選擇的行數(shù)。如果一個(gè)查詢無WHERE 子句,COUNT(*) 將給出表中的行數(shù)。下列查詢給出共有多少人當(dāng)過美國總統(tǒng):
如果查詢有WHERE 子句,COUNT(*) 將給出此子句選擇多少行。下面的查詢給出目前為止對班級進(jìn)行了多少次測試:
COUNT(*) 對選中的行進(jìn)行計(jì)數(shù)。而COUNT(col_name) 只對非NULL 值進(jìn)行計(jì)數(shù)。下面的查詢說明了這些差異:
這表示,總共有41位總統(tǒng),他們中只有一個(gè)具有名字后綴,并且大多數(shù)總統(tǒng)都已去世。自MySQL3.23.2 以來,可以將COUNT( ) 與DISTINCT 組合對選擇結(jié)果集中不同的值進(jìn)行計(jì)數(shù)。例如,為了對總統(tǒng)出生的不同州進(jìn)行計(jì)數(shù),可執(zhí)行下列查詢:
可以根據(jù)匯總列中單獨(dú)的值對計(jì)數(shù)值進(jìn)行分解。例如,您可能根據(jù)下列的查詢結(jié)果知道班級中所有學(xué)生的人數(shù):
但是,有多少是男孩?有多少是女孩?分別得出男孩、女孩的一種方法是分別對每種性別進(jìn)行計(jì)數(shù):
如果以這種方法對值計(jì)數(shù), GROUP BY 子句是必須的;它告訴MySQL在對值計(jì)數(shù)之前怎樣進(jìn)行聚集。如果將其省去,則要出錯(cuò)。COUNT(*) 與GROUP BY 一起用來對值進(jìn)行計(jì)數(shù)比分別對每個(gè)不同的列值進(jìn)行計(jì)數(shù)有更多的優(yōu)點(diǎn),這些優(yōu)點(diǎn)是:
■ 不必事先知道要匯總的列中有些什么值。
■ 不用編寫多個(gè)查詢,只需編寫單個(gè)查詢即可。
■ 用單一查詢就可以得出所有結(jié)果,因此可以對結(jié)果進(jìn)行排序。
前兩個(gè)優(yōu)點(diǎn)對于更方便地表示查詢很重要。第三個(gè)優(yōu)點(diǎn)也較為重要,因?yàn)樗峁┝孙@示
結(jié)果的靈活性。在使用GROUP BY 子句時(shí),其結(jié)果是在要分組的列上進(jìn)行排序的,但是可以
使用ORDER BY 來按不同的次序進(jìn)行排序。例如,如果想得到各州產(chǎn)生的總統(tǒng)人數(shù),并按產(chǎn)
生人數(shù)最多的州優(yōu)先排出,可以如下使用ORDER BY 子句:
如果希望進(jìn)行排序的列是從計(jì)算得出的,則可以給該列一個(gè)別名,并在ORDER BY 子句中引用這個(gè)別名。前面的查詢說明了這一點(diǎn); COUNT(*) 列的別名為count。引用這樣的列的另一種方法是引用它在輸出結(jié)果中的位置。前面的查詢可編寫如下:
如果不想用LIMIT 子句來限制查詢輸出,而是利用查找特定的COUNT( ) 值來達(dá)到這個(gè)目的,可使用HAVING 子句。下面的查詢給出了產(chǎn)生兩個(gè)以上總統(tǒng)的州:
從更為普遍的意義上說,這是一種在要查找的列中重復(fù)值時(shí)執(zhí)行的查詢類型。HAVING 類似于WHERE,但它是在查詢結(jié)果已經(jīng)選出后才應(yīng)用的,用來縮減服務(wù)器實(shí)際送到客戶機(jī)的結(jié)果。除了COUNT( ) 外還有許多匯總函數(shù)。MIN( )、MAX( )、SUM( ) 和AVG( ) 函數(shù)在確定列的最大、最小、總數(shù)和平均值時(shí)都非常有用,甚至可以同時(shí)使用它們。下面的查詢得出給定的測試和測驗(yàn)的各種數(shù)字特性。它還給出有多少學(xué)分參與了每個(gè)值的計(jì)算(有的學(xué)生可能缺曠或未計(jì)入)。
此查詢選擇已經(jīng)去世的總統(tǒng),按出生地對他們進(jìn)行分組,并計(jì)算出他們逝世時(shí)的年齡,計(jì)算出平均年齡(每個(gè)州的),然后按平均年齡進(jìn)行排序。換句話說,此查詢按所出生地確定已故總統(tǒng)的平均壽命。但這說明了什么呢?它僅僅說明您可寫該查詢,當(dāng)然并不說明此查詢是否值得寫。并不是用一個(gè)數(shù)據(jù)庫可以做的所有事情都同樣有意義;但是,人們有時(shí)在發(fā)現(xiàn)可以利用自己的數(shù)據(jù)庫進(jìn)行查詢時(shí)感到很開心。這可能說明關(guān)于轉(zhuǎn)播運(yùn)動會的不斷增加的深?yuàn)W的(空洞的)統(tǒng)計(jì)數(shù)據(jù)在過去幾年里正在不斷增多的原因。運(yùn)動統(tǒng)計(jì)者可以使用他們的數(shù)據(jù)庫來計(jì)算出某個(gè)隊(duì)的歷史紀(jì)錄,而這些數(shù)字你可能感興趣,也可能毫無興致。
9. 從多個(gè)表中檢索信息
到目前為止,我們所編寫的查詢都是從單個(gè)表中得到數(shù)據(jù)的?,F(xiàn)在,我們將進(jìn)行一件更為有趣的工作。以前筆者曾經(jīng)提到過,關(guān)系DBMS 的強(qiáng)大功能在于它能夠?qū)⒁粯訓(xùn)|西與另一樣?xùn)|西相關(guān)聯(lián),因?yàn)檫@樣使得能夠結(jié)合多個(gè)表中的信息來解答單個(gè)表不能解答的問題。本節(jié)介紹怎樣編寫這種查詢。在從多個(gè)表中選擇信息時(shí),需要執(zhí)行一種稱為連接( j o i n)的操作。這是因?yàn)樾枰獙⒁粋€(gè)表中的信息與其他表中的信息相連接來得出查詢結(jié)果。即通過協(xié)調(diào)各表中的值來完成這項(xiàng)工作。
此查詢找出具有給定日期的記錄,然后利用該記錄中的事件ID 查找具有相同事件ID 的學(xué)分。對于每個(gè)匹配的事件記錄和學(xué)分記錄組合,顯示學(xué)生ID、學(xué)分、日期和事件類型。此查詢在兩個(gè)重要方面不同于我們曾經(jīng)編寫過的其他查詢。它們是:
■ FROM 子句給出了不止一個(gè)表名,因?yàn)槲覀円獧z索的數(shù)據(jù)來自不止一個(gè)表:
FROM event,score
■ WHERE 子句說明event 和score 表是由每個(gè)表中的event_id 值的匹配連接起來的:
where event.event_id=score.event_id
請注意,我們是怎樣利用tbl_name.col_name 語法引用列,以便MySQL知道引用的是哪些表的列。(event_id 出現(xiàn)在兩個(gè)表中,如果不用表名來限定它的話將會出現(xiàn)混淆。)此查詢中的其他列( date、score、type)可單獨(dú)使用而不用表名限定符,因?yàn)樗鼈冊诒碇兄怀霈F(xiàn)一次,從而不會出現(xiàn)含混。但是,一般在連接中我們對每個(gè)列都進(jìn)行限定以便清晰地表示出每個(gè)列是屬于哪個(gè)表。在完全限定的形式下,查詢?nèi)缦拢?/p>
從現(xiàn)在起,我們將使用完全限定的形式。第二步,我們利用student 表完成查詢以便顯示學(xué)生名。(第一步中查詢的輸出給出了student_id 字段,但是名字更有意義。)名字顯示是利用score 表和student 表兩者都具有student_id 列,使它們中的記錄可被連接這個(gè)事實(shí)來完成的。最終的查詢?nèi)缦拢?/p>
■ student 表被增加到了FROM 子句中,因?yàn)槌薳vent 表和score 表外還用到了它。
■ student_id 列現(xiàn)在不明確了(因?yàn)楝F(xiàn)在有兩個(gè)引用到的表都含有此列),因此必須限定為score.student_id 或student.student_id 以表明使用的是哪個(gè)表。
■ WHERE 子句有一個(gè)附加項(xiàng),它說明根據(jù)學(xué)生ID 將score 表記錄與student 表記錄進(jìn)行匹配。
■ 此查詢是顯示學(xué)生名而不是學(xué)生ID。(當(dāng)然,如果愿意的話,可以兩者都顯示。)利用此查詢,可以加入任意日期,得到該日期的學(xué)分,用學(xué)生名和學(xué)分類型完善查詢結(jié)果。不一定要了解關(guān)于學(xué)生ID 或事件ID 的情況。MySQL小心地得出相關(guān)的ID 值并利用它們自動地使各表的行相配。
學(xué)分保存方案涉及的另一項(xiàng)工作是匯總學(xué)生的缺勤情況。缺勤情況是按學(xué)生ID 和日期在absence 表中記錄的。為得到學(xué)生名(而不僅僅是ID),我們需要根據(jù)student_id 的值將absence 表連接到student 表。下面的查詢給出了學(xué)生的ID 號和名字以及缺勤計(jì)數(shù):
前面,在“生成匯總”一節(jié)中,我們執(zhí)行了一個(gè)查詢,它生成score 表中數(shù)據(jù)的數(shù)值特征。該查詢的輸出列出了事件ID,但不包括學(xué)分日期或類型,因?yàn)槲覀儾恢涝鯓訉core 表連接到event 表以得到學(xué)分的日期和類型。現(xiàn)在可以做到了。下面的查詢類似于早先的那個(gè),但是它給出了學(xué)分的日期和類型而不只是簡單的數(shù)字事件ID:
可利用諸如COUNT( ) 和AVG( ) 這樣的函數(shù)生成多個(gè)列上的匯總,即使這些列來自不同的表也是如此。下面的查詢確定學(xué)分?jǐn)?shù),以及事件日期與學(xué)生性別的每種組合的平均學(xué)分。
我們可以使用一個(gè)類似的查詢來完成學(xué)分保存方案的一個(gè)任務(wù),即在學(xué)期末計(jì)算每個(gè)學(xué)生的總學(xué)分。相應(yīng)的查詢?nèi)缦拢?/p>
此查詢有兩個(gè)技巧性的東西:
■ 我們需要使用同一表的兩個(gè)實(shí)例,因此建立了表的別名( p 1、p 2),并利用它們無歧義地引用表列。
■ 每個(gè)總統(tǒng)的記錄與自身相匹配,但是我們不希望在輸出中看到同一總統(tǒng)出再現(xiàn)兩次。WHERE 子句的第二行保證比較的記錄為不同總統(tǒng)的記錄,使記錄不與自身匹配??梢跃帉懸粋€(gè)查找出生在同一天的總統(tǒng)的類似查詢。出生日期不能直接比較,因?yàn)槟菢訒e(cuò)過出生在不同年份的總統(tǒng)。我們用MONTH( ) 和DAYOFMONTH( ) 來比較出生日期的月和日,相應(yīng)的查詢?nèi)缦拢?/p>
利用DAYOFYEAR( ) 而不是MONTH( ) 和DAYOFMONTH( ) 將得出一個(gè)更為簡單的查詢,但是在比較閏年日期與非閏年日期時(shí)將會得出不正確的結(jié)果。迄今所執(zhí)行的連接結(jié)合了來自那些在某種意義上具有邏輯關(guān)系的表中的信息,但是只有您知道該關(guān)系無意義。MySQL并不知道(或不關(guān)心)所連接的表相互之間是否相關(guān)。例如,可將event 表連接到president 表以找出在某個(gè)總統(tǒng)生日那天是否進(jìn)行了測驗(yàn)或測試,此查詢?nèi)缦拢?/p>
它產(chǎn)生了您所想要的東西。但說明了什么呢?這說明MySQL將愉快地制造出結(jié)果,至于這些結(jié)果是否有意義它不管。這是因?yàn)槟褂玫氖怯?jì)算機(jī),所以它不能自動地判斷查詢的結(jié)果有用或無用。無論如何,我們都必須為自己所做的事負(fù)責(zé)。