MySQL入門

本章主要介紹MySQL關係數據庫管理系統(Relational Database Management System,RDBMS)和MySQL所使用的結構化查詢語言(Structured Query Language,SQL)。其中,列出了應該掌握的基本術語和概念,描述了示例所要用到的樣本數據庫sampdb,而且提供了一個用於展現如何使用`MySQL建立數據庫並與之進行交互操做的教程。html

若是你對數據庫系統還不甚瞭解,或者還不是很確定本身是否須要學習它,甚至還不願定是否須要使用它,那麼請從本章開始。若是你對MySQL和SQL還一無所知,而且須要一個入門指南,那麼也請從本章開始。對MySQL或其餘數據庫系統已有必定經驗的讀者,則能夠略讀一下本章的內容。不過,爲了你能對全書所用那個數據庫sampdb的目的和內容更熟悉,但願能夠閱讀一下1.2節。mysql

1.1 MySQL的用途

本節描述的是MySQL數據庫系統的用途,具體描述了「MySQL能夠幹什麼」,以及「它如何才能給你提供幫助」。若是你已經明白數據庫的用途(也許你正在思考某個問題,到這裏來只是想要找出「如何使用MySQL來解決它」的答案),那麼能夠直接翻閱到1.2節。程序員

數據庫系統在本質上是一種高效的管理大量列表信息的辦法。這些信息的來源可能多種多樣。它多是研究數據、業務記錄、客戶需求、體育統計、銷售報告、我的信息、人事檔案、bug報告或者學生成績。發揮數據庫系統強大做用的時機在於:想要組織和管理的信息很是龐大或複雜,以致於全部記錄採用手工處理會變得異常繁重。對於天天處理上百萬條事務的大公司來講,數據庫是必不可少的。不過,即便只有一我的的小公司,也可能會維護不少的信息,甚至多到須要用一個數據庫來管理它。假設有下面這些狀況。正則表達式

  • 你在牙科診所工做,在那裏須要管理好患者的跟蹤記錄:何人什麼時候到訪、作了些什麼、下次預定信息、保險信息等。
  • 你收集了多年的研究數據,爲了發表而須要對它們進行分析。你要從大量原始數據裏提煉出摘要性的信息,並取出選中的觀察子集進行詳細的統計分析。
  • 你是一名教師,須要跟蹤學生的成績和考勤。每次考試結束,你都須要記錄每個學生的分數。雖然將成績記錄到成績冊上很簡單,但以後的成績分析卻很繁瑣。你很想避免爲了肯定分數曲線,而對每次考試的分數進行排序;也真的很不肯意在期末時爲了肯定最終成績,而把每一個學生的成績都加起來。統計每一個學生的考勤也很無趣。
  • 你在某個組織機構(多是一個專業團體、一個俱樂部、一個交響樂團或者一個健身俱樂部)擔任祕書一職,具體負責維護機構成員名錄的工做。你每一年都要爲全部成員生成一份打印名錄,名錄是用文字處理軟件管理的,每當成員資料有變化時你都得編輯更新。你很是厭倦這種維護名錄的方式,由於它限制了你的發揮,主要表如今:很難對名錄條目按不一樣方式進行排序;沒法輕鬆地選到每一條目的指定部分,如列出人名和電話號碼;更不能輕鬆地找出一組的成員,如須要儘快更新成員資格的成員。若是有辦法,就能夠省卻每個月經過翻閱這些條目來找出那些須要更新成員資格的成員的工做。你據說過「無紙化辦公」,知道它是電子化記錄發展的結果,但你還未見過它所帶來的任何好處。雖然成員資格記錄是電子化的,但具備諷刺意味的是,它們記錄的形式除了能將名錄打印成紙質的之外,很難用做他途!

上面這些場景所涉及的信息量有大有小。但它們都有一個共同的特色,即這些工做均可以經過手工來完成,但使用數據庫系統來管理會更高效。sql

在使用像MySQL這樣的數據庫系統時,你但願從中得到哪些具體好處呢?這取決於你的特殊須要和需求,而且,如同上面示例所示,具體的好處都各不相同。不過,在通常狀況下,適合於用數據庫管理系統來處理任務的人羣是那些不使用數據庫管理系統就要使用文件櫃的人。事實上,數據庫系統就像一個內置了複雜文件系統的巨大文件櫃。與手工管理記錄的方式相比,以電子化方式來管理記錄存在有許多優點。下面來看看前面描述過的那個牙科診所的場景。在用於管理患者記錄的文檔系統能力方面,MySQL能夠爲你帶來下面這些幫助。shell

(1)縮短記錄歸檔時間。你不用在文件櫃裏挨個拉抽屜找存放新記錄的地方。你只需將它提交給MySQL,MySQL會爲你找到正確的存放這條記錄地方。數據庫

(2)縮短記錄檢索時間。當進行記錄查找時,沒必要爲了想要找到的記錄而親自動手去逐個搜索。爲了給那些最近一段時間沒來作檢查的患者發個提醒信息,你可讓MySQL來幫你找出這些記錄。固然,這與讓你告訴另外一我的「請幫忙確認一下最近6個月哪些患者沒來參加檢查」的狀況有所不一樣。事實上,你「念出的」是一段奇怪的「咒語」:express

SELECT last_name, first_name, last_visit FROM patient WHERE last_visit < DATE_SUB(CURDATE(), INTERVAL 6 MONTH);

若是你之前從沒見過相似的內容,那麼它初看起來可能會至關嚇人。不過,它的效果倒是至關吸引人:你不用再花費一小時來翻看你的記錄,只需一兩秒便可獲得想要的結果。無論怎樣,無需多久你就會習慣這種奇形怪狀的表達。等閱讀完本章,你便會明白它真正的含義。編程

(3)靈活的檢索排序。不用嚴格按照記錄存儲的順序(如按患者的名字排序)來檢索它們。MySQL能夠按任何你想要的順序來提取記錄,如按名字、保險公司名字、上次就診時間等。vim

(4)靈活的輸出格式。在找到感興趣的記錄以後,你無需手動複製這些信息,MySQL會爲你生成一個列表。有時,你可能只是想打印這些信息;有時,你可能會想在另外一個程序裏使用它們。例如,在生成了最近逾期未能複診的患者名單後,你即可以把這些資料輸送到某個文字處理軟件,讓它打印出你想要發送給那些患者的通知單。或許,你只是對如同「選中記錄統計」這樣的彙總信息比較感興趣。你不用親自來統計記錄,MySQL會替你生成彙總信息。

(5)多用戶同時訪問記錄。對於紙質記錄,若是有兩我的同時想要查看某個記錄,那麼其中一我的就必須等待另外一我的將這個記錄歸還以後才能查看。而MySQL則能夠容許這兩我的同時訪問這個記錄。

(6)記錄的遠程訪問與電子傳輸。想用紙質記錄,就得親自跑到存放它們的地方,或者讓人將它們複印以後再發送給你。電子記錄則爲遠程訪問記錄或者電子傳輸記錄提供了可能。若是你的牙科集團設有許多分支機構,那麼這些機構裏的人員即可以在當地訪問到你的資料。你徹底不用再經過快遞來傳送這些副本。若是有人須要記錄,但卻又沒有與你同樣的數據庫軟件,那麼你能夠選擇那些所需的記錄,將其中的內容經過網絡發送給他。

若是你曾經用過數據庫管理系統,那麼確定對剛纔描述的種種好處深有體會,並且可能也在思索如何才能超越常規「代替文件櫃」應用程序的限制。有許多組織機構將數據庫與網站結合在一塊兒使用,這就是一種很好的方式。假設貴公司有一個商品庫存數據庫,每當顧客打電話過來詢問倉庫裏是否有貨、其價格是多少時,服務檯的員工便會用到它。這是一種比較傳統的數據庫使用方式。不過,若是貴公司搭建一個供顧客訪問的網站,那麼就能夠提供一項新的服務:建立一個搜索頁面,讓顧客能夠查看條目,從而肯定價格、可用性以及商品的庫存狀況。若是支持在線訂購,那麼顧客甚至不用離開家門便能購買到你的產品。這讓顧客得到了他們想要的信息,而數據庫倒是根據提問自動搜索商品庫存信息來提供這些內容的。顧客當即得到了所關心的信息,不用一邊聽着惱人的錄音,一邊傻等着;也不用受限於服務檯的上下班時間。而且,每當有一位顧客使用貴公司網站,就意味着會少一個電話,而這是須要由一個在服務檯拿工資的人來處理的事情。如此看來,該網站或許能夠爲本身買單。

不過,你還能夠更進一步地發揮數據庫的做用。基於Web的庫存搜索請求,不只能夠把信息提供給顧客,也能提供給貴公司。這些查詢可讓你瞭解到顧客正在查找哪些商品,而查詢結果則會讓你知道是否可以知足他們的需求。在某種程度上,若是你沒有顧客想要的東西,那麼你可能會錯失這筆生意。所以,記錄下庫存搜索信息很是有意義,經過它能夠了解到:顧客正在找尋什麼商品,而你是否還有存貨。接着,就能夠根據這些信息來調整庫存,並向顧客提供更好的服務。

說了半天,那麼MySQL是如何工做的呢?找到答案的最好方式就是本身動手體驗一下。爲此,咱們須要有一個能夠操做的數據庫。


1.2 示例數據庫

本節將描述本書所使用的示例數據庫。在你學習使用MySQL的過程當中,能夠用這個數據庫提供的示例進行嘗試,該數據庫是針對以前描述過的兩種狀況設計的。

  • 「機構祕書」場景。該機構有一些特色:其成員對美國曆史很感興趣(因沒有一個更好的名字,因此姑且稱其爲「美史聯盟」)。全部成員都要按期繳納必定的費用以維持其成員資格。繳納的費用會用於一些正常開支,如出版通信——《Chronicles of U.S. Past 》(美國編年史)。該聯盟運營着一個小型網站,但還未被充分開發出來,而你很想要扭轉這一局面。
  • 「成績考評」場景。你是一名教師,要負責考評期間的各類考試與測驗,記錄分數和打分。以後,你要肯定出最終成績,並把它們隨同考勤狀況一同上交到學校辦公室。

下面,咱們進一步分析一下這兩個場景的需求。

  • 你必須決定數據庫裏的哪些內容是你所想要的——即那些你要實現的目標。
  • 你必須決定須要將哪些內容放入數據庫——即你想要跟蹤的數據是什麼。

在考慮「將什麼放入數據庫」以前考慮「要從數據庫裏得到什麼樣的內容」,這彷佛有些本末倒置。畢竟,你們都是這麼認爲的:要先輸入數據,而後才能檢索它。不過,你使用數據庫的方式取決於你的目標是什麼。並且,與想要放入數據庫的內容相比,想要從數據庫檢索的內容則與那些目標的關係更加密切。只有在計劃從此使用這些信息以後,你纔會想要花費時間和精力把它們放入數據庫。

1.2.1 美史聯盟項目

該項目的場景是:你是聯盟祕書,眼下正使用某個文字處理文檔維護那份成員資格名單。對於像「生成打印名錄」這樣的事情,處理起來固然沒什麼問題。可是,當你想要得到其餘更多信息時,就會受到限制。你還有幾個目標要實現。

  • 你想要以不一樣格式輸出名錄,同時按照不一樣的應用程序來定製信息。有一個目標是每一年生成打印好的名錄——這是聯盟一直以來的一個需求,要繼續實施下去。你可能還會想到名錄信息的其餘用途,例如向出席聯盟年會的人員提供一份打印好的最新成員名單。這兩個應用程序涉及的信息是不一樣的。打印名錄程序須要用到每一個成員條目的全部內容。而年會程序則只須要提取出成員的姓名便可(使用文字處理軟件沒法輕鬆完成這項工做)。
  • 你想要在名錄裏搜索出知足不一樣條件成員。例如,你想要知道近期有哪些成員須要更新成員資格。你還須要另外一個搜索相關的應用程序,用來維護每一個成員各自的關鍵字列表。這些關鍵字描述了全部成員都特別感興趣的一些美國曆史時期,如南北戰爭(Civil War)、經濟大蕭條(Depression)、民權法案(civil right)、托馬斯·傑斐遜(Thomas Jefferson)總統的平生事蹟。有時,有些成員會要求你爲他們提供一份與其志趣相投的其餘成員名單,而你也很願意知足這些需求。
  • 你想要將名錄發佈到聯盟網站上去。這樣作能讓你和全部成員都受益。若是你能經過某些自動化的過程將名錄轉換爲Web頁面,那麼名錄的在線版本將總能保持最新,而這是紙質形式沒法作到的。若是在線名錄能支持搜索功能,那麼成員們便能輕鬆地自行查找信息。例如,若是某成員想要知道其餘還有誰對「南北戰爭」感興趣,那麼他即可以自行查找,徹底用不着等你幫他搜索,而你也不用抽時間去處理這件事情。

數據庫不是世界上最使人興奮的工具,所以我不會鼓吹像「使用數據庫能激發人的創造性思惟」這樣不切實際的話。不過,若是你再也不把信息當作是一種累贅(如同你在使用文字處理文檔時所想的那樣),而是把它想成是可相對輕鬆處理的事情(如同你但願使用MySQL來處理同樣),那麼你天然會釋放出本身的潛能,找到更多新的使用這些信息的方法。

  • 若是數據庫中的信息可以以在線名錄的形式放到網站上去,那麼你也可讓這些信息以其餘方式流轉。例如,讓成員可以在線修改他們本身的資料,並更新到數據庫。這樣,你就不用本身負責全部的編輯工做,並且這也能讓名錄裏的信息更加準確。
  • 若是你把電子郵件地址也存儲到數據庫裏,那麼你就能夠利用它們來給成員發送電子郵件,提醒他們及時更新本身的資料。郵件內容能夠顯示出成員的當前資料,請成員們進行檢查,並提示他們如何使用網站提供的功能完成必要的修改。
  • 數據庫還能夠在不少方面拓展聯盟網站的用途,而並不只限於成員資格列表。聯盟通信《Chronicles of U.S. Past》有一個兒童專欄,其中每一期都會包含一個歷史知識測驗。最近幾期的重點是美國總統的傳記。聯盟網站上也能夠設置一個兒童專區,把那些測驗題目放在上面。或許這個專區還能夠弄成互動的,好比將作過的測驗信息放入數據庫,讓Web服務器在數據庫裏查詢問題的答案,而後呈現給訪客。

好了!此時此刻,你所想到的數據庫應用數量可能讓你有些得意忘形了。在稍息片刻以後,從新回到現實,你開始問一些比較實際的問題,例如:

  • 是否是想太多了?實現起來工做量會很大吧?

固然,全部事情只想不作都會變得很容易,而這些想法對於具體實現根本不重要。不過,在讀完本書以後,你就能實現咱們剛纔羅列出來的全部需求。可是請記住,沒有必要一會兒完成全部需求。要將整個工做分解成若干需求,一次只解決一個需求。

  • MySQL能實現全部這些目標嗎?

不,它不能,至少單靠它是不行。例如,MySQL沒有內置Web程序開發工具。不過,你能夠將它與其餘工具相結合,藉助它們來補充和擴展其功能。

咱們將使用腳本語言Perl和它的數據庫接口(Database Interface,DBI)模塊來編寫訪問MySQL數據庫的腳本程序。Perl語言有着強大的文本處理能力,它能以極其靈活的方式對數據庫的查詢結果進行處理,併產生各式各樣的輸出。例如,咱們能夠用Perl語言生成一份富文本格式(Rich Text Format,RTF)格式的成員名錄,該格式可供各類文字處理軟件讀取;另外,還能夠生成適用於Web瀏覽器的HTML格式的名錄。

咱們還要用到另外一種腳本語言PHP。PHP語言特別適用於編寫Web應用程序,而且它也易與數據庫進行交互。有了它,你就可以經過Web頁面來啓動MySQL查詢,而後生成新的包含數據庫查詢結果的頁面。有不少支持PHP語言的Web服務器(其中包括世界上最流行的Web服務器Apache),所以像「呈現一個搜索表單,並顯示搜索結果」這樣的事情,對它來講就是「小菜一碟」。

MySQL與這些工具能夠很好地集成在一塊兒,所以你能夠自由地選擇組合方式,以便達到你心中設定的目標。別太相信那些一體化的套裝組件,它們一般都大肆鼓吹本身具備「集成」功能,可是它們只有彼此配合才能更好地發揮做用。

  • 最後,還有一個很重要的問題:總計要花費多少錢?畢竟聯盟的預算是有限的。

答案可能使人難以置信,但實際上它可能不會有任何成本。若是你對數據庫系統有所瞭解,那麼你應該知道它們一般都很昂貴。相比之下,MySQL一般是可無償使用的。即使是在須要有技術支持和維護承諾保證的企業環境裏,將MySQL做爲數據庫系統,其成本也是很低的。(想了解更多詳情,請訪問www.mysql.com。)咱們將用到的其餘工具(其中包括Perl、DBI、PHP和Apache)都是免費的。所以,綜合考慮一下,你徹底可以以至關低的成本組建一個有用的系統。

用於開發數據庫的操做系統則由你來選擇。幾乎全部咱們將討論的軟件都能在UNIX(它指代了BSD UNIX、Linux、Mac OS X等)和Windows上運行。極少數的例外狀況通常是UNIX或Windows特有的shell腳本或批處理腳本。

1.2.2 成績考評項目

如今一塊兒來看另外一個要使用示例數據庫的狀況。該項目的場景是:你是一名負責成績考評的教師。你想將成績處理工做,從使用成績冊的手工操做方式轉換成使用MySQL的電子表示方式。在這種狀況下,從數據庫獲取信息的方式隱含在你目前使用成績冊的方式中。

  • 對於每一次測驗或考試,你都要記錄分數。若是是考試,你還須要對分數進行排序,以便查看它們並肯定每個字母成績(包括A、B、C、D和F)所表明的界線。
  • 在期末,你須要把計算出每個學生的總分數,對這些總分數進行排序,並以此爲基礎肯定出成績。總分數可能須要加權計算,由於你可能須要讓考試比測驗的權重更大。
  • 在期末,你還要向學校辦公室提供學生的考勤狀況。

最終目標是要避免手動排序、避免手動彙總分數和考勤狀況。換句話說,你但願MySQL能夠對分數排序,並在期末完成全部與每一個學生的總分和缺席次數相關的計算。爲實現這些目標,你須要班裏學生的名單、每次考試和測驗的分數,以及所有學生的缺勤日期。

1.2.3 如何運用示例數據庫

若是你對這裏的「美史聯盟」和「成績考評」兩個項目都不感興趣,那麼你可能會想「還有什麼場景必定適合你呢」。答案就是「無窮無盡」。其實,這兩個項目能夠說明你用MySQL和與之相關的工具能夠作什麼事情。稍微想象一下,你便能看到示例數據庫查詢是如何應用於

你所要解決的那些特定問題的。假設你正好就在我前面提到的那個牙科診所裏工做。雖然在這本書裏不會看到不少與牙科學有關的查詢,可是會看到這裏所發現的不少查詢均可以應用到患者記錄維護、辦公記載等那些工做中。例如,「肯定聯盟裏哪些成員須要更新他們的成員資格」與「肯定最近哪些患者將來複診」這兩個任務便很是類似。它們都是基於日期的查詢,所以只要你學會了「成員資格更新」的查詢,那麼即可以將該技巧用於編寫「久未複診患者」的查詢,從而帶來更多的收穫。


1.3 基本數據庫術語

你可能已經注意到了,儘管這是一本關於數據庫的書籍,但到目前爲止,你還未遇到過多少晦澀難懂的專業技術術語。事實上,儘管咱們對如何使用示例數據庫有過大體描述,但關於數據庫究竟是什麼樣子,我卻隻字未提。但是,既然咱們要設計數據庫,並實現它,那麼咱們就不能再避而不談有關的術語了。這正是本節的主要內容。本節所描述的術語都是本書要用到的,所以但願你們可以熟悉它們。慶幸的是,關係數據庫的許多概念都很簡單。人們之因此喜歡關係數據庫,很大程度上即是由於其基本概念都簡明易懂。

1.3.1 結構術語

在數據庫領域,MySQL被劃分爲關係數據庫管理系統(RDBMS)。咱們下面來拆解一下。

  • 數據庫(DataBase,即RDBMS裏的DB)是一個用來存儲信息的倉庫,它的結構簡單、規則。

    • 數據庫裏的數據集都被組織成表(table)。

    • 每一個表由多個行(row)和列(column)組成。

    • 表中的每一行稱爲一條記錄(record)。

    • 記錄能夠包含多項信息;表裏的每一列對應於其中的一項。

  • 管理系統(Management System,即RDBMS裏的MS)是一個軟件,咱們能夠經過它來插入(insert)、檢索(retrieve)、修改(modify)或刪除(delete)記錄。

  • 關係(Relational,即RDBMS裏的R)一詞表示這是一種特殊的DBMS,其長處在於經過查找兩個表裏的共同元素,將分別存放於兩個表裏的信息聯繫(即匹配)起來。RDBMS的強大之處在於:它能方便地將這些表裏的數據提取出來,並把相關表裏的信息結合起來生成答案,回答那些只靠單個表沒法回答的問題。(事實上,「關係」的正式定義與我在本書中用它的方式有所不一樣。爲此,我先向那些純粹主義者道歉。不過,個人定義更有助於表達出RDBMS的用途。)

關係數據庫是如何把數據組織到表裏的呢?又是如何把不一樣表的信息關聯在一塊兒的呢?下面來看一個例子。假設你在經營一個網站,它有一項橫幅廣告服務。你與多家想要刊登廣告的公司簽定了合同,知足它們想要在人們訪問你網站頁面時顯示其廣告的須要。每當有訪客點擊其中的某個頁面時,你就提供一個嵌有廣告的頁面發送給訪客瀏覽器,這樣你就能從刊登這條廣告的公司那裏得到一點費用。這就是所謂的廣告「點擊」。爲了表示這些信息,你須要用到3個表(見圖1-1)。第1個company表由這樣幾列構成:公司名稱、編號、地址和電話號碼。第2個ad表的構成列有:廣告編號、擁有該廣告的那家公司的編號,以及每點擊一次的收費數目。第3個hit表`須要記錄廣告點擊量和廣告點擊日期。

圖1-1 橫幅廣告的表

有些問題只用一個表便能回答。例如,想要知道與你簽定了合同的公司有多少家,那麼你只需統計一下company表共有多少行便可。一樣地,想要了解在某段指定的時間裏有多少點擊量,則只需檢查表hit便可。其餘有些問題則可能比較複雜,須要查詢多個表才能獲得答案。例如,想要知道在7月14日那天Pickles公司(Pickles,Inc.)的每一條廣告分別被點擊了多少次,那麼就須要像下面那樣使用全部的3個表。

(1)在company表裏查找公司名稱(Pickles,Inc.),從而查出該公司的編號(14)。

(2)在ad表裏,利用這個公司編號找出與之匹配的行,從而能夠肯定出全部相關廣告的編號。最後找到兩條廣告,即編號48和101。

(3)對於在ad表裏匹配到的每一行,使用該行裏的廣告編號在hit表裏找出日期介於給定日期範圍內的全部匹配行;而後,再統計這些匹配行的數量。最後查詢出的結果是:48廣告有3個匹配;101廣告有2個匹配。

這些聽起來好像很複雜!但這正是關係數據庫系統最擅長作的事情。這種複雜性只是表面現象,由於剛纔描述的每一步驟都是一個簡單的匹配操做:將一個表的行值與另外一個表的行值,經過匹配關聯起來。這種簡單的操做能夠有多種變化,用於回答各式各樣的問題,如各家公司分別投放了多少個不一樣的廣告?哪家公司的廣告最受歡迎?每一個廣告帶來的收益是多少?在當前結算期內,各家公司應該支付你多少廣告費?

有了以前講述的這些關係數據庫理論,你就能讀懂本書後續的內容了;咱們沒必要了解「第三範式」(Third Normal Form)、「實體聯繫圖」(Entity-Relationship Diagram)等這些枯燥乏味的內容。(若是想了解它們,請閱讀C. J. Date或E. F. Codd的著做。)

1.3.2 查詢語言術語

爲了與MySQL交互,須要使用一種名爲SQL的語言。全部主流數據庫系統都支持SQL,但各個服務商的實現都各不相同。SQL支持許多不一樣的語句,可讓你用很是有趣和實用的方式與數據庫進行交互。

與其餘計算機語言同樣,初次接觸SQL的人每每會以爲它很奇怪。例如,在建立表時,你必須告訴MySQL該表的結構是什麼樣子的。不少人都會想到把表與圖表或圖片聯繫起來。可是MySQL不是這樣子的,所以在建立表時,你必須告知MySQL一些相似下面那樣的內容:

CREATE TABLE company ( company_name VARCHAR(30), company_num INT, address VARCHAR(30), phone VARCHAR(12) );

若是對SQL還不太熟悉,那麼可能會對這樣的語句心生畏懼。不過請放心,你不用成爲程序員就能學會如何熟練地使用SQL。隨着對SQL的深刻了解,你對CREATE TABLE的見解也會悄然地發生變化——它再也不是一組怪誕的表達,而是一種有助於信息描述的強大工具。

1.3.3 MySQL的體系結構術語

當使用MySQL的時候,你實際上至少會用到兩個程序,由於MySQL採用的是「客戶端/服務器」體系結構。有一個程序是MySQL服務器,即mysqld。它運行在存放數據庫的那臺機器上,主要負責監聽網絡上的客戶端請求,並根據這些請求去訪問數據庫內容,而後向客戶端提供它們所查詢的信息。另外一個程序是MySQL客戶端,它主要負責鏈接到數據庫服務器,併發起查詢,以便將本身想要的信息告知服務器。

MySQL的大部分發行版本都包括數據庫服務器程序和客戶端程序。(在Linux下使用RPM包時,會有一個單獨的服務器RPM包和客戶端RPM包,所以應該安裝兩種包。)請根據本身的實際狀況使用合適的客戶端程序。最經常使用的客戶端程序是mysql,它是一個交互式客戶端程序,你能夠用它來發起查詢並查看結果。另外還有兩個主要用於管理的客戶端程序,即mysqldump和mysqladmin。前者主要用來把表的內容導出到文件裏;後者主要用來檢查數據庫服務器的工做狀態,以及執行一些數據庫管理相關的任務,如通知服務器關閉。MySQL發行版本里還包括一些其餘的客戶端程序。此外,MySQL還提供了一個客戶端開發庫,若是它自帶的標準客戶端程序沒法知足你的應用需求,你能夠利用這個庫來編寫本身的程序。這個開發庫能夠被C語言程序直接使用。若是你不習慣C語言,那麼也能夠選擇適用於其餘語言(如Perl、PHP、Python、Java和Ruby)的編程接口。

本書討論的客戶端程序都是基於命令行的。你也能夠試試MySQL工做臺(MySQL Workbench),這是一個基於圖形用戶界面(Graphical User Interface,GUI)的工具,提供了即點即擊(point-
and-click)功能。關於此工具的更多信息,請訪問http://www.mysql.com/products/
tools/。

MySQL的「客戶端/服務器」體系結構有如下幾個好處。

  • 服務器強制執行併發控制,能夠防止兩個用戶同時修改同一條記錄。全部客戶端請求都要通過服務器,所以服務器會負責安排處理它們的前後順序。即便出現多個客戶端同時訪問同一個表的狀況,它們也不用先找到對方進行協商。它們只需把本身的請求發往服務器,而後由服務器來決定執行這些請求的順序。
  • 不是隻有在數據庫所在的那臺機器上才能登陸。MySQL工做在網絡環境裏,所以你能夠在任意地方運行MySQL客戶端程序,它都可以經過網絡鏈接到服務器。距離不是問題!你能夠在世界的任何地方訪問服務器。例如,你的服務器位於澳大利亞,那麼就算你帶着筆記本電腦旅行到了冰島,你也仍然能夠訪問本身的數據庫。這是否意味着任何人都可以經過互聯網看到你的數據呢?答案是「不能」。MySQL有一套靈活的安全機制,你能夠設定只有獲得受權的人才能訪問。此外,你還能夠進一步限制這些人的操做。例如,財務部的Sally應該有查看和更新(修改)記錄的權限;而服務檯的Phil卻只應該有查看記錄的權限。總之,你能夠把這種訪問權限控制細化到每個人。若是你只想運行一個自屬的系統,那麼你徹底能夠把訪問權限設置爲只容許服務器上的客戶端程序進行鏈接。

MySQL與mysql之間的差別

爲避免混淆,咱們使用MySQL指代完整的MySQL RDBMS,而mysql指代特定客戶端程序的名字。雖然它們的發音相同,但咱們經過字母大小寫和字體差別對它們進行了區分。

說到發音,MySQL被讀做「my-ess-queue-ell」。具體細節能夠在MySQL參考手冊(MySQL Reference Manual)裏查閱到。此外,SQL的讀法有「sequel」和「ess-queue-ell」兩種,具體是哪一種由讀它的人決定。本書假定SQL的發音爲「ess-queue-ell」。


1.4 MySQL教程

如今你已具有了全部的預備知識。下面來實際操做一下MySQL。

本節提供了一個帶有多個示例的教程,其目的在於幫助你熟悉MySQL。主要內容包括建立一個示例數據庫和多個表,經過對這些表裏的信息執行插入、檢索、刪除和修改操做,練習與示例數據庫的交互。在此過程當中,你將學會如下內容。

  • 瞭解MySQL所能理解的SQL基礎知識。(MySQL與其餘RDBMS所使用的SQL有所不一樣,所以你最好也能快速瀏覽一下本節的內容,從而確認一下MySQL的SQL實現與你熟悉的版本是否存在差別。)
  • 瞭解MySQL自帶的標準客戶端程序是如何與MySQL服務器進行通訊的。前一節講過,MySQL採用的是「客戶端/服務器」體系結構。其中,服務器運行在數據庫所在的機器上;而客戶端則是經過網絡鏈接到服務器。本教程主要依賴於客戶端程序mysql,它首先讀取你輸入的SQL查詢語句,把它們發送到服務器執行,而後把執行結果顯示在你面前。客戶端mysql能夠在MySQL所支持的全部平臺上運行,而且提供了與服務器進行交互的最直接的方式。不過根據須要,有些示例會使用mysqlimport或者mysqlshow來代替。

本書將示例數據庫命名爲sampdb。若是在你的系統上已經有人佔用了這個名字,或者你的MySQL管理員爲你分配了另外一個名字,那麼你須要換一個名字。無論是何種狀況,你都須要把本書示例中的sampdb替換爲你實際使用的數據庫名稱。

即便你的系統裏有多個用戶,且他們都各自擁有本身的示例數據庫,本節全部示例裏的表名也均可以直接使用。在MySQL裏,只要每一個人都用本身的數據庫,那麼你們徹底能夠使用相同的表名,不會有任何問題。MySQL將表限制在各自的數據庫裏,防止了相互干擾。

1.4.1 獲取示例數據庫發行包

本教程在好幾個地方都要用到「示例數據庫發行包」( 也稱做「sampdb數據庫的發行版」)。該發行包裏包含了許多用來安裝示例數據庫的查詢語句和數據。發行包的獲取辦法和安裝步驟能夠在附錄A裏查到。在發行包解壓以後,它會自動建立一個名爲sampdb的子目錄,其中包含了你所須要的許多文件。順便提個建議:每次你操做數據庫裏的示例時,最好都能切換到一個新的目錄。

若是想要在任何目錄裏均可以方便地運行MySQL程序,那麼你最好能把位於MySQL目錄下包含着這些程序的bin目錄,添加到命令解釋器的搜索路徑裏。具體操做方法是,參考本書A.3.3節,把該目錄的路徑名稱添加到環境變量PATH裏

1.4.2 基本配置要求

爲練習本教程裏的各個示例,除得到示例數據庫發行包外,還必須知足如下幾項基本要求。

  • 必須安裝MySQL軟件。
  • 要有一個可以鏈接數據庫服務器的MySQL帳號。
  • 要有一個用來操做的數據庫。

所需的軟件包括MySQL客戶端和MySQL服務器。客戶端程序必須安裝在你本身要用的機器裏。服務器能夠選擇安裝在你的機器上,這點不強求。只要你有權限鏈接它,那麼服務器能夠位於任何地方。若是你的因特網服務提供商(Internet Service Provider,ISP)提供了MySQL服務,那麼也能夠申請使用它。想要本身得到和安裝MySQL,請參考附錄A。

除了MySQL軟件,還必需要有一個MySQL帳戶,這樣才能鏈接到服務器,並建立示例數據庫及其表。(若是你已有MySQL帳戶,則能夠直接用它。不過,建議你另外創建一個專用於本書學習的帳戶。)

此時此刻,咱們遇到了一個「是先有雞,仍是先有蛋」的難題:爲了創建一個用於鏈接服務器的MySQL帳戶,你必需要先鏈接到該服務器。一般狀況下,你須要在運行MySQL服務器的主機上,以root用戶身份登陸,而後執行CREATE USER語句GRANT語句來建立新的MySQL帳戶,併爲其分配數據庫權限。若是你是在本身的機器上安裝了MySQL服務器,而且正在運行,那麼你能夠像下面那樣用root身份鏈接服務器,而後建立一個新的示例數據庫管理員帳戶,其用戶名爲sampadm,密碼爲secret。你也能夠把其中的名字和密碼更改爲本身想用的,但在本書後面用到它們的地方也要作相應的修改。

% **mysql -p -u root **Enter password: ****** mysql> **CREATE USER 'sampadm'@'localhost' IDENTIFIED BY 'secret'; **Query OK, 0 rows affected (0.04 sec) mysql> **GRANT ALL ON sampdb.* TO 'sampadm'@'localhost'; **Query OK, 0 rows affected (0.01 sec)

命令mysql有一個選項-p,它能讓mysql提示輸入root用戶的MySQL密碼。如上例所示,輸入的密碼會被顯示爲一串星號,即**。這裏假設你已經爲MySQL的root用戶設置了密碼。若是你還未設置,則請在Enter Password:提示出現後直接按回車(Enter)鍵。不過,若是root用戶無密碼,那麼這是個很大的安全漏洞,你應該儘快給它設置一個。想要了解更多關於CREATE USER語句、GRANT語句、設置MySQL用戶帳戶以及修改密碼的信息,請參考第13章。

在建立sampadm帳戶以後,請輸入quit,並按回車鍵,而後退出mysql``程序``。

剛纔展現那些語句適合於在運行服務器的那臺機器上鍊接MySQL。它們讓你能夠經過用戶名sampadm和密碼secret來鏈接服務器,並讓你擁有徹底訪問sampdb數據庫的權限。不過,GRANT語句並不會建立數據庫(你能夠在建立數據庫以前爲它分配權限)。咱們稍後會討論與數據庫建立相關的操做。

若是你打算經過網絡從另外一臺主機鏈接到MySQL服務器,那麼須要把示例中的localhost更改成你正使用的那臺主機的名字。例如,你想要從主機boa.example.com鏈接到MySQL服務器,那麼相應的語句則應相似這樣:

mysql> **CREATE USER 'sampadm'@'boa.example.com' IDENTIFIED BY 'secret'; **mysql> **GRANT ALL ON sampdb.* TO 'sampadm'@'boa.example.com';**

若是你對服務器沒有控制權限,沒法建立用戶,那麼請求助MySQL管理員,讓他爲你創建一個帳戶。而後,將本書各示例裏的samp admsecretsampdb分別替換爲管理員分配給你的用戶名、密碼和數據庫名。

1.4.3 創建和斷開MySQL服務器鏈接

爲鏈接到MySQL服務器,請在命令提示符(即Unix系統的shell提示符,或者Windows下的控制檯窗口提示符)裏調用mysql程序。命令以下:

% **mysql**options

本書使用%來表示命令提示符。它是Unix系統的其中一個標準提示符;另外一個是$。在Windows系統裏,你所看到的提示符有點像C:\&gt;。當輸入這些示例裏所示的命令時,請不要輸入提示符自己。

mysql命令行裏的options部分能夠爲空。但下面這種命令形式更爲常見:

% **mysql -h**host_name**-p -u**user_name

在執行mysql時,不用提供全部的選項,但一般至少要指定用戶名和密碼。下面是所有選項的具體含義。

  • -h host_name(另外一種形式是:--host=host_name

指定運行MySQL服務器的那臺主機。若是它與你運行mysql程序的那臺機器相同,就能夠省略此選項。

  • -u user_name(另外一種形式是:--user=user_name

指定MySQL用戶名。若是你使用的是Unix系統,而且你的MySQL用戶名與登陸名徹底同樣,就能夠省略此選項。mysql將自動將你的登陸名看成MySQL用戶名。

在Windows系統上,默認用戶名爲ODBC,它有可能沒法使用。你能夠在命令行經過-u選項來指定,也能夠經過設置環境變量USER來添加一個默認用戶名。例如,你能夠用下面的set命令來指定一個用戶名sampadm

C:\>** set USER=sampadm**

若是你經過控制面板(Control Panel)裏的系統(System)項目設置環境變量USER,那麼該設置對每個控制檯窗口都會起做用,你就沒必要在命令提示符裏執行這條命令了。

  • -p(另外一種形式是:--password

此選項會讓mysql顯示Enter password: 提示符,並要求你輸入MySQL密碼。例如:

% **mysql -h **host_name**-p -u**user_name Enter password:

當你看到Enter password: 提示符時,請輸入你的密碼。(輸入的密碼不會顯示到屏幕上,以避免被人偷看到。)請注意:MySQL密碼並不必定與登陸Unix或Windows系統的密碼相同。

若是你省略選項-p,那麼mysql將認爲你不須要密碼,所以不會提示你輸入它。

在命令行上直接提供密碼的另外一種方式是輸入-pyour_pass選項(另外一種形式是:--password=your_pass,其中的your_pass即爲你的密碼)。不過,出於安全的考慮,最好別這樣作。由於在輸入時,你身邊的其餘人能看到屏幕上的密碼。另外,在Unix系統裏,其餘用戶可能也可以使用系統工具查看到命令行。

若是你確實想要在命令行直接輸入密碼,那麼請注意:在-p選項和後面的密碼值之間沒有空格。-p選項的這種行爲常常會形成混亂,由於它與選項-h-u的習慣要求有所不一樣:它們都是與跟在後面的單詞相關聯的,無論其間是否有空格。

假設MySQL的用戶名和密碼分別是sampadmsecret。若是運行MySQL服務器的那臺主機與你運行mysql程序的主機相同,那麼你能夠省略選項-h``, 此時mysql命令會像下面那樣鏈接服務器:

% **mysql -p -u sampadm **Enter password: ******

在輸入完這條命令以後,mysql會顯示Enter password:``,提示你輸入密碼,而後你即可以輸入它(輸入的secret會在屏幕上顯示爲6個星號******)。

若是一切順利,mysql將會顯示出一條歡迎消息和一個mysql&gt;提示符,此時代表它在等你發起查詢命令。完整的啓動過程近似以下:

%** mysql -p -u sampadm **Enter password: ****** Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 13762 Server version: 5.5.30-log Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>

若是想要鏈接運行於另外一臺機器上的MySQL服務器,那麼必須使用-h選項來指定其主機名。假設該主機名爲cobra.example.com,那麼相應的命令以下所示:

% **mysql -h cobra.example.com -p -u sampadm**

爲簡潔起見,在後面大部分顯示mysql命令行的示例裏,選項-h-u-p都會被省略,而且假定你會提供全部必要的選項。在運行其餘MySQL程序(如mysqlshow)時,你也會用到這些選項。

在鏈接到MySQL服務器以後,你能夠隨時輸入quit命令來終止會話:

mysql> **quit** Bye

也能夠輸入exit\q來退出。在Unix系統裏,能夠按組合鍵Ctrl+D來退出。

在剛開始學習MySQL時,不少人均可能會對它的安全系統感到煩惱,由於它很難操做。(必需要有建立和訪問數據庫的權限;還有,無論什麼時候鏈接服務器,都必須指定正確的用戶名和密碼。)不過,在練習完本書提供的示例數據庫,並開始輸入和使用本身的數據記錄以後,你的見解便會迅速發生轉變。到那時,你就會欣賞MySQL的這種方式,由於它能夠防止他人窺視(或者更爲糟糕的狀況,破壞)你的信息。

如何設置工做環境,才能不用在每次運行mysql時都須要在命令行指定鏈接參數?關於這個問題請參考1.5節。簡化服務器鏈接過程的最多見辦法是,將鏈接參數存放到一個選項文件裏。若是想如今就瞭解如何創建這樣的文件,那麼請直接跳轉到1.5節。

1.4.4 執行SQL語句

在鏈接上服務器之後,你即可以發起SQL語句,讓服務器執行它。本節將介紹一些與mysql``進行交互的通常原則。

想要在mysql裏輸入語句,只須要直接輸入便可。在語句的結尾,請輸入一個分號(;),並按下回車鍵。該分號會告知mysql——語句到此結束。在輸入完一條語句以後,mysql會將它發送到服務器執行。服務器接着處理它,並把結果發送回mysql,而後mysql會顯示結果。

下面這個示例展現了一條用於查詢當前日期和時間的簡單語句:

mysql> **SELECT NOW();** +---------------------+ | NOW() | +---------------------+ | 2013-01-08 17:42:33 | +---------------------+ 1 row in set (0.00 sec)

除使用分號外,還有另外一種終止語句的方法,即便用\g(意思是go):

mysql> **SELECT NOW()\g **+---------------------+ | NOW() | +---------------------+ | 2013-01-08 17:42:40 | +---------------------+ 1 row in set (0.00 sec)

也能夠使用\G。它會以「垂直」方式顯示結果,每行顯示一個值:

mysql> **SELECT NOW(), USER(), VERSION()\G ***************************** 1. row *************************** NOW(): 2013-01-08 17:54:24 USER(): sampadm@localhost VERSION(): 5.5.30-log 1 row in set (0.00 sec)

若是語句的輸出行比較短,那麼\G就沒什麼用處;但若是輸出行很長,並且會在屏幕上回繞顯示,那麼使用\G便能讓輸出內容更易於閱讀。

如上所示,mysql會顯示語句結果和一行統計信息,其中包括該結果所包含的行數,以及語句執行所花費的時間。在後面的示例裏,通常狀況下將再也不顯示統計信息行。

由於mysql會等待語句結束符,因此你不用在單獨一行裏輸入一條語句的所有內容。你可根據須要,將它分隔成多行進行輸入:

mysql> **SELECT NOW(),******** ** -> **USER(), ** -> **VERSION() ** -> **; **+---------------------+-------------------+------------+ | NOW() | USER() | VERSION() | +---------------------+-------------------+------------+ | 2013-01-08 17:54:56 | sampadm@localhost | 5.5.30-log | +---------------------+-------------------+------------+

請注意,在輸入該語句的第一行時,提示符從mysql&gt;變爲了-&gt;。這是在提醒你:mysql認爲你還要繼續輸入語句內容。這是個很重要的反饋。若是你忘了在語句末尾加上分號,那麼這個變化的提示符會提醒你注意:mysql仍在耐心等待你繼續輸入語句內容。不然,你會很不耐煩地等在一邊,內心疑惑:爲什麼MySQL執行你的語句要花這麼長的時間。命令mysql還有另外幾種提示符,附錄F對它們進行了詳細介紹。

若是你已經輸入了一條多行語句,但忽然決定不執行它了,那麼能夠輸入\c來清除(即取消)它:

mysql>** SELECT NOW(), ** ->** VERSION(), ** ->** \c **mysql>

請注意,提示符是如何變回mysql&gt;的。這種變化代表mysql已準備接收一條新的語句。

與將一條語句分紅多行輸入相反的操做是,在單獨一行裏輸入多條語句,兩條語句中間用終止符隔開:

mysql> **SELECT NOW();SELECT USER();SELECT VERSION(); **+---------------------+ | NOW() | +---------------------+ | 2013-01-08 17:55:20 | +---------------------+ +-------------------+ | USER() | +-------------------+ | sampadm@localhost | +-------------------+ +------------+ | VERSION() | +------------+ | 5.5.30-log | +------------+

大多數狀況下,輸入語句使用大寫、小寫或者大小寫混用均可以。例如,下面幾條語句檢索到的是相同的信息(雖然顯示結果裏列標題的大小寫有所不一樣):

SELECT USER(); select user(); SeLeCt UsEr();

本書的全部示例都將用大寫字母來表示SQL關鍵字和函數名,用小寫字母來表示數據庫、表和列的名字。

當在語句裏調用函數時,請不要在函數名和後面的括號之間加入空格。若是有空格,則會致使語法錯誤。

將多條語句存儲在一個文件裏能夠建立一個SQL腳本,而後讓mysql從該文件(而不從鍵盤)讀取語句。請使用shell的輸入重定向功能來實現這種操做。例如,假設語句都存儲在一個名爲myfile.sql的文件裏,那麼咱們能夠使用下面這樣的命令來執行其中的語句(請記得指定全部必需的鏈接參數選項):

% **mysql < myscript.sql**

你能夠爲該文件隨便取一個名。我給它加上了一個「.sql」後綴,以代表裏面存放的是SQL語句。

像這種調用mysql來執行文件裏的語句的作法,會在1.4.7節再次用到,到時,咱們會用這種辦法來往sampdb數據庫裏輸入數據。與逐條手工輸入相比,讓mysql從某個文件裏讀取多條INSERT語句來加載表要方便不少。

本教程的其他部分展現了不少SQL語句,你能夠自行練習。在這些語句前面都有提示符mysql&gt;做爲標誌,而且這些示例都提供了語句輸出結果。若是你輸入與示例顯示同樣的語句,那獲得的輸出結果也應該是相同的。前面未加提示符的語句主要是用來作說明用的,你不須要執行它們。固然,若是願意,執行一下也無妨。提醒一下,請記得在每條語句的末尾加上一個分號做爲結束符。

1.4.5 建立數據庫

數據庫的使用涉及如下幾個步驟。

(1)建立一個數據庫。

(2)在該數據庫裏建立多個表。

(3)對錶裏的數據執行插入、檢索、修改或刪除操做。

想要建立新的數據庫,請先使用mysql鏈接到服務器。而後執行CREATE DATABASE語句,並指定新的數據庫名字:

mysql> **CREATE DATABASE sampdb;**

在建立可進入或對其內容進行操做的表以前,必須先建立sampdb數據庫。

你可能會但願在建立某個數據庫的同時,讓它成爲默認(或當前)數據庫。但這是行不通的。看看下面這條檢查默認數據庫的語句,你就會明白這一點:

mysql> **SELECT DATABASE(); **+------------+ | DATABASE() | +------------+ | NULL | +------------+

NULL表示「未選擇到數據庫」。若是想要把sampdb設置爲默認選擇數據庫,那麼還須要執行一條USE語句:

mysql> **USE sampdb; **mysql> **SELECT DATABASE(); **+------------+ | DATABASE() | +------------+ | sampdb | +------------+

另外一種選擇默認數據庫的辦法是,調用mysql的時侯在命令行上給該數據庫取一個名字:

% **mysql sampdb**

事實上,這就是在選擇數據庫時用得最多的辦法。若是還須要使用鏈接參數,那麼能夠在命令行指定它們。例如,下面的命令可讓用戶sampadm鏈接到本地主機(若是未指定主機名字,則默認是它)上的sampdb數據庫:

%** mysql -p -u sampadm sampdb**

若是須要鏈接到運行於遠程主機上的MySQL服務器,那麼須要在命令行指定該主機:

% **mysql -h cobra.example.com -p -u sampadm sampdb**

若是無特別說明,後面全部的示例都將假定:當調用mysql時,命令行裏指定的默認數據庫就是sampdb。若是在調用mysql時忘記在命令行指定數據庫,那麼請在mysql&gt;提示符處輸入一條USE sampdb語句。

1.4.6 建立表

本節將建立示例數據庫sampdb所須要的那些表。首先,建立「美史聯盟」場景所須要的表,而後,再爲「成績考評」項目建立所需的表。有些數據庫書講到在此處便會開始討論「數據庫的分析與設計」、「實體聯繫圖」、「規範化過程」(Normalization Procedure)等內容。有不少書專門講解這些內容,因此本書在這裏只想說明咱們的數據庫應該是個什麼樣子,具體來說涉及這樣幾個方面:它應該包含哪些表、每一個表都應包含什麼內容,以及在決定如何表示這些數據時須要考慮哪些問題。

這裏所選擇的表示方式並非絕對的。在其餘場合,你能夠選用不一樣的方式來表示類似的數據。到底選擇哪種,須要由應用程序和數據的具體用途來決定。

1.4.6.1 美史聯盟表

美史聯盟的表至關簡單,包括如下兩個表。

  • president表。其中包含美國曆任總統的描述性記錄。咱們須要用它來實現聯盟網站上的在線小測驗(對聯盟通信兒童專欄裏出現的小測驗進行交互式模擬)。
  • member表。用於保存聯盟每位成員的最新我的資料。咱們能夠用它來建立成員名錄的印刷版本和在線版本,用它來向到期成員自動發送提醒通知,還能夠用它作不少其餘事情。
1. president

president表包含一些與美國曆任總統平生相關的基本信息。

  • 姓名。在表裏,表示姓名的方式有好幾種,如使用單列包含整個名字,或用不一樣的列分別表示姓(last name)和名(first name)。使用單列來表示固然更簡單一些,但這種作法不夠靈活,存在一些限制。

若是優先輸入名,就沒法按姓排序。

若是優先輸入姓,就沒法按名在前、姓在後的順序顯示它們。

難以查找姓名。例如,想要查找某人的姓,那麼必須先使用一種模式,而後去查找與之匹配的姓名。與直接查找姓的作法相比,這種作法的效率很低,速度很慢。

爲避免出現這些限制, president表將使用兩列分別表示姓和名。

那個「名」列還要存放中間名或其縮寫。這種作法不會影響到之後的姓名排序操做,由於咱們不太可能對中間名進行排序(也不太可能對名進行排序)。另外,姓名也能夠正常顯示,由於不管是以「Bush, George W.」或「George W. Bush」中的哪種格式來輸出,姓名裏的中間名老是排在名字的後面。

還有一點須要注意。若是一位總統(如Jimmy Carter)的姓名後面帶有一個「Jr.」,那麼這個「Jr.」應該放在哪裏呢?根據英文姓名輸出格式習慣,這位總統的名字能夠顯示爲「James E. Carter, Jr.」,或者「Carter, James E., Jr.」。這裏的「Jr.」與名和姓沒有聯繫,所以咱們還須要另外建立一個列,用它來保存姓名後綴。這種狀況代表:當你在選擇數據的表示形式時,即便只是一個單值,也可能會引起某些問題。它同時還代表:在把數據值放入數據庫以前,最好儘量多地對數據值進行深刻的瞭解。若是對數據值的真正含義瞭解不夠,那麼在表啓用以後,可能還要被迫更改它的結構。雖然這種事不能算做是一場災難,但總的說來仍是儘可能避免爲妙。

  • 出生地(城市和州)。與姓名的狀況相似,它既能夠用一列來表示,也能夠用多列表示。採用單列來表示的作法顯得更簡單些;但與姓名的狀況同樣,使用多列能實現某些更復雜的操做。例如,若把州名與市名分開表示,那麼像「找出出生在某個州的總統共有多少位」這種相似的操做便能輕易地實現。咱們將使用兩個單獨的列來分別存放州名與市名。
  • 出生日期和逝世日期。這裏惟一須要特殊處理的事情是:由於有些總統依然健在,因此咱們不能要求必須填上逝世日期。特殊值NULL的意思即表示「無值」,所以咱們能夠在逝世日期列裏用它來表示該位總統「依然健在」。
2. member

從每條記錄都保存着單獨某我的的我的資料這一角度來看,存放「美史聯盟」成員列表的member表與剛纔介紹的president表很類似。只是member表的每一行還包含了如下這些列。

  • 姓名。咱們將沿用與president表相同的3列表示法:姓、名和姓名後綴。
  • ID編號。這是一個惟一值,爲每一個首次加入的成員分配一個。聯盟此前從未對成員編過號,但如今須要全部的記錄都更系統化,所以這裏須要這個值。(但願你能不斷髮現MySQL的好處,並找到更多將編號應用於聯盟記錄的方式。當想要將member表裏的行,與你所建立的成員相關的其餘表創建關聯時,使用編號則會比使用姓名更容易實現。)
  • 有效期。全部成員必須按期更新其成員資格,以免過時。對於某些應用程序,可能還須要把最近一次資格更新後的起始日期存儲起來,但「美史聯盟」不須要這樣作。成員資格的有效期是一個變值(一般有1年、2年、3年或者5年之分),而最近一次的資格更新日期也並不能說明該成員下一次的資格更新日期必定是在何時。所以,咱們須要把成員資格的截止日期存儲起來。此外,聯盟還提供了終身成員資格。雖然咱們能夠用一個很遙遠的日期來表示這種狀況,但使用NULL會更合適,由於「無值」在邏輯上正好對應於「永不失效」。
  • 電子郵件地址。公開電子郵件地址能夠使興趣相投的成員交流起來更方便。做爲聯盟的祕書,在有了這些地址以後,就能夠用電子郵件來向成員發送成員資格更新通知,而不用郵寄信件了。與到郵局寄信相比,這種作法既方便又省錢。你還能夠利用電子郵件把每位成員的我的最新資料發送給他們,讓他們在必要時更新信息。
  • 通訊地址。當與那些沒有電子郵件(或者長期沒有回覆你郵件)的成員進行聯繫時,你會須要這條信息。咱們將使用多個列來分別存儲街道地址、城市名、州名和郵政編碼。

咱們假設全部聯盟成員都居住在美國。固然,對於那些成員遍及於世界各地的組織機構來講,這個假設可能顯得有些簡單。若是想要處理涉及多個國家的地址,那麼你將遭遇到一些棘手的問題,必需要解決好不一樣國家所使用的地址格式不一樣的問題。例如,郵政編碼就不是一項國際標準;另外,有不少國家都只有「省」的概念,而無「州」的說法。

  • 電話號碼。與地址列類似,主要用於聯繫成員。
  • 特殊興趣關鍵字。聯盟的每位成員對美國曆史確定都很感興趣,但他們的興趣卻可能集中於某些特定的歷史時期。此列即是用於記錄這些興趣。每位成員均可以利用這些信息來尋找與本身興趣相投的其餘成員。(嚴格來說,創建一個獨立的表可能會更好些,表中的每一行都由一個關鍵字和相關成員的ID組成。這點有些複雜,咱們暫不做處理。)
3. 建立美史聯盟表

如今,咱們準備建立美史聯盟表。爲此,咱們須要使用CREATE TABLE語句,該語句具備如下格式:

CREATE TABLE tbl_name (column_specs);

其中,tbl_name指的是你要給表指定的名字;column_specs指的是該表的各列。該語句還會包括各類索引的定義,若是有的話。索引可以加快查找速度,關於這一點請參考第5章的介紹。

下面是針對president表的CREATE TABLE語句:

CREATE TABLE president ( last_name VARCHAR(15) NOT NULL, first_name VARCHAR(15) NOT NULL, suffix VARCHAR(5) NULL, city VARCHAR(20) NOT NULL, state VARCHAR(2) NOT NULL, birth DATE NOT NULL, death DATE NULL );

執行此語句的方法有兩種:能夠本身手動輸入;也能夠使用sampdb發行包中create_president.sql文件裏包含的預先編寫好的語句。

若是想要自已輸入這條語句,那麼請先調用mysql,同時將sampdb設置爲默認數據庫:

%** mysql sampdb**

接着,輸入上面的CREATE TABLE語句。請記得在語句末尾輸入分號,以便將這條語句的結束位置告知mysql程序。沒有縮進格式也沒有關係,你不用像上面那樣換行。你徹底能夠在同一行輸入整條語句。

若是想用預先編寫好的描述來建立president表,那麼能夠使用sampdb發行包裏的create_president.sql文件。當你解壓發行包時,解壓程序會自動建立一個sampdb目錄,並將那個文件放在這個目錄裏。先進入該目錄,而後執行下面這條命令:

% **mysql sampdb < create_president.sql**

不管你採用何種方式調用mysql,都請記得在命令行的命令名後面指定你可能須要的鏈接參數(其中包括主機名、用戶名和密碼)。

如今讓咱們來仔細看一下CREATE TABLE語句。該語句會爲每一列指定列名、數據類型(列要存放的值的類型),以及可能有的某些列屬性。

president表用到了兩種數據類型:VARCHARDATEVARCHAR(n)表示的是:該列能夠存放長度可變的字符型值,且最大長度爲n個字符。也就是說,它們包含了長度不定的字符串,但其長度存在上限。那個n的值即代表了你所指望的數據長度。例如,將state定義爲VARCHAR(2)類型,即表示咱們須要以兩個字母的縮寫形式輸入州名。而其餘值爲字符串類型列,其數據長度必需要再長一點才能容納下更長的值。

咱們用到的另外一個數據類型是DATE。很顯然,此類型表示該列用於保存日期值。不過,日期的表示格式可能會讓你感到意外。MySQL要求將日期表示爲'CCYY-MM-DD'格式,其中的CCYYMMDD分別表明了世紀、世界的年份、月和日。這也是SQL標準規定的日期表示格式(也叫作ISO 8601格式)。例如,想要在MySQL裏指定「2013年7月18日」這樣一個日期,則須要使用'2013-07-18'而不能用'07-18-2013''18-07-2013'`。

president表裏,咱們用到的列屬性只有NULL(能夠沒有值)和NOT NULL(必需要有值)。大部分數據列的屬性都爲NOT NULL,由於咱們要求它們必需要有值。能夠有NULL屬性的兩列是:suffix(大部分總統的姓名都無後綴)和death(若是總統還健在,則無逝世日期)。

對於member表,其CREATE TABLE語句以下:

CREATE TABLE member ( member_id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (member_id), last_name VARCHAR(20) NOT NULL, first_name VARCHAR(20) NOT NULL, suffix VARCHAR(5) NULL, expiration DATE NULL, email VARCHAR(100) NULL, street VARCHAR(50) NULL, city VARCHAR(50) NULL, state VARCHAR(2) NULL, zip VARCHAR(10) NULL, phone VARCHAR(20) NULL, interests VARCHAR(255) NULL );

與以前同樣,你能夠手動將這些語句輸入mysql中,也能夠利用預先編寫好的文件。發行包sampdb中的creat_member.sql文件包含了member表的CREATE TABLE語句。要使用它,須要執行如下命令:

% **mysql sampdb < create_member.sql**

就數據類型而言,member表裏的大部分列(有兩個例外)的類型都很普通,都是可變長度的字符串類型。列member_idexpiration是例外,它們分別保存的是序號和日期。

使用成員資格編號列member_id的主要考慮是:爲了不成員之間產生混淆,其中每個值都應該是惟一的。此時,AUTO_INCREMENT列正好能夠派上用場,由於當咱們添加新成員時,MySQL便能爲咱們自動生成一個惟一編號。雖然member_id列只是包含數字,但其定義卻包含有好幾個部分。

  • INT。它表示該列用於存放整數(無小數部分的數值)。
  • UNSIGNED。它表示該值不能爲負數。
  • NOT NULL。它表示該列必需要填值,這能夠防止建立的成員沒有ID號。
  • AUTO_INCREMENT。它是MySQL的一個特殊屬性,表示該列存放的是序號。AUTO_INCREMENT的工做原理爲:當往member表裏添加新記錄時,若是沒有爲member_id列提供值,那麼MySQL將自動生成下一個編號,並將它賦給該列。若是你顯式地將NULL賦給該列,結果也是同樣的。AUTO_INCREMENT的這種特性使得爲每一位成員分配一個惟一的ID變得很簡單,由於MySQL會替咱們生成這些值。

PRIMARY KEY子句表示爲member_id列建立索引,這樣能加快查找速度。同時,它還設置了約束:要求該列裏的全部值都必須惟一。後面這個屬性正好符合對成員ID值的要求,由於它能夠防止咱們兩次誤用相同的ID。此外,MySQL要求AUTO_INCREMENT列必須有某種索引,若是沒有索引,那麼表的定義就是不合法的。(任何一個PRIMARY KEY列也必須是NOT NULL的,所以就算咱們在member_id定義裏省略了NOT NULL,MySQL也會自動加上。)

若是你還不太明白AUTO_INCREMENTPRIMARY KEY是怎麼回事,那麼能夠把它們想象成一種能生成索引ID號的神奇魔法。這些值究竟是什麼並不重要,只要這些ID號對每位成員來講都是惟一的就能夠了。(關於AUTO_INCREMENT列的更多信息請參考第3章。)

expiration列的數據類型是DATE。它容許值爲NULL,且默認也爲NULL,即表示能夠不輸入日期。正如前面所提到的,咱們將使用這樣一個約定:當expiration``值NULL時,代表該成員擁有終身成員資格。

到目前爲止,你已經讓MySQL建立了兩個表,如今讓咱們來確認一下結果是否正確。在mysql裏,調用下面這條命令能夠查看president表的結構:

mysql> **DESCRIBE president; **+------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +------------+-------------+------+-----+---------+-------+ | last_name | varchar(15) | NO | | NULL | | | first_name | varchar(15) | NO | | NULL | | | suffix | varchar(5) | YES | | NULL | | | city | varchar(20) | NO | | NULL | | | state | varchar(2) | NO | | NULL | | | birth | date | NO | | NULL | | | death | date | YES | | NULL | | +------------+-------------+------+-----+---------+-------+

調用DESCRIBE member語句,則能夠看到有關member表的相似信息。(若是你想知道爲何未定義爲容許NULLDefault列會顯示NULL,那麼我在這裏告訴你,那是由於NULL也可用於代表該列沒有顯式的DEFAULT子句。)

若是你不記得一個表中的列名、想知道其數據類型,或者想了解其寬度是多少,那麼能夠使用DESCRIBE。你還能夠利用它來查看MySQL在錶行裏存儲各列的前後順序。當你執行INSERTLOAD DATA語句時,這個順序很重要,由於這些語句要求各列的值是以它們的默認列順序列出的。

你也能夠經過其餘方式來得到DESCRIBE生成的信息。既能夠是簡寫的DESC,也能夠是EXPLAIN``語句SHOW語句。下面這些語句具備相同的做用:

DESCRIBE president; DESC president; EXPLAIN president; SHOW COLUMNS FROM president; SHOW FIELDS FROM president;

這些語句還容許對輸出列加以限制。例如,能夠在SHOW語句的末尾加上一個LIKE子句,這樣便只能看到與給定模式相匹配的那些列的信息:

mysql> **SHOW COLUMNS FROM president LIKE '%name'; **+------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +------------+-------------+------+-----+---------+-------+ | last_name | varchar(15) | NO | | | | | first_name | varchar(15) | NO | | | | +------------+-------------+------+-----+---------+-------+

使用``DESCRIBE president`` '%name'也能夠獲得一樣的結果。這裏用到的百分號(%)是一個特殊的通配符,咱們將在後面的1.4.9 .7節中介紹它。

SHOW FULL COLUMNSSHOW COLUMNS很像,不一樣之處在於它會顯示附加的列信息。你能夠如今試一下,看看結果。

SHOW語句還有其餘幾種形式,可用於從MySQL得到不一樣類型的信息。SHOW TABLES語句會列出默認數據庫裏的表。到目前爲止,咱們已在數據庫sampdb裏建立了兩個表,所以執行該語句將獲得以下輸出:

mysql> **SHOW TABLES; **+------------------+ | Tables_in_sampdb | +------------------+ | member | | president | +------------------+

SHOW DATABASES語句會列出當前鏈接到的服務器上的全部數據庫:

mysql> **SHOW DATABASES; **+--------------------+ | Database | +--------------------+ | information_schema | | mysql | | sampdb | | test | +--------------------+

這個列表可能會因服務器不一樣而有所差別,可是應該能看到information_schemasampdb。其中,information_schema是一個事先存在的特殊數據庫;而sampdb則是剛纔建立的。另外還有一個數據庫test,它是MySQL在安裝過程當中建立的。若是你的訪問權限足夠高,你可能還會看到名爲mysql``的數據庫,它是一個權限分配表。

客戶端程序mysqlshow提供了一個命令行接口,經過它所得到的信息和使用SHOW語句所顯示的同樣。請記住:當你運行mysqlshow時,還須要提供正確的命令行選項,其中包括用戶名、密碼和主機名。這些選項與你在運行mysql時所用的同樣。

當不帶參數時,mysqlshow會顯示出一個數據庫列表:

% **mysqlshow** +--------------------+ | Databases | +--------------------+ | information_schema | | mysql | | sampdb | | test | +--------------------+

在帶上數據庫名以後,mysqlshow會列出給定數據庫裏的全部表:

% **mysqlshow sampdb **Database: sampdb +-----------+ | Tables | +-----------+ | member | | president | +-----------+

若是同時帶上數據庫名和表名,那麼mysqlshow會顯示出該表裏各列的信息,這時等同於SHOW ``FULL ``COLUMNS語句。

1.4.6.2 成績考評項目表

要想肯定成績考評項目須要用到哪些表,得先弄清在用紙質成績冊來記錄考生成績時可能會怎麼作。圖1-2展現了成績冊中一頁的內容。該頁的主體部分是用於記錄分數的表格。該頁裏面還包含了其餘一些讓分數有意義的信息。學生的姓名和ID號列在表格的左側。(爲了簡潔,這裏只列出了4位學生。)考試或測驗的舉行日期則列在表格的頂部。從該表格能夠看出:在9月的三、六、1六、23日有測驗;在9月9日和10月1日有考試。

圖1-2 成績冊示例

爲使用數據庫持續跟蹤這些信息,咱們須要一個score``(分數)表。那麼,在這個表裏應該包含哪些行呢?此問題不難回答。在每一行裏,須要列出學生姓名、考試或測驗日期,以及學生的考試分數。圖1-3展現了成績冊裏的部分分數在表裏的表現形式。(其中,日期是按MySQL的方式來表現的,即'CCYY-MM-DD'格式。)

圖1-3 最初的score

惋惜的是,採用這種方式獲得的表遺漏了一些信息。例如,請仔細看看圖1-3中的各行,它還未清楚地代表該成績是考試分數仍是測驗分數。通常來講,在評定的期末總成績時,考試分數與測驗分數的權重是有明顯區別的,所以有必要將考分類別標識出來。固然,咱們也能夠根據某給定日期的分數範圍(在數值上,測驗分數一般要比考試分數低不少)來推測其類型,但這種不採用具體數據明確標識的作法會帶來問題。

能夠在每行記錄裏將各種考分區別開來,具體方法就是:給score表增長一列,用TQ分別表示考試(test)或測驗(quiz),如圖1-4所示。這種作法的優勢是考分類別能直接體如今數據上;缺點是顯得有些冗餘。看看那些日期相同的行就能發現,考分類別欄裏的值都是相同的。全部9月23日的考分都屬於Q類,全部10月1日的考分都屬於T類。沒人會喜歡這個樣子。若是按這種辦法來記錄學生們的考試分數,那麼咱們不只要反覆輸入相同的日期,並且還要屢次輸入一個相同的考分類別。天啊!誰會願意輸入這麼多的冗餘信息呢?

圖1-4 修改後包含分數類型的score

咱們再來試試另外一種表示方式。此次不把考分類別放到score表裏,而是將它與考試日期對應起來。咱們能夠把考試日期列出來,而後用它來跟蹤在各個日期發生過什麼樣的「考試事件」(包括測驗和考試)。這樣,咱們就能夠根據score表裏的日期在grade_``event表裏查出當天的考試事件類型,從而得知某個分數是來自測驗,仍是來自考試。將score表裏的日期與grade_``event表進行匹配,便能得到考試的類別。圖1-5列出了這個表的結構,並展現了它與score表之間是如何與9月23日那天關聯起來的。經過將該行與grade_``event表裏的相應行相匹配,咱們便能看出該分數是一次測驗成績。

圖1-5 score表與grade_event表,經過日期連接

與經過推測來判斷考分類別的作法相比,這種方法更好一些。由於如今可以從記錄在數據庫裏的數據直接得出考試分數的類型。與將考分類別直接記錄在score表裏的作法相比,這種方法也更易讓人接受。如今,咱們只需記錄一次考分類別,不再用爲每一個考分都記錄一次了。

不過,咱們如今須要把多個表的信息進行組合。若是你是我,那麼在第一次據說這種事的時候,或許會想:「嘿,這個主意真不錯。可是,有這麼多的表,想查什麼東西會不會太費事?這會不會把事情搞得更復雜了?」

在某種程度上,這種擔憂是有道理的,它須要作更多的工做。記錄兩個表確定會比記錄一個表複雜。可是,請再仔細看看當初的成績冊(如圖1-2所示),你不是已經在記錄兩組信息了嗎?請看下面兩個事實。

  • 你須要把考試成績記錄在表格的每個格里,這些格都要按學生姓名和考試日期進行排列(姓名由上往下排列,考試日期則由左往右排列)。這正是我剛纔講過的兩組信息當中的一組,而它正好對應於score表裏的內容。
  • 你要如何才能知道各日期所對應的考試類型呢?你或許會這樣作:在日期上方寫一個TQ。因而,你又在表格的頂部把考試日期與考試類型關聯起來了。這正是我剛纔講過的兩組信息當中的第二組,而它也正好對應於grade_``event表裏的內容。

換句話說,儘管你可能還未意識到這一點,但你在成績冊裏所作的事,與我把信息放到兩個表裏的作法並沒有多大差別。惟一的區別在於,紙質成績冊裏的兩類信息沒有明確地分離開來。

成績冊裏的頁的概念體現了咱們對信息的思惟方式,同時這也代表:把信息妥善地放到數據庫裏並非一件簡單的事情。在平常生活中,人們習慣於把不一樣信息綜合起來,而後把它們看成一個總體來考慮。但數據庫沒法那樣工做,這也正是它們看起來不太天然的緣由。咱們習慣把信息統一塊兒來,這也使得咱們有時很難清楚地分辨出本身所擁有的信息是有多種類型,仍是隻有一種。正因如此,「以數據庫系統的方式進行思考」,考慮如何表現數據才具備挑戰性。

圖1-5裏的grade_``event表還隱含了這樣一個要求:全部的日期必須惟一,由於每一個日期要用於連接scoregrade_``event兩個表裏的各個行。換句話說,不能在同一天進行兩場測驗,或者一次測驗加一次考試。若是這樣作的話,那麼對於同一個日期,在score表裏將會出現兩組記錄,而在grade_``event表裏也會有兩條記錄,並且你也沒法說清如何將score``中的這些行與grade_``event``中的那兩行進行匹配。

假如你天天最多隻進行一場考試,那麼這個問題就毫不會出現。可是,能否假設這一狀況永遠不會發生呢?彷佛可行。畢竟,心地善良的你應該不會對學生過於苛刻,要對他們天天進行兩場考試。不過,我仍是會常常聽到有人聲稱:對於他們的數據,「這種奇怪的狀況永遠不會發生」。然而,事實證實,這種奇怪的狀況偶爾也會出現;而這時爲了彌補這一奇怪狀況所引起的各類問題,你便不得不從新設計相關表。

最好能防患於未然,事先想好如何處理這些問題。所以,咱們如今假設:你有時也會須要在同一天記錄兩組分數。這一問題該如何解決呢?事實證實,這個問題並非那麼難以解決。只需對有關數據的佈局結構做一點小小的改動,就能處理在同一天存在多個事件的狀況了,以下所示。

(1)在grade_``event表裏增長一列,用它來爲該表裏的每一行分配一個惟一的編號。這樣,每次事件就都擁有本身的ID編號了,咱們能夠將這一列命名爲event_id。這種作法看起來有點奇怪,不過在圖1-2的成績冊裏其實已經隱式地用到了這個屬性:這裏的事件ID與成績冊分數表格裏的列序號是至關的。雖然說這個列序號未被顯式地寫出來,並標註爲「event ID」,但它實際就是列序號。

(2)當把各個分數放到score表中的時候,記錄事件ID,不記錄日期。

完成上述改動以後,咱們會獲得圖1-6所示的結果。如今要用event_id來連接score表和grade_``event表,再也不使用date``了。使用grade_``event表不只能查出每一個考分的類別,還能查出它具體發生的日期。還有,在grade_``event表裏,具備惟一性的再也不是日期,而是事件ID。這意味着,在同一天能夠進行屢次考試和測驗(在聽到這個消息以後,你的學生確定會欣喜若狂),並且你也能把它們直接記錄下來。

圖1-6  score``表grade_``event 表,經過事件ID連接

必須認可,圖1-6裏的表結構不如前面的那幾個看起來順眼。score表變得更加抽象,由於它包含的列愈來愈讓人看不懂。請看圖1-4裏的score表,裏面既有考試日期又有考分類別,讓人一眼即能看明白。但在圖1-6所示的score表裏,那兩列都不見了,咱們看到的是一個高度抽象化的信息表示形式。誰願意看一個只包含「事件ID」的score表呢?它毫無心義。

此時此刻,咱們到了一個十字路口。你們以前還對電子化的成績考評系統充滿但願,以爲很快就能從繁瑣的評分工做中解脫出來。但是,在瞭解到「在數據庫裏分數信息其實是如何表示的」以後,你卻因這些信息表示起來至關抽象和分散而開始躊躇不前。

這天然會引出一個問題:「也許MySQL不適合我。不用數據庫會不會好些?」想必你們已猜到了,我對此持反對意見。緣由很簡單,你看本書的厚度就知道了。可是,當你在考慮如何開始某項工做以前,多考慮幾種狀況,以及想一下「若是使用像MySQL這樣的數據庫系統,或者使用其餘像電子表格程序那樣的工具,事情是否會變得更好」,這些都是不錯的作法。對比狀況以下。

  • 成績冊由行和列構成,電子表格也是如此。這使得它們在概念和外觀上都很類似。
  • 電子表格程序可以執行計算,所以能夠使用計算字段來統計每一個學生的分數。將測驗分數和考試分數按不一樣權重來統計可能會有點棘手,但相信你能辦到。

另外一方面,若是你想只對一部分數據進行操做(如只統計測驗分數,或者只統計考試分數),進行對比分析(如男生與女生的對比),或者想以靈活方式顯示統計信息,那麼狀況就有所不一樣了。這些工做不是電子表格所擅長的,而關係數據庫系統卻能輕易地完成。

另一個須要考慮的地方是:在關係數據庫裏表示具備抽象和分散特性的數據,也不是什麼大問題。在數據庫創建之初,仔細考慮好信息在數據庫裏的表示方式是頗有必要的,這樣你才能按照最符合你目標的方式來安排你的數據。不過,肯定好如何表示信息以後,你便須要依賴數據庫引擎來收集數據,並把它以一種對你來說頗有意義的方式呈現出來。這樣,你所看到的就不會是一組分散的數據塊了。

例如,當從score表檢索分數時,你想要看考試日期,而不想看到事件ID。這點很容易作到。數據庫將在grade_``event表裏根據事件ID查出考試日期,並顯示在你面前。你可能還想要知道考試分數是屬於測驗的,仍是屬於考試的。這點也很容易作到。數據庫能夠採用一樣的方式(根據事件ID)查出考分類別。別忘了,像MySQL這類的關係數據庫系統最擅長的就是:將一個事物與另外一樣事物進行關聯,從而在多個信息源裏把你最想知道的信息提取出來。在成績考評這個示例裏,MySQL會負責考慮經過事件ID將信息聚集到一塊兒,你沒必要去關心其中的細節。

如今,爲了提早讓你們瞭解到如何讓MySQL實現這種事物之間的關聯,假設你想要查看2012年9月23日的考試分數。下面這個查詢能夠將指定日期的考試分數查出來:

SELECT score.name, grade_event.date, score.score, grade_event.category FROM score INNER JOIN grade_event ON score.event_id = grade_event.event_id WHERE grade_event.date = '2012-09-23';

至關可怕,哈?這個查詢經過將score表中的各行和event表中的各行進行鏈接(關聯),檢索得出學生姓名、考試日期、考試分數和考分類別等信息。結果以下所示:

+--------+------------+-------+----------+ | name | date | score | category | +--------+----------- +-------+----------+ | Billy | 2012-09-23 | 15 | Q | | Missy | 2012-09-23 | 14 | Q | | Johnny | 2012-09-23 | 17 | Q | | Jenny | 2012-09-23 | 19 | Q | +--------+------------+-------+----------+

是否是以爲上面這個表格有點面熟?你應該很熟悉,它與圖1-4裏的表格佈局是同樣的。你沒必要知道事件ID就能得到結果。你只要指定你感興趣的那個日期,而後MySQL便會根據這個日期將考試分數找出來。所以,當從數據庫裏以某種對咱們很意義的形式將信息提取出來的時候,若是你還在擔憂那種抽象和分散是否會讓咱們迷失,那麼到這個時候你應該能夠看到根本不會出現這種狀況。

固然,在仔細查看該查詢以後,你或許又會產生一些新的疑問。換句話說,這個查詢看起來又長又複雜。只是要查出某一天的考試分數,就要寫得這麼複雜?沒錯,它是有點複雜。不過,在每次你想要調用某個查詢時,也有不少方法能夠避免輸入佔用不少行的SQL語句。通常狀況下,在執行完某個相似這樣的查詢以後,你能夠將它保存起來;而後,在必要時你即可以輕易地重複使用它。關於這一作法的更多內容請參考1.5節。

爲了讓你們對查詢過程先有所瞭解,我提早展現了這個示例。事實上,與咱們真正用來檢索考試分數的查詢相比,它算是簡單的。由於咱們還須要對錶的結構再作一次較大的改動。首先,咱們將score表裏的學生姓名替換成具備惟一性的學生ID。(也就是說,咱們將使用成績冊裏「ID」欄的值,而不使用「名稱」欄裏的值。)咱們還要另外新建一個名爲student的表,其中包含學生姓名(name)和學號(student_id)兩列(見圖 1-7)。

圖1-7 score表、student表和grade_event表,經過學生ID和事件ID連接

爲何要作這樣的改動呢?只爲解決可能會出現的兩名學生同名的狀況。使用惟一的學生ID編號有助於把他們的分數區別開來。這與咱們不用日期而是使用惟一的事件ID來區分同一天進行考試和測試的分數是同樣的道理。在對錶的結構作了上述改動以後,根據指定日期查詢考試分數的語句又複雜了一點:

SELECT student.name, grade_event.date, score.score, grade_event.category FROM grade_event INNER JOIN score INNER JOIN student ON grade_event.event_id = score.event_id AND score.student_id = student.student_id WHERE grade_event.date = '2012-09-23';

若是你如今爲看不懂這個查詢命令而擔憂,那麼請放鬆。大部分人都看不懂。在本教程的後半部分,咱們還會遇到這個查詢命令,不過先後的差別在於後面的那個版本會讓你眼前一亮。真的,不開玩笑。

從圖1-7裏能夠看到,student表裏增長了一些在成績冊裏沒有的內容:它多了一個用於記錄性別的列。能夠利用這個列來統計班級裏男女生的人數;也能夠用它來作一些複雜的事情,如比較男女生的成績。

至此,咱們幾乎完成了成績考評項目全部表的建立。最後還需再增長一個表,用來記錄考勤狀況。這個表的內容相對比較簡單,只包含一個學生ID和一個日期(見圖1-8)。這個表的每一行都表明了一位在指定日期有缺席的學生。到期末的時候,咱們將利用MySQL的統計功能來對錶裏的數據進行彙總,從而統計出每位學生的缺勤天數。

圖1-8 absence表

1.student

如今,咱們知道了那些成績考評表的樣子,下面來建立它們。用於建立student表的CREATE TABLE語句以下:

CREATE TABLE student ( name VARCHAR(20) NOT NULL, sex ENUM('F','M') NOT NULL, student_id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (student_id) ) ENGINE = InnoDB;

請注意觀察,我在CREATE TABLE語句里加入了一些新的內容(在末尾加上了ENGINE子句)。稍後我會解釋它的用途。

你能夠在mysql客戶端程序裏輸入上面的CREATE TABLE語句,也能夠在命令行裏執行下列命令:

% **mysql sampdb < create_student.sql**

這條CREATE TABLE語句將建立一個名爲student的表,其中包含三列:namesexstudent_id

name是一個長度可變的字符串列,它最多能夠存儲20個字符。這裏的名字表示比「美史聯盟」表裏的簡單,它只使用了一個單列,並無將名和姓單獨分開。這樣作的緣由在於:我事先知道成績考評查詢示例不須要用到多個列來表示名字。(沒錯,這是騙人的。我認可!實際上你可能須要使用多個列。)

sex用於代表某位學生是男生仍是女生。這是一個ENUM(枚舉)列,其取值只能是在該列的規範裏列出的那些值當中的一個:'F'表明女生,'M'表明男生。當你想把某列的可取值限定在某個有限集合內時,ENUM會很是管用。固然,咱們也能夠把該列定義爲CHAR(1),但ENUM可讓被容許的列值更加明確。若是你忘了它都有哪些可取值,就能夠調用DESCRIBE命令來查看。MySQL會列出ENUM列的合法枚舉值:

mysql> **DESCRIBE student 'sex'; **+-------+---------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+---------------+------+-----+---------+-------+ | sex | enum('F','M') | NO | | | | +-------+---------------+------+-----+---------+-------+

ENUM列的值不必定非得是單個字符。例如,還能夠把sex列定義爲:ENUM ( 'female', 'male')

student_id是一個整型列,用於保存惟一的學生ID編號。一般狀況下,ID編號來源於某個權威機構,如學校辦公室。但本書出於示例目的,將本身編造它們。咱們將使用一個AUTO_INCREMENT列,其定義與前面建立member表時所用的member_id列相相似。

若是的確須要從學校辦公室得到學生ID編號,而不能自動生成它們,那麼在定義student_id列時請不要爲它加上AUTO_INCREMENT屬性。不過,須要保留PRIMARY KEY子句,其目的在於要避免ID值出現重複或爲NULL

如今,CREATE TABLE語句末尾的ENGINE子句有什麼做用呢?若是存在這個子句,那麼它將在建立新表時, 爲MySQL應該使用的存儲引擎指定名字。「存儲引擎」就是一種用來管理某種表的處理器。MySQL有好幾種存儲引擎,都各有特點。其中兩個最經常使用的引擎是InnoDB(MySQL 5.5版本的默認引擎)和MyISAM(MySQL 5.5版本以前的默認引擎)。

關於這兩個引擎之間的不一樣之處請參考2.6.1節。如今,只需說明成績考評項目表的定義顯式指定了InnoDB引擎就好了,由於咱們須要InnoDB引擎所提供的稱爲「引用完整性(referential integrity)」的功能。該功能是經過使用外鍵(foreign key)來實現的。也就是說,咱們能夠使用MySQL將某些約束施加到兩個表之間的相互關係上,這種作法對成績考評項目的各個表來說是頗有必要的。

  • 考試成績與考試事件和學生都有關聯:只有當與考試成績相關聯的學生ID和考試事件ID分別在student``表grade_event表裏存在時,才容許考試成績進入score表。
  • 相似地,考勤記錄與學生有關聯:只有與考勤相關聯的學生ID在student表裏存在時,才容許考勤狀況進入absence表。

爲了實施這些約束,咱們須要創建一些外鍵關係。這裏的「外」表示的意思是「在另外一個表裏」,而「外鍵」指的是必須與另外一個表裏的某個鍵值相匹配的鍵值。隨着後面更多成績考評項目表的建立,這些概念將會變得愈來愈清晰。

在早些時候,咱們在建立美史聯盟的表(presidentmember)時,並無使用ENGINE子句,所以服務器會使用默認存儲引擎來建立它們。如前所述,默認存儲引擎是InnoDB(除非服務器被從新配置過)。student表的定義顯式地包括了ENGINE = InnoDB,防止了服務器爲其配置與此不一樣的默認值。

2.grade_event

grade_event表的定義以下所示:

CREATE TABLE grade_event ( date DATE NOT NULL, category ENUM('T','Q') NOT NULL, event_id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (event_id) ) ENGINE = InnoDB;

要建立grade_event表,能夠在mysql客戶端程序裏輸入上述CREATE TABLE語句,也能夠在命令行上執行下面這條命令:

% **mysql sampdb < create_grade_event.sql**

d``ate``列用於存放標準的MySQL的DATE(日期類型)值,格式爲'CCYY-MM-DD'(年在前)。

category表示的是分數類別。與student表裏的sex列同樣,category也是一個枚舉列。其容許的取值是'T''Q',分別表明測試(test``)和測驗(quiz``)

event_id是一個AUTO_INCREMENT列,同時也被定義爲了PRIMARY KEY。它與student表裏的student_id列相似。利用AUTO_INCREMENT屬性,咱們能方便地生成惟一事件ID的值。與student表裏的student_id列相似,其特定的值並不重要,重要的是它們必須惟一。

由於這些列必須都要有值,因此它們所有被定義成了NOT NULL

3.``score

score表的建立語句以下所示:

CREATE TABLE score ( student_id INT UNSIGNED NOT NULL, event_id INT UNSIGNED NOT NULL, score INT NOT NULL, PRIMARY KEY (event_id, student_id), INDEX (student_id), FOREIGN KEY (event_id) REFERENCES grade_event (event_id), FOREIGN KEY (student_id) REFERENCES student (student_id) ) ENGINE = InnoDB;

這個表的定義又包含了新內容:FOREIGN KEY結構。咱們稍後會講到它。

要建立score表,能夠在mysql客戶端程序裏輸入上面的語句,也能夠在命令行裏執行下面的命令:

% **mysql sampdb < create_score.sql**

score是一個INT列,用於保存整型分數值。若是想要保存像58.5那樣帶有小數部分的分數,那麼最好使用能表示它們的數據類型,如DECIMAL

student_id列和event_id列都是整型列,它們分別表示每個考試分數所對應的學生和考試事件。經過它們與student表和grade_``event表裏的相應ID值連接起來,咱們就可以查出學生姓名和考試日期。關於student_id列和event_id列有兩個要點須要注意一下。

  • 咱們已將這兩列的組合設置成了一個PRIMARY KEY。這樣能夠確保咱們不會重複記錄某位學生在某次考試或測驗的分數。請注意,只有event_idstudent_id的組合才具備惟一性。在score表裏,這兩個ID值自身都不具有惟一性。對於每個event_id值(每位學生有一個)都會有多個分數行與之對應;對於每個student_id值(每次考試或測驗有一個)也會有多行記錄相對應。
  • 每個ID列都須要用FOREIGN KEY子句來定義約束條件。此子句的REFERENCES部分代表這個ID列是與哪一個表的哪一列相對應。event_id列的約束條件爲:該列裏的每個值都必須與grade_event表裏的某個event_id值相匹配。相似地,score表裏的每個student_id值都必須與student表裏的某個student_id值相匹配。

那個PRIMARY KEY定義能夠確保咱們不會建立重複的分數行。而FOREIGN KEY定義能夠確保在咱們的記錄行不會有虛假的ID值,即要求它們必須存在於grade_event和student表裏。

爲何student_id列會有一個索引呢?這是由於,對於FOREIGN KEY定義裏的任何列,都應該有一個關於它們的索引,或者它們應該是某個多列索引裏被首先列出的列,這樣能加快查找速度。對於event_id列的FOREIGN KEY,該列被優先列在PRIMARY KEY裏。對於student_id列的FOREIGN KEY,則沒法使用PRIMARY KEY,由於student_id列未被首先列出來。所以,咱們須要在student_id列上單首創建一個索引。

若有必要,InnoDB存儲引擎會自動爲出如今外鍵定義裏的那些列建立一個索引,但它使用的索引定義不必定是你所指望的(更多有關信息請參考2.13節)。顯示地定義這個索引能夠避免這一問題。

4.absence

absence表用於記錄學生的考勤狀況,其建立語句以下所示:

CREATE TABLE absence ( student_id INT UNSIGNED NOT NULL, date DATE NOT NULL, PRIMARY KEY (student_id, date), FOREIGN KEY (student_id) REFERENCES student (student_id) ) ENGINE = InnoDB;

要建立absence表,能夠在mysql客戶端程序裏輸入上述語句,也能夠在命令行上執行如下命令:

% **mysql sampdb < create_absence.sql**

student_id列和date列都定義爲NOT NULL,這樣能夠防止有缺失值。爲了不出現重複行,咱們將這兩列的組合定義爲一個主鍵(primary key)。在同一天統計兩次學生缺勤狀況確定是不公平的,對吧?

absence表也包含有一個外鍵關係,其目的在於確保每個student_id值都與student表裏的一個student_id值相匹配。

在成績考評項目的各個表裏設置外鍵關係,能夠在讓這些約束條件在數據錄入階段發揮做用,如咱們只想插入那些包含合法考試事件ID值和學生ID值的記錄行。不過,外鍵關係還有另一種效果。它們會造成某些依賴關係,讓你按照必定的順序來建立和刪除表,以下所示。

  • score表依賴於grade_event表和student表,所以在建立score表以前必須先建立其依賴的表。相似地,adsence表依賴於student表,所以在建立adsence表以前,student表必須已存在。
  • 在刪除表時,必須把上面的順序顛倒過來。若是不先刪除score表,就沒法刪除grade_event表;若是不先刪除score表和absence表,也沒法刪除student表。

1.4.7 添加新行

至此,咱們的數據庫和表就都建立好了。接下來,咱們須要往表裏添加一些行。不過,在往表裏放入某些內容以後,得能知道如何檢查表裏的內容,所以,儘管有關檢索操做的詳細介紹要在1.4.9節纔會講到,此時也至少應該知道下面這條語句是用來查看tbl_name表裏的所有內容的:

SELECT * FROM tbl_name;

例如:

mysql> **SELECT * FROM student;** Empty set (0.00 sec)

如今,mysql報告說該表爲空,但在練習完本節的幾個示例以後,你會看到不同的結果。

往數據庫裏添加數據的辦法有好幾種。能夠用INSERT語句將行手工插到表中,也能夠利用文件把行添加到表裏。該文件的內容既能夠是一系列事先編寫好的能直接提供給mysqlINSERT語句,也能夠是經過LOAD DATA語句或mysqlimport客戶端程序來加載的原始數據值。

本節將演示各類把記錄插到表中的方法。你們應該多練習這些方法,熟悉和掌握它們的工做原理以及用法。在練習完這些方法以後,再轉到1.4.8節,運行那裏的命令。那些命令能夠用來刪除這些表,而後再重建它們,並將本書提供的數據加載到這些表裏。這樣,你的數據庫所包含的內容就會與我在後面示例中用到的數據保持一致,而你在練習本書其餘示例時也會看到相同的結果。若是你已知道如何插入行,那麼能夠直接跳過本節。

1.4.7.1 利用INSERT添加行

咱們先使用INSERT語句來添加行,這是一條SQL語句,你可用它來指定要插入數據行的那個表,以及要插入的數據行和該行的各個列值。INSERT語句有多種格式。

(1)一次性指定所有列值。語法以下:

INSERT INTO _tbl_name_ VALUES(value1, value2, ...);

例如:

mysql> **INSERT INTO student VALUES('Kyle', 'M', NULL); **mysql> **INSERT INTO grade_event VALUES('2012-09-03', 'Q', NULL);**

在使用此語法時, VALUES列表必須包含表中每一列的值,而且值的順序要與各列在表裏的存儲順序保持一致。(一般狀況下,該順序就是各列在該表的CREATE TABLE語句裏指定的順序。)若是你不太肯定列的順序,那麼能夠利用DESCRIBE tbl_name語句來查明。

在MySQL裏,你能夠使用單引號或雙引號將字符串和日期值括起來,不過使用單引號會更標準些。NULL值對應於student表和grade_event表裏的AUTO_INCREMENT列。在AUTO_INCREMENT列裏插入一個「缺失值」(missing value),可讓MySQL爲該列自動生成下一個序號。

MySQL還支持使用一條INSERT語句,同時指定多個值列表的方式,將多個行插入一個表裏:

INSERT INTO tbl_name VALUES(...),(...),... ;

例如:

mysql> **INSERT INTO student VALUES('Avery','F',NULL),('Nathan','M',NULL);**

與使用多條INSERT語句的方式相比,這種方式不只能讓你少打字,還能提升服務器的執行效率。請注意,將每行各列的值括起來的那對括號不可少。下列語句是非法的,由於它括號內包含的列值的個數不正確。

mysql>** INSERT INTO student VALUES('Avery','F',NULL,'Nathan','M',NULL); **ERROR 1136 (21S01): Column count doesn't match value count at _row_ 1

(2)命名賦值列,並列出它們的值。當你建立的行只有少數幾列須要初始化時,這種方式特別有用。語法以下:

INSERT INTO _tbl_name_ (col_name1,col_name2,...) VALUES(value1,value2,...);

例如:

mysql> **INSERT INTO member (last_name,first_name) VALUES('Stein','Waldo');**

這種形式的INSERT語句也能夠一次插入多個值列表:

mysql> **INSERT INTO student (name,sex) VALUES('Abby','F'),('Joseph','M');**

對於沒在INSERT語句中指定的列,將被賦予默認值。例如,上面兩條語句都未給member_idevent_id賦值,所以MySQL會將默認值NULL賦給它們。又由於member_idevent_id都是AUTO_INCREMENT列,所以最後的結果是這兩列都會被分別賦予各自的下一個序號,這與顯式地將NULL賦給它們是同樣的。

(3)使用一系列的「列/值」形式進行賦值。此語法使用SET子句實現,其中包含多個col_name``= value的賦值形式,沒有使用VALUES()列表的形式。

INSERT INTO tbl_name SET col_name1=value1, col_name2=value2, ... ;

例如:

mysql>** INSERT INTO member SET last_name='Stein',first_name='Waldo';**

對於沒在SET子句裏指定的列,將被賦予默認值。這種形式的INSERT語句沒法用於一次插入多個行的情形。

既然已對INSERT語句的工做原理有所瞭解,那麼如今便用它來檢查一下,咱們所創建的外鍵關係是否真的可以防止將不規範的行錄入score表和absence表裏。試着在grade_event和student表裏找幾個不存在的ID值,而後插入幾條分別包含這些值的行:

mysql> **INSERT INTO score (event_id,student_id,score) VALUES(9999,9999,0); **ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`sampdb`.`score`, CONSTRAINT `score_ibfk_1` FOREIGN KEY (`event_id`) REFERENCES `grade_event` (`event_id`)) mysql> **INSERT INTO absence SET student_id=9999, date='2012-09-16'; **ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`sampdb`.`absence`, CONSTRAINT `absence_ibfk_1` FOREIGN KEY (`student_id`) REFERENCES `student` (`student_id`))

有錯誤消息出現,即代表這些約束髮揮了做用。

1.4.7.2 利用文件添加新行

另外一種把行載入表中的方法是,直接從文件裏讀取它們。該文件能夠包含INSERT語句或原始數據。例如,在sampdb發行版裏就有一個名爲insert_president.sql的文件,它包含一系列用於將新行添加到president表裏的INSERT語句。若是你是在與該文件相同的目錄裏,那麼你能夠像下面那樣直接執行這些語句:

% **mysql ****Sampdb ****< insert_president.sql**

若是你已經運行了mysql,那麼能夠用一條source命令來讀取這個文件:

mysql> **source insert_president.sql****;**

若是文件裏存儲的行不是INSERT語句而是原始數據,那麼能夠利用LOAD DATA語句或客戶端程序mysqlimport來加載它們。

LOAD DATA語句是一個從文件裏讀取數據的批量加載程序。它須要在mysql裏運行:

mysql> **LOAD DATA LOCAL INFILE 'member.txt' INTO TABLE member;**

假設member.txt文件位於客戶端主機的當前目錄裏,那麼上面這條語句會讀取該文件,並將其內容發送至服務器,加載到member表裏。member.txt文件可在sampdb發行版裏找到。

默認狀況下, LOAD DATA語句會假設各列的值是以製表符分隔的,各行末尾都是換行符。同時,假設這些值的順序都與表裏存儲的列的順序相同(文件裏的\N值表示的是NULL)。你也能夠用它來讀取其餘格式的文件,或者指定不一樣的列順序。有關LOAD DATA的更多細節請參考附錄E。

LOAD DATA語句裏的關鍵字LOCAL會引起客戶端程序(在本示例裏,指的是mysql)讀取數據文件,並把文件內容發送到服務器進行加載。若是省略了LOCAL,那麼數據文件必須存在於服務器主機上,而且你須要擁有FILE服務器訪問權限(大部分MySQL用戶都沒有這樣的權限)。另外,你還要指定完整的文件路徑,以便服務器能找到它。

若是在LOAD DATA語句裏使用LOCAL時遇到如下錯誤信息,那麼極可能是由於在默認狀況下LOCAL功能被禁用了:

ERROR 1148 (42000): The used command is not allowed with this MySQL version

能夠在mysql以後加上--local-infile選項再試一次。例如:

% **mysql --local-infile sampdb **mysql> **LOAD DATA LOCAL INFILE 'member.txt' INTO TABLE member;**

若是這招也無論用,那麼說明服務器在啓動時須要帶上--local-infile選項。

另外一種加載數據文件的方法是在命令提示符裏使用客戶端程序mysqlimport。它會爲你生成一條LOAD DATA語句:

% **mysqlimport --local sampdb member.txt**

與程序mysql的用法同樣,請根據須要在命令行裏指定鏈接參數,並把它們放置在那個數據庫名字的前面,緊挨着它。

對於上面這條命令, mysqlimport程序將生成一條能將member.txt文件裏的內容加載到member表裏的LOAD DATA語句。這是由於mysqlimport程序是根據數據文件的名字來肯定表名的,同時它會把文件名中第一個句號(.)以前的全部內容都看成表名。例如,mysqlimport會把名爲member.txtpresident.txt的文件分別加載到member表和president表裏。這意味着,你應該仔細挑選數據文件名,不然,mysqlimport將沒法使用正確的表名。若是想要加載文件member1.txtmember2.txt,那麼mysqlimport會認爲是要把這兩個文件分別加載到名爲member1member2的表裏去。若是你真的想將這兩個文件加載到member表裏,那麼能夠分別將它們命名爲member.1.txtmember.2.txt,或者member.txt1member.txt2

1.4.8 重置sampdb數據庫

在練習完成上面介紹的這幾種添加行的方法以後,爲了順利進行後面的學習,你應該從新創建和加載sampdb數據庫裏的各個表,把整個數據庫恢復爲原樣。請在包含sampdb發佈版文件的目錄下,運用mysql程序來執行下面這些語句:

% **mysql sampdb **mysql> **source create_member.sql; **mysql> **source create_president.sql; **mysql> **source insert_member.sql; **mysql> **source insert_president.sql; **mysql> **DROP TABLE IF EXISTS absence, score, grade_event, student; **mysql> **source create_student.sql; **mysql> **source create_grade_event.sql; **mysql> **source create_score.sql; **mysql> **source create_absence.sql; **mysql> **source insert_student.sql; **mysql> **source insert_grade_event.sql; **mysql> **source insert_score.sql; **mysql> **source insert_absence.sql;**

若是不想單獨輸入這麼多條語句,那麼在Unix系統上,能夠執行下面這條命令:

% **sh init_all_tables.sh sampdb**

而在Windows系統上,能夠執行下面這條命令:

C:\> **init_all_tables.bat sampdb**

不管使用哪條命令,若是須要在命令行裏指定鏈接參數,那麼請把它們放到命令名的後面,且緊挨着它。

1.4.9 檢索信息

如今,咱們的表都建好了,而且加載了數據。下面一塊兒來看看如何使用這些數據。使用SELECT語句能夠檢索和顯示錶裏的信息。你能夠根據本身的須要以常規或特定的方式來檢索信息。例如,能夠把表裏的全部內容都顯示出來:

SELECT * FROM president;

也能夠只顯示不多的數據,如一行中的一列:

SELECT birth FROM president WHERE last_name = 'Eisenhower';

SELECT語句擁有幾個子句,你能夠根據須要組合它們,用於檢索你感興趣的信息。這些子句能夠很簡單,也能夠很複雜,所以語句SELECT也會隨之變得簡單或者複雜。不過,本書中絕對沒有整頁長的、須要花費一個鐘頭才能搞明白的查詢語句。當我遇到長長的(arm-length)查詢語句時,一般會跳過它們,我想你也會這樣作。

SELECT語句的簡化語法以下:

SELECT what to retrieve FROM table or tables WHERE conditions that data must satisfy;

在寫SELECT語句時,須要先指定檢索的內容,而後再加上一些可選的子句。上面顯示的兩個子句(FROMWHERE)是最爲常見的,儘管還能夠指定其餘子句,如GROUP BYORDER BYLIMIT。請記住,SQL語言對語句格式並無嚴格的要求,所以寫你本身的SELECT語句時,沒必要嚴格像本書示例那樣換行排列。

FROM子句一般是不可少的,但當你不須要給出表名時,能夠省略它。例如,下面這條查詢語句只顯示一些表達式的值。這些值的計算並未涉及任何表,所以這裏不須要FROM子句:

mysql> **SELECT 2+2, 'Hello, world', VERSION(); **+-----+--------------+------------+ | 2+2 | Hello, world | VERSION() | +-----+--------------+------------+ | 4 | Hello, world | 5.5.30-log | +-----+--------------+------------+

當的確須要使用FROM子句來指定要從哪一個表檢索數據時,還須要指明要查看哪些列。SELECT語句最多見的一種形式是使用一個星號(*)做爲列說明符,表明「全部列」。下面這條查詢語句將顯示student表裏的全部列:

mysql> **SELECT * FROM student; **+-----------+-----+------------+ | name | sex | student_id | +-----------+-----+------------+ | Megan | F | 1 | | Joseph | M | 2 | | Kyle | M | 3 | | Katie | F | 4 | ...

這些列將按它們在表裏的存儲順序依次顯示出來。這個順序與你用DESCRIBE student語句看到的列順序是一致的。(示例末尾處的省略號「...」表示該查詢返回的行實際上有不少。)

也能夠把本身想要查看的那些列的名字列出來。例如,只想查看學生姓名,則能夠這樣作:

mysql> **SELECT name FROM student; **+-----------+ | name | +-----------+ | Megan | | Joseph | | Kyle | | Katie | ...

若是要列出多個列名,那麼須要使用逗號把它們分隔開。下面這條語句等價於SELECT ``*`` FROM student語句,但它把各列的名字明確地列了出來:

mysql> **SELECT name, sex, student_id FROM student; **+-----------+-----+------------+ | name | sex | student_id | +-----------+-----+------------+ | Megan | F | 1 | | Joseph | M | 2 | | Kyle | M | 3 | | Katie | F | 4 | ...

你能夠按任意順序列出各個列名:

SELECT name, student_id FROM student; SELECT student_id, name FROM student;

只要你願意,甚至還能夠重複列出某一列的名字,只是這樣作一般沒什麼意義。

另外,還能夠從一個以上的表裏選取列,即多表「鏈接」(join)。更多關於鏈接的信息請參考1.4.9.10節。

一方面,在MySQL裏,列名不區分大小寫,所以下面這些檢索語句都是等價的:

SELECT name, student_id FROM student; SELECT NAME, STUDENT_ID FROM student; SELECT nAmE, sTuDeNt_Id FROM student;

另外一方面,數據庫名和表名多是區分大小寫的。具體狀況取決於服務器主機所使用的文件系統,以及MySQL的配置。Windows系統的文件名不區分大小寫,因此運行在它上面的服務器也不區分數據庫名和表名的大小寫。在Unix系統上,文件名一般都區分大小寫,所以運行在它上面的服務器會區分數據庫名和表名的大小寫。Mac OS X系統的擴展文件系統比較特殊,它不區分大小寫。

若是想讓MySQL服務器不區分數據庫名和表名的大小寫,那麼能夠對它進行配置。更多詳細信息請參考11.2.6節。

1.4.9.1 指定檢索條件

要想限制SELECT語句檢索出來的行數,能夠使用WHERE子句,指定列值所必須知足的檢索條件。例如,能夠搜索某個範圍內的數值:

mysql> **SELECT * FROM score WHERE score > 95; **+------------+----------+-------+ | student_id | event_id | score | +------------+----------+-------+ | 5 | 3 | 97 | | 18 | 3 | 96 | | 1 | 6 | 100 | | 5 | 6 | 97 | | 11 | 6 | 98 | | 16 | 6 | 98 | +------------+----------+-------+

能夠查找包含字符數據的字符串值。對於默認的字符集和排序方式,字符串的比較操做一般不區分大小寫:

mysql> **SELECT last_name, first_name FROM president ** -> **WHERE last_name='ROOSEVELT'; **+-----------+-------------+ | last_name | first_name | +-----------+-------------+ | Roosevelt | Theodore | | Roosevelt | Franklin D. | +-----------+-------------+ mysql> **SELECT last_name, first_name FROM president ** -> **WHERE last_name='roosevelt'; **+-----------+-------------+ | last_name | first_name | +-----------+-------------+ | Roosevelt | Theodore | | Roosevelt | Franklin D. | +-----------+-------------+

也能夠查找日期:

mysql> **SELECT last_name, first_name, birth FROM president ** -> **WHERE birth < '1750-1-1'; **+------------+------------+------------+ | last_name | first_name | birth | +------------+------------+------------+ | Washington | George | 1732-02-22 | | Adams | John | 1735-10-30 | | Jefferson | Thomas | 1743-04-13 | +------------+------------+------------+

甚至還能夠查找組合值:

mysql> **SELECT last_name, first_name, birth, state FROM president ** -> **WHERE birth < '1750-1-1' AND (state='VA' OR state='MA'); **+------------+------------+------------+-------+ | last_name | first_name | birth | state | +------------+------------+------------+-------+ | Washington | George | 1732-02-22 | VA | | Adams | John | 1735-10-30 | MA | | Jefferson | Thomas | 1743-04-13 | VA | +------------+------------+------------+-------+

WHERE子句裏的表達式容許使用算術運算符(見表1-1)、比較運算符(見表1-2)和邏輯運算符(見表1-3)。在表達式裏還能夠使用括號。在運算時,能夠使用常量、表列和函數調用。本教程的語句裏會用到一些MySQL的函數,但因爲函數比較多,這裏沒法一一列出。有關這些函數的詳細信息請參考附錄C。

表1-1 算術運算符

運算符

含  義

+

加法

-

減法

*

乘法

/

除法

DIV

整除

%

模運算(除法餘數)

 

 

表1-2 比較運算符

運算符

含  義

<

小於

<=

小於等於(不大於)

=

等於

<=>

等於(可用於NULL值)

<> 或 !=

不等於

>=

大於等於(不小於)

>

大於

表1-3 邏輯運算符

運算符

含  義

AND

邏輯與

OR

邏輯或

XOR

邏輯異或

NOT

邏輯非

當須要在查詢語句裏使用邏輯運算符時,千萬要注意:邏輯運算符AND與人們平常生活中所說的「和」在含義上是不同的。假設你想要找出「出生於弗吉尼亞州和馬薩諸塞州的總統」。這裏用到了「和」字,它彷佛是在暗示你該編寫以下查詢語句:

mysql>** SELECT last_name, first_name, state FROM president ** ->** WHERE state='VA' AND state='MA'; **Empty set (0.01 sec)

空的結果清楚代表,這條語句沒起做用。爲何沒起做用呢?由於這條查詢語句的真正含義是「把同時出生於弗吉尼亞州和馬薩諸塞州的總統」找出來,而這是不可能的。在平常生活裏,你能夠用「和」來表達你的查詢條件;但在SQL裏,必須使用OR來鏈接這兩個條件:

mysql> **SELECT last_name, first_name, state FROM president** -> **WHERE state='VA' OR state='MA'; **+------------+-------------+-------+ | last_name | first_name | state | +------------+-------------+-------+ | Washington | George | VA | | Adams | John | MA | | Jefferson | Thomas | VA | | Madison | James | VA | | Monroe | James | VA | | Adams | John Quincy | MA | | Harrison | William H. | VA | | Tyler | John | VA | | Taylor | Zachary | VA | | Wilson | Woodrow | VA | | Kennedy | John F. | MA | | Bush | George H.W. | MA | +------------+-------------+-------+

請你們務必注意平常語言與SQL語言之間的差別,不僅是在本身編寫查詢語句時要引發注意,在爲其餘人編寫查詢時也要注意。必定要仔細傾聽別人對查詢內容的描述,不能將他們的描述照搬成SQL的邏輯運算符。以剛描述的那個查詢爲例,與查詢語句至關的天然語言表述應該是:「把出生於弗吉尼亞州或馬薩諸塞州的總統找出來」。

當在組織能夠查找到多個獨立值的查詢語句時,你可能會發現,使用IN()運算符更簡潔。在使用IN()以後,前面的那個查詢能夠改寫成下面這個樣子:

SELECT last_name, first_name, state FROM president WHERE state IN('VA','MA');

當把一個列與大量值進行比較時,使用IN()會特別方便。

1.4.9.2 NULL

NULL值很特殊。其含義是「無值」或「未知值」,因此不能採用兩個「已知值」的比較方式,將它與「已知值」進行比較。若是試圖將NULL與常規的算術比較運算符一塊兒使用,那麼其結果將是未定義的(undefined):

mysql> **SELECT NULL < 0, NULL = 0, NULL <> 0, NULL > 0; **+----------+----------+-----------+----------+ | NULL < 0 | NULL = 0 | NULL <> 0 | NULL > 0 | +----------+----------+-----------+----------+ | NULL | NULL | NULL | NULL | +----------+----------+-----------+----------+

事實上,你也不能讓NULL與其自身進行比較,由於兩個「未知值」的比較結果是沒法肯定的:

mysql> **SELECT NULL = NULL, NULL <> NULL; **+-------------+--------------+ | NULL = NULL | NULL <> NULL | +-------------+--------------+ | NULL | NULL | +-------------+--------------+

若是須要測試多個NULL值是否相等,那麼必須使用IS NULLIS NOT NULL,而不能使用=、<>或者!=。例如,對於目前仍然健在的美國總統,其逝世日期在president表裏表示爲NULL。若是想要找到他們,能夠使用下面這條查詢語句:

mysql> **SELECT last_name, first_name FROM president WHERE death IS NULL; **+-----------+-------------+ | last_name | first_name | +-----------+-------------+ | Carter | James E. | | Bush | George H.W. | | Clinton | William J. | | Bush | George W. | | Obama | Barack H. | +-----------+-------------+

IS NOT NULL能夠用來查找非NULL值。下面這條查詢語句能夠找到那些具備後綴名的名字:

mysql> **SELECT last_name, first_name, suffix ** -> **FROM president WHERE suffix IS NOT NULL; **+-----------+------------+--------+ | last_name | first_name | suffix | +-----------+------------+--------+ | Carter | James E. | Jr. | +-----------+------------+--------+

MySQL特有的<=>比較運算符可用於NULLNULL的比較。將前面兩個查詢語句改寫成使用這個運算符的查詢語句:

SELECT last_name, first_name FROM president WHERE death <=> NULL; SELECT last_name, first_name, suffix FROM president WHERE NOT (suffix <=> NULL);

1.4.9.3 對查詢結果排序

每位MySQL用戶最終都會注意到這樣一種狀況:在你建立一個表,並存入一些行以後,使用 「SELECT ``* ``FROMtbl_name」 語句查詢出的行,其順序一般與插入它們時的順序一致。這很符合人們的思惟習慣,人們天然會認定查詢出的行的順序與插入它們時的順序是相同的。但實際狀況並不是這樣。由於在表數據初始加載完以後,刪除和插入行都會改變服務器返回行的順序。

關於行檢索順序,請記住這樣一條原則:服務器不會保證返回行的前後順序,除非你本身指定順序。要想結果有序,須要在查詢語句後面增長一條ORDER BY子句。下面這條查詢語句會按姓的字母順序返回美國總統的姓名:

mysql>** SELECT last_name, first_name FROM president ** ->** ORDER BY last_name; **+------------+---------------+ | last_name | first_name | +------------+---------------+ | Adams | John Quincy | | Adams | John | | Arthur | Chester A. | | Buchanan | James | ...

ORDER BY子句的默認排序方式是升序排列。在其中的列名後面加上關鍵字ASCDESC,能夠指定是按照升序排列仍是按照降序排列。例如,想讓美國總統的姓名按姓的逆序(降序)排列顯示,那麼就要使用DESC關鍵字:

mysql> **SELECT last_name, first_name FROM president ** -> **ORDER BY last_name DESC; **+------------+---------------+ | last_name | first_name | +------------+---------------+ | Wilson | Woodrow | | Washington | George | | Van Buren | Martin | | Tyler | John | ...

你能夠對多列進行排序,並且每一列單獨地按升序或降序排列。下面的查詢語句用於檢索president表裏的行,先按出生地所在州進行逆序排列,而後在每個相同的州里再按姓升序排列:

mysql> **SELECT last_name, first_name, state FROM president ** -> **ORDER BY state DESC, last_name ASC; **+------------+---------------+-------+ | last_name | first_name | state | +------------+---------------+-------+ | Arthur | Chester A. | VT | | Coolidge | Calvin | VT | | Harrison | William H. | VA | | Jefferson | Thomas | VA | | Madison | James | VA | | Monroe | James | VA | | Taylor | Zachary | VA | | Tyler | John | VA | | Washington | George | VA | | Wilson | Woodrow | VA | | Eisenhower | Dwight D. | TX | | Johnson | Lyndon B. | TX | ...

在一個列裏,對於升序排列,NULL值老是出如今開頭;而對於降序排列,它老是出如今末尾。爲確保NULL值出如今指定排列順序的末尾,須要額外增長一個能夠區分NULL值和非NULL值的排序列。例如,想按逝世日期降序排列全部總統,那麼當前健在的(逝世日期爲NULL的)那些總統就應該出如今結果順序的末尾。而若是想讓他們出如今開頭,就要使用下面這條查詢語句:

mysql>** SELECT last_name, first_name, death FROM president ** ->** ORDER BY IF(death IS NULL,0,1), death DESC, last_name; **+------------+---------------+------------+ | last_name | first_name | death | +------------+---------------+------------+ | Bush | George W. | NULL | | Bush | George H.W. | NULL | | Carter | James E. | NULL | | Clinton | William J. | NULL | | Obama | Barack H. | NULL | | Ford | Gerald R. | 2006-12-26 | | Reagan | Ronald W. | 2004-06-05 | | Nixon | Richard M. | 1994-04-22 | ... | Adams | John | 1826-07-04 | | Jefferson | Thomas | 1826-07-04 | | Washington | George | 1799-12-14 | +------------+---------------+------------+

其中, IF()函數的做用是計算第一個參數給出的那個表達式的值,而後根據計算結果的真假來決定是返回第二個參數(爲真),仍是返回第三個參數(爲假)。對於這條查詢語句,當遇到NULL值時, IF()函數的計算結果爲0;當遇到非NULL值時,它計算結果爲1。最終結果會把全部的NULL值放到非NULL值的前面。同時,對於death值相同的行,將last_name做爲輔助列按姓繼續進行排序。

1.4.9.4 限制查詢結果

查詢結果每每有不少行,若是隻想看到其中的一小部分,那麼能夠在查詢命令裏增長一條LIMIT子句。若是將它與ORDER BY子句聯合使用,效果會特別好。MySQL容許限制查詢輸出的行數,只輸出結果中前面的n行。下面的查詢語句將把按出生日期排在前5位的總統列出來:

mysql> **SELECT last_name, first_name, birth FROM president ** -> **ORDER BY birth LIMIT 5; **+------------+------------+------------+ | last_name | first_name | birth | +------------+------------+------------+ | Washington | George | 1732-02-22 | | Adams | John | 1735-10-30 | | Jefferson | Thomas | 1743-04-13 | | Madison | James | 1751-03-16 | | Monroe | James | 1758-04-28 | +------------+------------+------------+

若是用DESC來逆序排列查詢結果,那麼能夠獲得最晚出生的那5位總統:

mysql> **SELECT last_name, first_name, birth FROM president** -> **ORDER BY birth DESC LIMIT 5; **+-----------+-------------+------------+ | last_name | first_name | birth | +-----------+-------------+------------+ | Obama | Barack H. | 1961-08-04 | | Clinton | William J. | 1946-08-19 | | Bush | George W. | 1946-07-06 | | Carter | James E. | 1924-10-01 | | Bush | George H.W. | 1924-06-12 | +-----------+-------------+------------+

LIMIT子句還容許從查詢結果的中間抽出部分行。此時須要指定兩個值:第一個,給出從查詢結果的開頭部分跳過的行數目;第二個,須要返回的行數目。下面這條查詢語句與前面那條很類似,但它返回的是跳過前面10行以後的5行:

mysql> **SELECT last_name, first_name, birth FROM president ** -> **ORDER BY birth DESC LIMIT 10, 5; **+-----------+-------------+------------+ | last_name | first_name | birth | +-----------+-------------+------------+ | Eisenhower| Dwight D. | 1890-10-14 | | Truman | Harry S | 1884-05-08 | | Roosevelt | Franklin D. | 1882-01-30 | | Hoover | Herbert C. | 1874-08-10 | | Coolidge | Calvin | 1872-07-04 | +-----------+-------------+------------+

若是想從某個表裏隨機抽取出一行或幾行,那麼能夠聯合使用LIMIT子句和ORDER BY RAND()子句:

mysql> **SELECT last_name, first_name FROM president** -> **ORDER BY RAND() LIMIT 1; **+-----------+------------+ | last_name | first_name | +-----------+------------+ | Johnson | Lyndon B. | +-----------+------------+ mysql> **SELECT last_name, first_name FROM president ** -> **ORDER BY RAND() LIMIT 3; **+-----------+-------------+ | last_name | first_name | +-----------+-------------+ | Harding | Warren G. | | Bush | George H.W. | | Jefferson | Thomas | +-----------+-------------+

1.4.9.5 對輸出列進行計算和命名

到目前爲止,大部分查詢語句都是直接經過檢索表中的值來得到輸出結果。MySQL也支持根據表達式的結果計算輸出值,引不引用表均可以。下面的這條查詢語句計算了一個簡單表達式(一個常量)和一個複雜表達式(它使用了幾種算術運算和兩個函數調用,這兩個函數一個用於計算表達式的平方根,另一個用於將結果格式化成保留3位小數):

mysql> **SELECT 17, FORMAT(SQRT(25+13),3); **+----+-----------------------+ | 17 | FORMAT(SQRT(25+13),3) | +----+-----------------------+ | 17 | 6.164 | +----+-----------------------+

在表達式裏也能夠使用表列,以下所示:

mysql> **SELECT CONCAT(first_name,' ',last_name), CONCAT(city,', ',state)** -> **FROM president; **+----------------------------------+-------------------------+ | CONCAT(first_name,' ',last_name) | CONCAT(city,', ',state) | +----------------------------------+-------------------------+ | George Washington | Wakefield, VA | | John Adams | Braintree, MA | | Thomas Jefferson | Albemarle County, VA | | James Madison | Port Conway, VA | ...

這個查詢對總統的名字進行了格式化,將名和姓用空格鏈接成了一個字符串。對他們的出生地也進行了格式,將所在城市和州用逗號和空格鏈接成了一個字符串。

計算某列的值的表達式會成爲該列的名字,並被用做輸出結果的標題。若是表達式很長(如前面那個查詢示例所示),那麼它會使輸出列的寬度變得很大。爲使輸出更具意義和可讀性,你能夠利用AS name結構爲該列分配另外一個名字(也稱「別名」):

mysql> **SELECT CONCAT(first_name,' ',last_name) AS Name,** -> **CONCAT(city,', ',state) AS Birthplace ** -> **FROM president; **+-----------------------+-------------------------+ | Name | Birthplace | +-----------------------+-------------------------+ | George Washington | Wakefield, VA | | John Adams | Braintree, MA | | Thomas Jefferson | Albemarle County, VA | | James Madison | Port Conway, VA | ...

若是輸出列的別名裏包含空格,那麼必須給它加上引號:

mysql> **SELECT CONCAT(first_name,' ',last_name) AS 'President Name',** -> **CONCAT(city,', ',state) AS 'Place of Birth' ** -> **FROM president; **+-----------------------+-------------------------+ | President Name | Place of Birth | +-----------------------+-------------------------+ | George Washington | Wakefield, VA | | John Adams | Braintree, MA | | Thomas Jefferson | Albemarle County, VA | | James Madison | Port Conway, VA | ...

在爲列提供別名時,能夠省略關鍵字AS

mysql> **SELECT 1**** one****, 2 two, 3 three; **+---+-----+-------+ |one| two | three | +---+-----+-------+ | 1 | 2 | 3 | +---+-----+-------+

若是一個查詢結果的列名有誤,或者有列丟失,那麼請檢查在某兩個列之間是否忘了加上逗號。若是真是這種狀況,那麼第二列會被當成第一列的別名。例如,你原打算編寫一個查詢語句,選取下列總統的姓名,惋惜不當心漏掉了first_namelast_name兩列之間的逗號。結果,first_name列被誤命名爲last_name,而列last_name列卻不見了:

mysql> **SELECT first_name last_name FROM president; +---------------+ **| last_name | +---------------+ | George | | John | | Thomas | | James | ...

1.4.9.6 處理日期

在MySQL裏使用日期時,千萬要記住的是年份老是在最前面。在寫2012年7月27日這個日期時,請將其寫成'2012-07-27'。不要像平常生活中那樣,將它寫成'07-27-2012''27-07-2012'。對於其餘格式的輸入值,或許能使用STR_TO_DATE()函數來進行轉換。具體示例請參考3.2.6節。

MySQL支持多種類型的日期運算。

  • 按日期排序(咱們已經操做過屢次)。
  • 搜索特定日期或日期範圍。
  • 從日期值裏提取各組成部分,如年、月或日。
  • 計算兩個日期之間的時間差。
  • 經過將一個日期加上或減去一個時間間隔,計算出另外一個日期。

下面是一些與日期運算有關的查詢示例。

爲了經過確切值或經過另外一日期的相對值,查詢出特定的日期來,能夠將某個DATE列與你感興趣的那個日期值進行比較:

mysql> **SELECT * FROM grade_event WHERE date = '2012-10-01'; **+------------+----------+----------+ | date | category | event_id | +------------+----------+----------+ | 2012-10-01 | T | 6 | +------------+----------+----------+ mysql> **SELECT last_name, first_name, death ** -> **FROM president ** -> **WHERE death >= '1970-01-01' AND death < '1980-01-01'; **+-----------+------------+------------+ | last_name | first_name | death | +-----------+------------+------------+ | Truman | Harry S | 1972-12-26 | | Johnson | Lyndon B. | 1973-01-22 | +-----------+------------+------------+

爲測試或檢索日期的各個部分,能夠使用像YEAR()MONTH()DAYOFMONTH()這樣的函數。例如,下面這個查詢能夠把生於3月的美國總統查找出來:

mysql> **SELECT last_name, first_name, birth ** -> **FROM president WHERE MONTH(birth) = 3;** +-----------+------------+------------+ | last_name | first_name | birth | +-----------+------------+------------+ | Madison | James | 1751-03-16 | | Jackson | Andrew | 1767-03-15 | | Tyler | John | 1790-03-29 | | Cleveland | Grover | 1837-03-18 | +-----------+------------+------------+

也能夠用月份名稱來改寫這個查詢:

mysql> **SELECT last_name, first_name, birth ** -> **FROM president WHERE MONTHNAME(birth) = 'March'; **+-----------+------------+------------+ | last_name | first_name | birth | +-----------+------------+------------+ | Madison | James | 1751-03-16 | | Jackson | Andrew | 1767-03-15 | | Tyler | John | 1790-03-29 | | Cleveland | Grover | 1837-03-18 | +-----------+------------+------------+

再進一步,把函數MONTH()DAYOFMONTH()結合起來使用,找出生於3月某一天的總統:

mysql> **SELECT last_name, first_name, birth ** -> **FROM president WHERE MONTH(birth) = 3 AND DAYOFMONTH(birth) = 29;** +-----------+------------+------------+ | last_name | first_name | birth | +-----------+------------+------------+ | Tyler | John | 1790-03-29 | +-----------+------------+------------+

你偶爾會看到相似「今日名人」之類的信息,用上面的查詢語句就能生成一份這樣的名單。不過,若是你的查詢與「當前日期」有關,那麼大可沒必要像前面的例子那樣插入一個具體的日期值。無論今日是一年裏的哪一天,只要將各位總統的生日與CURDATE()函數(該函數老是返回當前日期)裏的月和日進行比較,即可查出「今日出生的」總統,以下所示:

SELECT last_name, first_name, birth FROM president WHERE MONTH(birth) = MONTH(CURDATE()) AND DAYOFMONTH(birth) = DAYOFMONTH(CURDATE());

若是想知道兩個日期值之間的時間間隔,能夠將它們相減。例如,想要知道哪位總統活得最久,那麼能夠用他們的逝世日期減去出生日期。此時,TIMESTAMPDIFF()函數就派上用場了,由於它有一個參數,能夠指定計算結果的單位(在本例裏爲年,即YEAR):

mysql> **SELECT last_name, first_name, birth, death,** -> **TIMESTAMPDIFF(YEAR, birth, death) AS age ** -> **FROM president WHERE death IS NOT NULL ** -> **ORDER BY age DESC LIMIT 5; **+-----------+------------+------------+------------+------+ | last_name | first_name | birth | death | age | +-----------+------------+------------+------------+------+ | Reagan | Ronald W. | 1911-02-06 | 2004-06-05 | 93 | | Ford | Gerald R. | 1913-07-14 | 2006-12-26 | 93 | | Adams | John | 1735-10-30 | 1826-07-04 | 90 | | Hoover | Herbert C. | 1874-08-10 | 1964-10-20 | 90 | | Truman | Harry S | 1884-05-08 | 1972-12-26 | 88 | +-----------+------------+------------+------------+------+

若是想要計算相差的天數,則須要使用另外一種計算兩個日期之間時間間隔的方法:使用TO_DAYS()函數,將日期轉換爲天數。檢測出與某個參考日期相差有多少天是該函數的一種主要應用。例如,爲找出近期須要更新成員資格的「美史聯盟」成員,能夠將成員的有效日期與當前日期相減。若是其結果小於某個閾值,即代表該成員的資格快要到期了。下面這條查詢語句能夠將成員資格已過時的以及在60天內將到期的成員查找出來:

SELECT last_name, first_name, expiration FROM member WHERE (TO_DAYS(expiration) - TO_DAYS(CURDATE())) < 60;

使用TIMESTAMPDIFF()函數的等效語句,以下所示:

SELECT last_name, first_name, expiration FROM member WHERE TIMESTAMPDIFF(DAY, CURDATE(), expiration) < 60;

要根據某個日期計算出另外一個日期,能夠使用函數DATE_ADD()DATE_SUB()。這兩個函數的參數都是一個日期值和一個時間間隔,而後返回一個新的日期值。例如:

mysql> **SELECT DATE_ADD('1970-1-1', INTERVAL 10 YEAR);** +----------------------------------------+ | DATE_ADD('1970-1-1', INTERVAL 10 YEAR) | +----------------------------------------+ | 1980-01-01 | +----------------------------------------+ mysql> **SELECT DATE_SUB('1970-1-1', INTERVAL 10 YEAR); **+----------------------------------------+ | DATE_SUB('1970-1-1', INTERVAL 10 YEAR) | +----------------------------------------+ | 1960-01-01 | +----------------------------------------+

在本節裏的前面有一個查詢,它選取那些逝世於20世紀70年代的美國總統,使用文字量型的日期值做爲選取範圍的結束點。能夠重寫該查詢語句,使用一個文字量型的起點日期,而後在起點日期上加上一個時間間隔計算得出終點日期:

mysql> **SELECT last_name, first_name, death ** -> **FROM president ** -> **WHERE death >= '1970-1-1' ** -> **AND death < DATE_ADD('1970-1-1', INTERVAL 10 YEAR); **+-----------+------------+------------+ | last_name | first_name | death | +-----------+------------+------------+ | Truman | Harry S | 1972-12-26 | | Johnson | Lyndon B. | 1973-01-22 | +-----------+------------+------------+

另外還有一種編寫「成員資格更新」查詢的方法,即便用DATE_ADD()函數:

SELECT last_name, first_name, expiration FROM member WHERE expiration < DATE_ADD(CURDATE(), INTERVAL 60 DAY);

若是expiration列通過了索引,那麼這個查詢將會比前一個更有效率。具體緣由請參考第5章。

大概在本章開頭,有下面這樣一個查詢,用於找出「在牙醫診所的患者中,有哪些患者在到期後還將來複診」:

SELECT last_name, first_name, last_visit FROM patient WHERE last_visit < DATE_SUB(CURDATE(), INTERVAL 6 MONTH);

或許當時你還不太能看懂這個查詢。那麼如今是否已徹底明白了呢?

1.4.9.7 模式匹配

MySQL支持模式匹配操做,這使咱們可以在未給出精確比較值的狀況下把行查出來。模式匹配須要使用像LIKENOT LIKE那樣的運算符,而且須要指定一個包含通配字符的字符串。下劃線「_」能夠匹配任何的單個字符,百分號「%」則能匹配任何字符序列(其中包括空序列)。

下面這個模式能匹配到全部以字母Ww開頭的姓:

mysql> **SELECT last_name, first_name FROM president ** -> **WHERE last_name LIKE 'W%'; **+------------+------------+ | last_name | first_name | +------------+------------+ | Washington | George | | Wilson | Woodrow | +------------+------------+

下面這個查詢展現了一種常見錯誤。這個「模式匹配」不會返回任何內容,由於它沒有使用帶LIKE的模式,而是使用了帶算術比較運算符的模式。

mysql> **SELECT last_name, first_name FROM president** -> **WHERE last_name = 'W%'; **Empty set (0.00 sec)

上面這個比較表達式惟一成功的地方是該列正好包含了字符串'W%''w%'

下面這個模式將與那些包含有'W%''w%'(並不只限於第一個字母)的姓相匹配:

mysql> **SELECT last_name, first_name FROM president** -> **WHERE last_name LIKE '%W%'; **+------------+------------+ | last_name | first_name | +------------+------------+ | Washington | George | | Wilson | Woodrow | | Eisenhower | Dwight D. | +------------+------------+

下面這個模式將與剛好包含4個字母的姓相匹配,以下所示:

mysql> **SELECT last_name, first_name FROM president ** -> **WHERE last_name LIKE '____'; **+-----------+-------------+ | last_name | first_name | +-----------+-------------+ | Polk | James K. | | Taft | William H. | | Ford | Gerald R. | | Bush | George H.W. | | Bush | George W. | +-----------+-------------+

MySQL還提供另外一種基於正則表達式(regular expression)和REGEXP運算符的模式匹配。關於LIKEREGEXP的更多信息請參考3.5.1.1節和附錄C。

1.4.9.8 設置和使用自定義變量

MySQL支持你定義本身的變量。這些變量能夠被設置爲查詢結果,這使咱們能夠方便地把一些值存儲起來以供從此的查詢使用。假設想知道在Andrew Jackson總統以前出生的總統有哪些。能夠先將他的出生日期檢索出來,並存儲到一個變量裏,而後再將出生日期早於該變量值的其餘總統查找出來:

mysql> **SELECT @ Jackson********_********birth := birth FROM president ** -> **WHERE last_name = 'Jackson' AND first_name = 'Andrew'; **+------------------------+ | @jackson******_******birth := birth | +------------------------+ | 1767-03-15 | +------------------------+ mysql> **SELECT last_name, first_name, birth FROM president ** -> **WHERE birth < @****jackson********_********birth ORDER BY birth; **+------------+------------+------------+ | last_name | first_name | birth | +------------+------------+------------+ | Washington | George | 1732-02-22 | | Adams | John | 1735-10-30 | | Jefferson | Thomas | 1743-04-13 | | Madison | James | 1751-03-16 | | Monroe | James | 1758-04-28 | +------------+------------+------------+

自定義變量的語法爲「@變量名」。在SELECT語句裏,賦值語法是形如「@變量名:=值」的表達式。所以,上面的第一個查詢主要負責把Andrew Jackson總統的出生日期查出來,並把它賦給一個名爲@jackson_birth的變量。這條SELECT語句的查詢結果仍會被顯示出來;而將查詢結果賦給變量的過程並不會阻礙該查詢的輸出顯示。第二個查詢會引用該變量,並在president表裏找出birth值小於該變量值的那些行。

實際上,前面的那個問題能夠經過一條使用鏈接或子查詢的查詢語句來解決,但這裏不想對此有過多的討論。有時,使用一個變量可能會更容易讓人理解。更多關於子查詢的信息請參考1.4.9.10節。

也能夠使用SET`語句來對變量進行賦值。此時,「=」和「:=」均可以用做賦值運算符:

mysql>** SET @today = CURDATE(); **mysql>** SET @one_week_ago := DATE_SUB(@today, INTERVAL 7 DAY); **mysql>** SELECT @today, @one_week_ago; **+------------+---------------+ | @today | @one_week_ago | +------------+---------------+ | 2012-04-21 | 2012-04-14 | +------------+---------------+

1.4.9.9 生成統計信息

MySQL最有用的一項功能就是,可以對大量原始數據進行概括和統計。你們都明白,單純依靠人工手段來生成統計信息是一項既枯燥耗時,又易出錯的工做。若是你們能學會使用MySQL來生成各類統計信息,那麼它將會成爲你的得力助手。

在一組值裏把各個惟一值找出來,這是一項典型的統計工做。能夠使用DISTINCT關鍵字清除查詢結果裏重複出現的行。例如,下面的查詢能夠將美國曆任總統出生地所在的州不加劇復地列舉出來:

mysql> **SELECT DISTINCT state FROM president ORDER BY state;** +-------+ | state | +-------+ | AR | | CA | | CT | | GA | | HI | | IA | | IL | | KY | | MA | | MO | ...

另外一種形式的統計是計數,須要使用COUNT()函數。若是使用COUNT(*),那麼計算出來的結果將是查詢所選取到的行數。若是查詢語句沒有帶WHERE子句,那麼它會查詢全部行,所以,COUNT(*)計算出來的結果就是表的行數。下面這個查詢能夠顯示出「美史聯盟」的member表裏包含多少行:

mysql> **SELECT COUNT(*) FROM member; **+----------+ | COUNT(*) | +----------+ | 102 | +----------+

若是查詢語句帶有WHERE子句,那麼COUNT(*)計算出來的結果就是該子句匹配到了多少行。下面這個查詢能夠顯示出「到目前爲止,你的班級已進行了多少次測驗」:

mysql> **SELECT COUNT(*) FROM grade_event WHERE category = 'Q'; **+----------+ | COUNT(*) | +----------+ | 4 | +----------+

COUNT(*)函數會統計全部被查詢到的行數。與之相對的是,COUNT(列名)只會統計全部非NULL值的數目。下面這個查詢展現了兩個函數的區別:

mysql> **SELECT COUNT(*), COUNT(email), COUNT(expiration) FROM member; **+----------+--------------+-------------------+ | COUNT(*) | COUNT(email) | COUNT(expiration) | +----------+--------------+-------------------+ | 102 | 80 | 96 | +----------+--------------+-------------------+

從上面的查詢結果能夠看出, member表目前共有102行,其中只有80行在email列裏有值。它同時還顯示出有6名成員具備終身成員資格。(expiration列裏的NULL值表示具備終身成員資格,而且因爲在102條記錄裏有96條不爲NULL,所以剩下的6條必然屬於終身成員。)

組合使用COUNT()與DISTINCT,能夠統計出在查詢結果裏有多少個不一樣的非NULL值。例如,想要知道美國共有多少個不一樣的州曾經誕生過總統,那麼能夠使用下面這條查詢語句:

mysql> **SELECT COUNT(DISTINCT state) FROM president; **+-----------------------+ | COUNT(DISTINCT state) | +-----------------------+ | 21 | +-----------------------+

你能夠對某個數據列進行全面統計,也能夠對該列進行分類統計。例如,使用下面這個查詢,能夠肯定出班級裏總共有多少名學生:

mysql> **SELECT COUNT(*) FROM student; **+----------+ | COUNT(*) | +----------+ | 31 | +----------+

不過,班級裏的男生和女生分別是多少呢?有一種辦法能夠找到答案,即按性別分別進行統計:

mysql> **SELECT COUNT(*) FROM student WHERE sex='f'; **+----------+ | COUNT(*) | +----------+ | 15 | +----------+ mysql> **SELECT COUNT(*) FROM student WHERE sex='m'; **+----------+ | COUNT(*) | +----------+ | 16 | +----------+

這個辦法可行,但比較麻煩,並且很不適合於有多種不一樣列值的情形。假設如何採用這種方式來肯定出生自美國各個州的總統人數。首先,你必須一個很多地把涉及的州所有找出來(使用查詢語句SELECT DISTINCT state FROM president),而後,再針對各州執行查詢語句SELECT COUNT(*)來統計出最終結果。顯然這是你想避免的事情。

幸運的是,只用一個查詢即可以統計出某一列裏的不一樣值分別出現過多少次。對於那個學生列表,能夠使用GROUP BY子句來分別統計男、女學生的人數,以下所示:

mysql> **SELECT sex, COUNT(*) FROM student GROUP BY sex; **+-----+----------+ | sex | COUNT(*) | +-----+----------+ | F | 15 | | M | 16 | +-----+----------+

使用一樣的查詢形式,能夠分別統計出生自各州的總統人數,以下所示:

mysql> **SELECT state, COUNT(*) FROM president GROUP BY state; **+-------+----------+ | state | COUNT(*) | +-------+----------+ | AR | 1 | | CA | 1 | | CT | 1 | | GA | 1 | | HI | 1 | | IA | 1 | | IL | 1 | | KY | 1 | | MA | 4 | | MO | 1 | ...

在採用這種方式進行分組統計時, GROUP BY子句會告知MySQL在統計以前應該如何對值進行分組。

與分別統計某列的不一樣值所出現次數的作法相比,將COUNT(*)函數與GROUP BY子句結合在一塊兒用於進行分組統計的作法有不少優勢。

  • 不用事先知道被統計列裏有些什麼值。
  • 只需一個查詢語句。
  • 由於只用一個查詢便能得到全部的結果,因此能夠對輸出進行排序。

前兩項優勢的重要性體如今:它們有助於簡化查詢語句的表達。第3項優勢的重要性則體如今:它能以更靈活的方式顯示查詢結果。默認狀況下,MySQL會根據GROUP BY子句裏的列名來對查詢結果進行排序,但你也能夠用ORDER BY子句指定一個特定的排序順序。例如,你想得到按出生地所在州分組後的總統人數,並按人數從多到少的順序排列出來,因而能夠多加一個ORDER BY子句,以下所示:

mysql> **SELECT state, COUNT(*) AS count FROM president ** -> **GROUP BY state ORDER BY count DESC; **+-------+-------+ | state | count | +-------+-------+ | VA | 8 | | OH | 7 | | MA | 4 | | NY | 4 | | NC | 2 | | VT | 2 | | TX | 2 | | GA | 1 | | IL | 1 | | SC | 1 | ...

若是用於排序的列是由某個彙總函數產生的,那麼不能直接在ORDER BY子句裏引用該函數。而是應該先爲該列取一個別名,而後再在ORDER BY子句裏引用這個別名。上面那個查詢就是這樣作的,其中的COUNT(*)列的別名爲count

要使用GROUP BY子句來對某個計算列的結果進行分組,須要使用別名或列位置來引用它,具體實現方法與ORDER BY相相似。下面這個查詢能夠肯定出在一年的每月分別有多少位總統出生:

mysql> **SELECT MONTH(birth) AS Month, MONTHNAME(birth) AS Name,** -> **COUNT(*) AS count ** -> **FROM president GROUP BY Name ORDER BY Month; **+-------+-----------+-------+ | Month | Name | count | +-------+-----------+-------+ | 1 | January | 4 | | 2 | February | 4 | | 3 | March | 4 | | 4 | April | 4 | | 5 | May | 2 | | 6 | June | 1 | | 7 | July | 4 | | 8 | August | 5 | | 9 | September | 1 | | 10 | October | 6 | | 11 | November | 5 | | 12 | December | 3 | +-------+-----------+-------+

COUNT()函數能夠與ORDER BYLIMIT組合在一塊兒使用。例如,想要在president表裏找出哪4個州出生的總統最多,那麼能夠使用下面這條查詢語句:

mysql> **SELECT state, COUNT(*) AS count FROM president ** -> **GROUP BY state ORDER BY count DESC LIMIT 4; **+-------+-------+ | state | count | +-------+-------+ | VA | 8 | | OH | 7 | | MA | 4 | | NY | 4 | +-------+-------+

若是不是想用LIMIT子句來限制查詢結果中的記錄條數,而是想把COUNT()的某些特定值找出來,那麼須要用到HAVING子句。該子句與WHERE相相似,它們均可用來設定輸出行所必須知足的查詢條件。與WHERE子句的不一樣之處在於,它能夠引用像COUNT()那樣的彙總函數輸出的結果。下面這個查詢會告訴你「哪些州曾經出現過兩位及以上的總統」:

mysql> **SELECT state, COUNT(*) AS count FROM president ** -> **GROUP BY state HAVING count > 1 ORDER BY count DESC;** +-------+-------+ | state | count | +-------+-------+ | VA | 8 | | OH | 7 | | MA | 4 | | NY | 4 | | NC | 2 | | VT | 2 | | TX | 2 | +-------+-------+

通常狀況下,帶有HAVING子句的查詢語句,特別適合於查找在某個數據列裏重複出現的值。也可用於查找不重複出現的值,此時使用HAVING count = 1便可。

COUNT()之外,還有其餘幾個彙總函數。函數MIN()MAX()SUM()AVG()可分別用於肯定某個數據列的最小值、最大值、總計和平均值。你甚至能夠同時在一個查詢語句裏使用它們。下面這個查詢能夠顯示出已進行過的每次考試或測驗的各類數值特徵。它也會顯示出有多少分數參與了各個值的計算。(可能有的學生缺勤或未被統計。)

mysql> **SELECT** -> **event_id,** -> **MIN(score) AS minimum,** -> **MAX(score) AS maximum,** -> **MAX(score)-MIN(score)+1 AS span,** -> **SUM(score) AS total,** -> **AVG(score) AS average,** -> **COUNT(score) AS count** -> **FROM score** -> **GROUP BY event_id;** +----------+---------+---------+------+-------+---------+-------+ | event_id | minimum | maximum | span | total | average | count | +----------+---------+---------+------+-------+---------+-------+ | 1 | 9 | 20 | 12 | 439 | 15.1379 | 29 | | 2 | 8 | 19 | 12 | 425 | 14.1667 | 30 | | 3 | 60 | 97 | 38 | 2425 | 78.2258 | 31 | | 4 | 7 | 20 | 14 | 379 | 14.0370 | 27 | | 5 | 8 | 20 | 13 | 383 | 14.1852 | 27 | | 6 | 62 | 100 | 39 | 2325 | 80.1724 | 29 | +----------+---------+---------+------+-------+---------+-------+

很明顯,若是從中還能明確地知道event_id列的值是表示考試仍是表示測驗,那麼這些信息會更具備意義。想要得到該信息,還須要查詢grade_``event表。關於此操做的更多細節請參考1.4.9.10節。

若是想要生成額外的輸出行,顯示出「統計結果的統計」,那麼還須要增長一條WITH ROLLUP子句。它會讓MySQL計算各分組行的「超集」(super-aggregate)值。這裏有個簡單的示例,它是基於先前那個按性別統計學生人數的示例改進的。WITH ROLLUP子句將生成另一行,對兩類性別的人數進行彙總:

mysql> **SELECT sex, COUNT(*) FROM student GROUP BY sex WITH ROLLUP; **+-----+----------+ | sex | COUNT(*) | +-----+----------+ | F | 15 | | M | 16 | | NULL| 31 | +-----+----------+

分組列裏的NULL代表,相應的計數結果就是其前面那些分組統計的彙總值。

WITH ROLLUP子句還能夠與其餘彙集函數搭配使用。下面這條語句,除了像前面幾個段落那樣能夠對考試成績進行了彙總之外,還能夠產生一個額外的超集行:

mysql> **SELECT ** -> **event_id, ** -> **MIN(score) AS minimum, ** -> **MAX(score) AS maximum, ** -> **MAX(score)-MIN(score)+1 AS span, ** -> **SUM(score) AS total, ** -> **AVG(score) AS average, ** -> **COUNT(score) AS count ** -> **FROM score ** -> **GROUP BY event_id WITH ROLLUP;**** **+----------+---------+---------+------+-------+---------+-------+ | event_id | minimum | maximum | span | total | average | count | +----------+---------+---------+------+-------+---------+-------+ | 1 | 9 | 20 | 12 | 439 | 15.1379 | 29 | | 2 | 8 | 19 | 12 | 425 | 14.1667 | 30 | | 3 | 60 | 97 | 38 | 2425 | 78.2258 | 31 | | 4 | 7 | 20 | 14 | 379 | 14.0370 | 27 | | 5 | 8 | 20 | 13 | 383 | 14.1852 | 27 | | 6 | 62 | 100 | 39 | 2325 | 80.1724 | 29 | | NULL | 7 | 100 | 94 | 6376 | 36.8555 | 173 | +----------+---------+---------+------+-------+---------+-------+

在上面這個輸出裏,最後一行顯示出了一些彙集值,它們都是根據其前面的所有分組統計值計算出來的。

WITH ROLLUP子句頗有用,由於它可讓你沒必要爲了得到一些額外的信息,而執行另一條查詢語句。只用一條查詢語句就能達到目的,固然效率會更高,由於服務器無需對數據進行兩次檢查。若是GROUP BY子句指定了多列,那麼WITH ROLLUP還會再生成其餘的超集行,其中會包含更高層的彙總值。

1.4.9.10 從多個表裏檢索信息

到目前爲止,咱們查詢出來的信息都是來自一個表。不過,MySQL的能耐遠不止於此。前面說過,DBMS的威力在於它們能夠把源自多個表的信息結合起來,從而解答那些只靠單個表而沒法解答的問題。本節將介紹如何編寫涉及多個表的查詢語句。

在從多個表中查詢信息時,有一種類型的操做叫鏈接(join)。之因此叫這個名字,是由於必須把一個表與另外一個表中的信息鏈接起來才能獲得結果。此操做是經過匹配多個表裏的公共值實現的。另外一種類型的多表操做是將一條SELECT語句嵌套在另外一條SELECT語句裏使用。這種嵌套的SELECT語句叫子查詢(subquery)。本節將對這兩種類型的操做進行介紹。

先一塊兒來看一個關於鏈接的例子。1.4.6.2節給出了一個用來檢索給定日期考試或測驗分數的查詢命令,但在那裏並未對它進行解釋。如今能夠對它進行解釋了。那條查詢語句實際上涉及了一個三方的鏈接操做,所以咱們將它分紅兩步來實現。第一步,構造一條能夠查出給定日期的分數的查詢語句:

mysql> **SELECT student_id, date, score, category ** -> **FROM grade_event INNER JOIN score ** -> **ON grade_event.event_id = score.event_id ** -> **WHERE date = '2012-09-23'; **+------------+------------+-------+----------+ | student_id | date | score | category | +------------+------------+-------+----------+ | 1 | 2012-09-23 | 15 | Q | | 2 | 2012-09-23 | 12 | Q | | 3 | 2012-09-23 | 11 | Q | | 5 | 2012-09-23 | 13 | Q | | 6 | 2012-09-23 | 18 | Q | ...

這個查詢先查出給定日期('20``12``-09-23' )的grade_``event行,再利用此行裏的事件ID把score表裏擁有同一事件ID的分數都查詢出來。對於grade_``eventscore兩個表裏相匹配的每個行組合,把其中的學生ID、分數、日期和事件類別都顯示出來。

這個查詢與以前介紹的查詢語句在如下兩個方面有着顯著的區別。

  • FROM子句指定了多個表名,由於須要從多個表裏檢索信息:
FROM grade_event INNER JOIN score
  • ON子句指定了表grade_``eventscore的鏈接條件,即這兩個表的event_id值必須相互匹配:
ON grade_event.event_id = score.event_id

請注意,咱們是如何經過grade_event.event_idscore.event_id來引用event_id列的,其語法形式爲:tbl_name.col_name。這樣,MySQL便能知道咱們是在引用哪一個表。由於這兩個表都有event_id列,因此在不限定表名時,會產生二義性。這條查詢語句裏的其餘列(datescorecategory)能夠直接使用,不用限定表名,由於它們只存在於其中的一個表裏,不會產生二義性。

在鏈接語句裏,我我的比較喜歡在每一個列的前面都加上表名,從而可讓每一列屬於哪一個表變得更加清楚。在後面的鏈接語句裏,我將一直沿用這個習慣。在爲每列加上完整的表名以後,這個查詢如今變成了下面這個樣子:

SELECT score.student_id, grade_event.date, score.score, grade_event.category FROM grade_event INNER JOIN score ON grade_event.event_id = score.event_id WHERE grade_event.date = '2012-09-23';

在第一階段的查詢裏,咱們利用grade_event表將日期映射到了事件ID,並使用這個事件ID在score表裏找到了與之匹配的分數。這個查詢的輸出只包含了student_id值,可是若是能把學生的姓名直接顯示出來則會更具意義。第二階段,咱們將利用student表,把學生ID映射成他們的姓名。score表和student表都有student_id列,經過它能夠將兩個表的各個行連接起來,顯示出學生的名字。最終的查詢語句以下所示:

mysql> **SELECT ** -> **student.name, grade_event.date, score.score, grade_event.category ** -> **FROM grade_event INNER JOIN score INNER JOIN student ** -> **ON grade_event.event_id = score.event_id ** -> **AND score.student_id = student.student_id ** -> **WHERE grade_event.date = '2012-09-23'; **+-----------+------------+-------+----------+ | name | date | score | category | +-----------+------------+-------+----------+ | Megan | 2012-09-23 | 15 | Q | | Joseph | 2012-09-23 | 12 | Q | | Kyle | 2012-09-23 | 11 | Q | | Abby | 2012-09-23 | 13 | Q | | Nathan | 2012-09-23 | 18 | Q | ...

這個查詢與之前介紹的查詢命令在如下幾方面有區別。

  • FROM子句如今包含了student表,由於這條查詢語句除了要用到grade_``event表和score表之外,還須要用到它。
  • 在前一個查詢裏,student_id列不會產生二義性,所以在引用它時,既能夠不限定表名(student_id),也能夠限定表名(score.student_id)。但在這個查詢裏,由於score表和student表都有student_id列,因此確定會出現二義性。因而,爲了不產生二義性,必須將它們分別限定爲score.student_idstudent.student_id
  • ON子句裏多了一個查詢條件,用於指定score表裏的行與student表裏的行必須基於學生ID匹配在一塊兒:
ON ... score.student_id = student.student_id
  • 這個查詢會顯示出學生的姓名,而不顯示學生的ID。(若是想要二者都顯示,只須要在輸出列的列表里加上student.student_id便可。)

對於這個查詢,只要插入任何日期,便可得到那天對應的分數、參加考試的學生姓名,以及考試的類別。你根本不用瞭解學生ID和事件ID,由於MySQL會自動查出有關的ID值並利用它們把你想要的信息找出來。

在前面的1.4.9.9節,咱們運行了一個查詢,它能夠將score表裏各種數據的數值特徵統計出來。在那個查詢的輸出結果裏,只列出了事件ID,而沒有列出事件日期或類別,由於咱們當時還不知道如何將score錶鏈接到grade_event表上,將事件ID映射到考試日期和類別上去。如今,咱們來實現這一效果。下面這個查詢與前面那個相差無幾,但它顯示出來的是考試日期和類別,而不僅是一些數字形式的事件ID:

mysql> **SELECT ** -> **grade_event.date,grade_event.category, ** -> **MIN(score.score) AS minimum, ** -> **MAX(score.score) AS maximum, ** -> **MAX(score.score)-MIN(score.score)+1 AS span,** -> **SUM(score.score) AS total, ** -> **AVG(score.score) AS average, ** -> **COUNT(score.score) AS count ** -> **FROM score INNER JOIN grade_event ** -> **ON score.event_id = grade_event.event_id ** -> **GROUP BY grade_event.date; **+------------+----------+---------+---------+------+-------+---------+-------+ | date | category | minimum | maximum | span | total | average | count | +------------+----------+---------+---------+------+-------+---------+-------+ | 2012-09-03 | Q | 9 | 20 | 12 | 439 | 15.1379 | 29 | | 2012-09-06 | Q | 8 | 19 | 12 | 425 | 14.1667 | 30 | | 2012-09-09 | T | 60 | 97 | 38 | 2425 | 78.2258 | 31 | | 2012-09-16 | Q | 7 | 20 | 14 | 379 | 14.0370 | 27 | | 2012-09-23 | Q | 8 | 20 | 13 | 383 | 14.1852 | 27 | | 2012-10-01 | T | 62 | 100 | 39 | 2325 | 80.1724 | 29 | +------------+----------+---------+---------+------+-------+---------+-------+

雖然GROUP BY列帶有限定符,但對於這條查詢語句來講並非必須的。GROUP BY引用了多個輸出列,不過名叫date的列只有一個,所以MySQL能夠清楚地知道你所指的是哪個。

即便有源自多個表的多個列,你也能夠使用像COUNT()AVG()這樣的函數,爲它們生成彙總信息。下面這個查詢能夠爲事件日期與學生性別的每種組合,肯定出各分數的數目,以及平均分數:

mysql> **SELECT grade_event.date, student.sex, ** -> **COUNT(score.score) AS count, AVG(score.score) AS average ** -> **FROM grade_event INNER JOIN score INNER JOIN student ** -> **ON grade_event.event_id = score.event_id ** -> **AND score.student_id = student.student_id ** -> **GROUP BY grade_event.date, student.sex; **+------------+-----+-------+---------+ | date | sex | count | average | +------------+-----+-------+---------+ | 2012-09-03 | F | 14 | 14.6429 | | 2012-09-03 | M | 15 | 15.6000 | | 2012-09-06 | F | 14 | 14.7143 | | 2012-09-06 | M | 16 | 13.6875 | | 2012-09-09 | F | 15 | 77.4000 | | 2012-09-09 | M | 16 | 79.0000 | | 2012-09-16 | F | 13 | 15.3077 | | 2012-09-16 | M | 14 | 12.8571 | | 2012-09-23 | F | 12 | 14.0833 | | 2012-09-23 | M | 15 | 14.2667 | | 2012-10-01 | F | 14 | 77.7857 | | 2012-10-01 | M | 15 | 82.4000 | +------------+-----+-------+---------+

咱們能夠用一條相似的查詢語句來完成成績考評項目的其中一項任務,即在期末的時候計算每一個學生的總成績:

SELECT student.student_id, student.name, SUM(score.score) AS total, COUNT(score.score) AS n FROM grade_event INNER JOIN score INNER JOIN student ON grade_event.event_id = score.event_id AND score.student_id = student.student_id GROUP BY score.student_id ORDER BY total;

成績考評項目的另外一項任務是彙總全部學生的缺勤狀況。全部缺勤狀況都記錄在absence表裏,其中包括學生ID和日期。爲得到學生的名字(不僅是ID),咱們必須基於student_id的值,將absence錶鏈接到student表上。下面這個查詢能夠列出學生的ID號和姓名,以及缺勤狀況:

mysql> **SELECT student.student_id, student.name, ** -> **COUNT(absence.date) AS absences ** -> **FROM student INNER JOIN absence ** -> **ON student.student_id = absence.student_id ** -> **GROUP BY student.student_id; **+------------+-------+----------+ | student_id | name | absences | +------------+-------+----------+ | 3 | Kyle | 1 | | 5 | Abby | 1 | | 10 | Peter | 2 | | 17 | Will | 1 | | 20 | Avery | 1 | +------------+-------+----------+

若是你只想知道有哪些學生缺勤,那麼這個查詢輸出已能知足須要。若是想要把這個列表交到學校辦公室,那麼他們可能會問:「其餘學生的狀況怎麼樣呢?咱們但願看到每位學生的狀況。」這就是個有所不一樣的問題了。它既要求統計缺勤學生的數量,也要求統計完好勤狀況學生的數量。既然這個問題有所不一樣,那麼回答這個問題的查詢也就會有所不一樣。

使用內鏈接(inner join)來回答此問題並不合適,咱們須要使用LEFT JOIN子句。該子句會告知MySQL,對於鏈接裏的第一個表(即LEFT JOIN關鍵字左邊的那個表),爲從其裏面查詢出的每一行產生一個輸出行。經過將student表指定爲第一個表,咱們將能得到每位學生的輸出,其中甚至包括那些absence表裏沒有的學生。在編寫此查詢語句時,能夠在FROM子句裏的兩個表之間使用LEFT JOIN(而不是用逗號把這兩個表分開),而後使用ON子句說明如何匹配這兩個表中的行。此查詢以下所示:

mysql> **SELECT student.student_id, student.name, ** -> **COUNT(absence.date) AS absences ** -> **FROM student LEFT JOIN absence ** -> **ON student.student_id = absence.student_id ** -> **GROUP BY student.student_id; **+------------+-----------+----------+ | student_id | name | absences | +------------+-----------+----------+ | 1 | Megan | 0 | | 2 | Joseph | 0 | | 3 | Kyle | 1 | | 4 | Katie | 0 | | 5 | Abby | 1 | | 6 | Nathan | 0 | | 7 | Liesl | 0 | ...

鏈接操做並不是只能用於兩個不一樣的表。這乍聽起來有點兒奇怪,但你徹底能夠把某個表與其自身鏈接起來。例如,想肯定是否有某位總統與另外一位總統出生在同一個城市,這時便須要檢查每位總統的出生地與其餘總統的出生地是否一致:

mysql> **SELECT p1.last_name, p1.first_name, p1.city, p1.state ** -> **FROM president AS p1 INNER JOIN president AS p2 ** -> **ON p1.city = p2.city AND p1.state = p2.state ** -> **WHERE (p1.last_name <> p2.last_name OR p1.first_name <> p2.first_name) ** -> **ORDER BY state, city, last_name; **+-----------+-------------+-----------+-------+ | last_name | first_name | city | state | +-----------+-------------+-----------+-------+ | Adams | John Quincy | Braintree | MA | | Adams | John | Braintree | MA | +-----------+-------------+-----------+-------+

這條查詢命令有如下兩個地方須要特別注意。

  • 它須要引用同一個表中的兩個實例,所以咱們必須爲它建立兩個別名(p1p2),並用它們來將表中的同名列區別開來。因爲列已有別名,因此在爲表指定別名時, AS關鍵字就是可選項了。
  • 每位總統的記錄都與其自己相匹配,但這並非咱們想要的輸出結果。在確保參與比較的總統名字都不相同的狀況下, WHERE子句便能防止出現「行與其自己相匹配」的狀況。

用一個相似的查詢能夠查出在同月同日出生的總統。不過,若是直接比較某兩位總統的出生日期,那麼查詢結果裏就會缺乏那些生於同月同日但不一樣年的總統。所以,咱們必須用函數MONTH()DAYOF``M``ONTH()來比較出生日期裏的月和日:

mysql> **SELECT p1.last_name, p1.first_name, p1.birth ** -> **FROM president AS p1 INNER JOIN president AS p2 ** -> **WHERE MONTH(p1.birth) = MONTH(p2.birth) ** -> **AND DAYOFMONTH(p1.birth) = DAYOFMONTH(p2.birth) ** -> **AND (p1.last_name <> p2.last_name OR p1.first_name <> p2.first_name) ** -> **ORDER BY p1.last_name; **+-----------+------------+------------+ | last_name | first_name | birth | +-----------+------------+------------+ | Harding | Warren G. | 1865-11-02 | | Polk | James K. | 1795-11-02 | +-----------+------------+------------+

使用DAYOFYEAR()來代替MONTH()DAYOF``M``ONTH()的組合,能夠獲得一個稍微簡單一點兒的查詢語句。但其查詢結果卻可能不正確,由於它沒有考慮到出現閏年的狀況。

另外一種類型的多表檢索操做是使用「子查詢」,即把一條SELECT語句嵌套在另外一條SELECT語句裏。子查詢有幾種類型,詳細內容將在2.9節進行討論。咱們如今只看兩個示例。假設須要把全勤的學生都找出來。此要求等價於把沒在absence表裏出現過的學生都找出來,所以咱們能夠這樣作:

mysql> **SELECT * FROM student ** -> **WHERE student_id NOT IN (SELECT student_id FROM absence); **+-----------+-----+------------+ | name | sex | student_id | +-----------+-----+------------+ | Megan | F | 1 | | Joseph | M | 2 | | Katie | F | 4 | | Nathan | M | 6 | | Liesl | F | 7 | ...

嵌套於內層的SELECT語句會肯定出absence表裏的student_id值集合,而外層的那個SELECT語句則會檢索出student表裏與該集合中的ID值都不匹配的那些行。

子查詢還能爲1.4.9.8節提出的那個問題(即有哪些總統出生在Andrew Jackson總統以前)提供一種單語句解決方案。當時的解決方案是使用兩條語句和一個用戶變量,而如今能夠用一條子查詢語句來解決,以下所示:

mysql> **SELECT last_name, first_name, birth FROM president ** -> **WHERE birth < (SELECT birth FROM president ** -> **WHERE last_name = 'Jackson' AND first_name = 'Andrew');** +------------+------------+------------+ | last_name | first_name | birth | +------------+------------+------------+ | Washington | George | 1732-02-22 | | Adams | John | 1735-10-30 | | Jefferson | Thomas | 1743-04-13 | | Madison | James | 1751-03-16 | | Monroe | James | 1758-04-28 | +------------+------------+------------+

內層的SELECT語句用來肯定Andrew Jackson的出生日期,外層SELECT語句負責檢索出生日期早於該日期的總統。

1.4.10 刪除或更新已有行

有時候,你會須要刪除某些行,或者修改其內容。這時便須要用到DELETE語句和UPDATE語句。

DELETE語句的基本格式以下所示:

DELETE FROM tbl_name WHERE whichrows to delete_;_

其中,WHERE子句是可選的,它用於指定須要刪除掉哪些行。若是沒有WHERE子句,那麼將刪除表裏的全部行。這意味着,越簡單的DELETE語句越危險,例以下面這條語句:

DELETE FROM tbl_name_;_

它會完全刪除表裏的全部內容,所以請務必當心使用!若是隻想刪除特定的行,那麼必須先使用WHERE子句把它們篩選出來。這種作法與在SELECT語句裏使用WHERE子句來避免查詢出整個表的作法相相似。例如,在president表裏,只想將出生於Ohio的美國總統刪除掉,那麼能夠使用下面這條查詢語句:

mysql> **DELETE FROM president WHERE state='OH'; **Query OK, 7 rows affected (0.01 sec)

若是你不太清楚某條DELETE語句到底會刪除哪些行,那麼最好先把這條語句的WHERE子句放到一條SELECT語句裏,看看這條SELECT語句能查出哪些行。這有助於你確認:它們的確是你想要刪除的那些行,並且只有這些,不能多也不能少。假設想要刪除Teddy Roosevelt總統對應的那一行。那麼下面這條語句是否能完成此項工做呢?

DELETE FROM president WHERE last_name='Roosevelt';

這條語句確實能刪除掉Teddy Roosevelt總統對應的那一行,但它同時也會刪除掉Franklin Roosevelt總統對應的那一行。所以,出於安全的考慮,最好先把這個WHERE子句放到SELECT語句裏檢查一下,就像這樣:

mysql> **SELECT last_name, first_name FROM president ** -> **WHERE last_name='Roosevelt'; **+-----------+-------------+ | last_name | first_name | +-----------+-------------+ | Roosevelt | Theodore | | Roosevelt | Franklin D. | +-----------+-------------+

從上面的查詢結果能夠看出,還須要在條件里加上總統的名,從而讓它更具體一點:

mysql> **SELECT last_name, first_name FROM president ** -> **WHERE last_name='Roosevelt' AND first_name='Theodore'; **+-----------+------------+ | last_name | first_name | +-----------+------------+ | Roosevelt | Theodore | +-----------+------------+

如今,你應該知道如何使用WHERE子句正確地標識那些想要刪除的行了。下面是更正後的DELETE語句:

mysql> **DELETE FROM president ** -> **WHERE last_name='Roosevelt' AND first_name='Theodore';**

表面看起來彷佛是爲了刪除一行而輸入了不少的內容,但事實上穩妥一點老是賽過過後來後悔。若是真的遇到這種狀況,則能夠經過複製粘貼或者輸入行編輯(line-editing)技術,儘量減小輸入操做。更多相關信息請參考1.5節。

若是想修改已有記錄行,則須要使用UPDATE語句,其基本格式以下:

UPDATE tbl_name SET which columns to change WHERE which rows to update_;_

這裏WHERE子句的狀況與DELETE語句裏的類似。它是可選的,所以在沒有指定它時,會更新表裏的每一行。例如,下面這個查詢會把每一位學生的名字都更改成George:

mysql> **UPDATE student SET name='George';**

很顯然,必須謹慎對待這類查詢,因此一般狀況下都須要加上一個WHERE子句,用它來更具體地指出須要更新哪些行。假設你最近往「美史聯盟」裏新增了一名成員,但只填寫了不多的幾欄信息:

mysql> **INSERT INTO member (last_name,first_name)** -> **VALUES('York','Jerome');**

接着,你發現本身忘了爲他設置成員資格有效日期。此時,能夠用一條UPDATE語句來進行修復,其中包含一條WHERE子句,用以標識須要更新的那行:

mysql> **UPDATE member ** -> **SET expiration='20****13****-7-20' ** -> **WHERE last_name='York' AND first_name='Jerome';**

你能夠在一條語句裏同時更新多個列。下面這條UPDATE語句將更新Jerome的電子郵件地址和郵箱地址:

mysql
相關文章
相關標籤/搜索