明輝手游網(wǎng)中心:是一個免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

用PHP與MySQL構(gòu)建一個數(shù)據(jù)庫驅(qū)動的網(wǎng)站(10)

[摘要]在我們目前的情況下,我們所需要的列是Jokes表中的JokeText列以及Authors表中的Name列和Email列。Jokes表和Authors表的關(guān)聯(lián)條件是Jokes表中的AID列的值等于Authors表中的ID列的值。下面是一個連接的例子(前兩個查詢只是用來顯示我們的兩個表中所包含的內(nèi)容)...
在我們目前的情況下,我們所需要的列是Jokes表中的JokeText列以及Authors表中的Name列和Email列。Jokes表和Authors表的關(guān)聯(lián)條件是Jokes表中的AID列的值等于Authors表中的ID列的值。下面是一個連接的例子(前兩個查詢只是用來顯示我們的兩個表中所包含的內(nèi)容):


mysql> SELECT LEFT(JokeText,20), AID FROM Jokes;
+----------------------+------+
LEFT(JokeText,20) AID
+----------------------+------+
Why did the chicken 1
A man walked into a 1
Knock knock. Who's t 2
+----------------------+------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM Authors;
+----+------------+---------------------+
ID Name EMail
+----+------------+---------------------+
1 Kevin Yank kyank@attglobal.net
2 Joan Smith joan@somewhere.net
+----+------------+---------------------+
2 rows in set (0.00 sec)

mysql> SELECT LEFT(JokeText,15), Name, Email
-> FROM Jokes, Authors WHERE AID = Authors.ID;
+-------------------+------------+--------- -- -
LEFT(JokeText,15) Name EMail
+-------------------+------------+--------- -- -
Why did the chi Kevin Yank kyank@attg...
A man walked in Kevin Yank kyank@attg...
Knock knock. Wh Joan Smith joan@somew...
+-------------------+------------+--------- -- -
3 rows in set (0.00 sec)



  現(xiàn)在明白了嗎?第三個SELECT的結(jié)果就是一個連接,它將存儲在兩個表中的數(shù)據(jù)關(guān)聯(lián)數(shù)據(jù)顯示到了一個結(jié)果表中,盡管我們的數(shù)據(jù)是存儲在兩個表中的,我們?nèi)匀豢梢允褂靡粋數(shù)據(jù)庫查詢就獲得我們的Web頁面所需要的笑話列表的全部信息。

  在這里,要注意一個問題,因為在兩個表中都有一個叫ID的列,所以我們在用到Authors表中的ID列時我們必須指定表名(Authors.ID)。如果我們沒有指定表名,MySQL將無法知道我們指的是哪一個表中的ID,這會導(dǎo)致這樣的一個錯誤:


mysql> SELECT LEFT(JokeText,20), Name, Email
-> FROM Jokes, Authors WHERE AID = ID;
ERROR 1052: Column: 'ID' in where clause is ambiguous



  現(xiàn)在我們知道如何有效率地從我們的兩個表中獲取信息了,我們可以利用連接來重新編寫我們的笑話列表的程序:


$jokelist = mysql_query(
"SELECT JokeText, Name, EMail " .
"FROM Jokes, Authors WHERE AID=Authors.ID");

while ($joke = mysql_fetch_array($jokelist)) {
$joketext = $joke["JokeText"];
$name = $joke["Name"];
$email = $joke["EMail"];

// Display the joke with author information
echo( "<P>$joketext<BR>" .
"(by <A HREF='mailto:$email'>$name)</P>" );
}



  隨著你對數(shù)據(jù)庫的使用,你會越來越發(fā)現(xiàn)連接的功能有多大的意義。例如,下面的查詢用來顯示所有由Joan Smith寫的笑話:


mysql> SELECT JokeText FROM Jokes, Authors WHERE
-> Name="Joan Smith" AND AID=Authors.ID;



  上面的查詢的輸出結(jié)果僅僅來源于Jokes表,但是我們使用了一個連接來通過存儲在Authors表中的值搜索笑話。在我們的這篇文章中會有更多的這樣的精巧的查詢,在實際應(yīng)用中,連接是經(jīng)常會被使用的,而且在絕大多數(shù)的情況下,這會很大程度地簡化我們的工作!

簡單的數(shù)據(jù)關(guān)系

  對于給定的情況的最好的數(shù)據(jù)模型往往決定于我們所工作的兩種數(shù)據(jù)之間的關(guān)系類型。我這篇文章中,我們將對典型的關(guān)系類型進(jìn)行研究,并學(xué)會如何在一個關(guān)系型數(shù)據(jù)中用最好的方法描述它。

  對于簡單的一對一的關(guān)系,只要用一個表就足夠了。一對一關(guān)系的一個例子就是我們在前面已經(jīng)看到的在笑話數(shù)據(jù)庫中的每一個作者的e-mail地址。因為對于每一個作者只有一個e-mail地址,而且對于一個e-mail地址對應(yīng)的也只有一個作者,將它們分到兩個數(shù)據(jù)庫中是沒有道理的。

  多對一的關(guān)系可能會稍微復(fù)雜一點(diǎn),但是在之前其實我們也已經(jīng)解決了這個問題,我們的數(shù)據(jù)庫中的每一個笑話只會有一個作者,但是同一個作者可能寫了很多笑話。笑話和作者之間的關(guān)系就是一個多對一的關(guān)系。我們曾經(jīng)有過一個初步的解決方案,那就是將與這個笑話關(guān)聯(lián)的作者的信息也促成在同一個數(shù)據(jù)庫中。但是這樣做,對于同一個數(shù)據(jù)會有許多拷貝,這不僅會在同步上造成困難,而且會浪費(fèi)空間。將數(shù)據(jù)分開到兩個數(shù)據(jù)表中并使用一個ID列來連接兩個表(象上面所說的那樣使用連接),所有的問題會得到很好的解決。

  到目前為止,我們還沒接觸到一對多的關(guān)系,但是想象這樣的一個關(guān)系應(yīng)該是不困難的。在我們之前建立的數(shù)據(jù)庫中,我們假定一個作者只有一個e-mail地址。事實上情況并不總是這樣的,作出這個限制的理由只是因為我們只需要一個e-mail地址來與作者聯(lián)系。我們簡單地假設(shè)了作者總會輸入他們常用的e-mail地址,或者至少是一個正常使用的e-mail地址。如果我們想要支持多個e-mail地址,我們將面對一個一對多的關(guān)系(一個作者會有幾個e-mail地址,但是一個e-mail地址只會與一個確定的作者對應(yīng))。

  一個沒有經(jīng)驗的數(shù)據(jù)庫設(shè)計者面對一個一對多的關(guān)系時,他首先會想到的是試圖把多個數(shù)據(jù)存儲到一個數(shù)據(jù)庫域中,就象這樣:

  這種結(jié)構(gòu)在投入使用后,要從數(shù)據(jù)庫中獲得一個單個的e-mail地址,將不得不通過搜索逗號(或者你所選擇的用來分隔的其他符號)來分割字符串,這樣做并不簡單,而且會很耗時。設(shè)想一下如果要用PHP來刪除某個作者的某個e-mail地址,那也將會是很困難的事。另外,對于EMail列我們需要很長的長度,這會導(dǎo)致磁盤空間的浪費(fèi),因為大多數(shù)的作者都只會有一個e-mail地址。

  解決一對多的關(guān)系和我們上面解決多對一的關(guān)系是非常類似的。實際上兩者之前只是一個簡單的顛倒。我們可將Authors表分成兩個表,Authors和EMails,然后在EMails表中使用作者的ID(AID)這樣的一個列來實現(xiàn)兩個表之間的連接:

  使用一個連接,顯示某個作者的所有e-mail地址將會是很簡單的:


mysql> SELECT EMail FROM Authors, EMails WHERE
-> Name="Kevin Yank" AND AID=Authors.ID;
+---------------------+
EMail
+---------------------+
kevin@sitepoint.com
kyank@attglobal.net
+---------------------+
2 rows in set (0.00 sec)



多對多的關(guān)系

  Ok,現(xiàn)在你有了一個發(fā)布在你的網(wǎng)站上的穩(wěn)定增長的笑話數(shù)據(jù)庫。事實上,這種增長是非常迅速的,笑話的數(shù)量會變得難以管理!你的訪問者將面對一個龐大的頁面,在這個頁面上雜亂地排列了數(shù)以百計的笑話。現(xiàn)在,我們不得不考慮作一些變動了。

  你決定將你的笑話放置到不同的目錄中,這些目錄可能是“Knock-Knock笑話”、“Crossing the Road笑話”、“Lawyer笑話”和“Political笑話”。記住我們之前的處理規(guī)則,因為我們的笑話目錄是一個不同類型的“事物”,所以我們要為它們建立一個新的數(shù)據(jù)表:


mysql> CREATE TABLE Categories (
-> ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> Name VARCHAR(100),
-> Description TEXT
-> );
Query OK, 0 rows affected (0.00 sec)



  對你的笑話定義其所屬目錄將會是一個困難的任務(wù)。因為一個“political”笑話可能也是一個“crossing the road”笑話,同樣,一個“knock-knock”可能也是一個“l(fā)awyer”笑話。一個單個的笑話可能屬于許多目錄,每一個目錄也會包含許多笑話。這是一個多對多的關(guān)系。

  許多沒有經(jīng)驗的設(shè)計者又會想到將幾個數(shù)據(jù)存儲到一個列中,最直接的解決方案是在Jokes表中增加Categories列,并在其中列舉笑話所屬的目錄的ID,F(xiàn)在適用我們的第二個處理規(guī)則了:如果你需要在一個列中存儲多個值,那證明你的設(shè)計可能是有缺陷的。

  描述一個多對多關(guān)系的正確方法是使用一個“l(fā)ookup”表。這個表不包含任何實際的數(shù)據(jù),只是用來定義關(guān)聯(lián)的事物。這兒是我們這部分的數(shù)據(jù)庫設(shè)計的示意圖:

  JokeLookup 表將笑話的ID(JID)的目錄的ID(CID)進(jìn)行了關(guān)聯(lián)。從上面的例子我們可以看出,以“How many lawyers...”開頭的笑話既屬于“Lawyer”目錄,又屬于“Light Bulb”目錄。

  建立lookup表的方法和建立其他表的方法基本一樣。不同點(diǎn)在于選擇主鍵。我們之前所建立的每一個表都有一個名為ID的列,這一列被我們定義為PRIMARY KEY。將一個列定義為主鍵意味著這一列不會出現(xiàn)重復(fù)值。而且可以加快基于這一列的連接操作的速度。

  對于我們的lookup表來說,沒有一個單個的列可以保證不出現(xiàn)重復(fù)值。每一個笑話可以屬于幾個目錄,所以一個joke ID可能會出現(xiàn)多次;同樣的,一個目錄可能包含多個笑話,所以一個category ID也可能會出現(xiàn)多次。我們所要求的只是相同的數(shù)據(jù)對不應(yīng)重復(fù)出現(xiàn)。因為我們這個表的唯一作用就是用來實現(xiàn)連接,所以使用主鍵來提高連接操作的速度對我們肯定有價值。所以,我們通常會為lookup表建立一個多列的主鍵:


mysql> CREATE TABLE JokeLookup (
-> JID INT NOT NULL,
-> CID INT NOT NULL,
-> PRIMARY KEY(JID,CID)
-> );



  現(xiàn)在我們的表中的JID和CID共同組成了這個表的主鍵。保持lookup表中數(shù)據(jù)的唯一性是有價值的(防止重復(fù)定義某一個笑話屬于某一個目錄),而且這會提高這個表用來連接時的速度。

  使用我們的lookup表中包含的目錄分配,我們可以使用連接來建立幾個有趣而且非常實用的查詢。下面的查詢列出了“Knock-Knock”目錄下的所有笑話:


mysql> SELECT JokeText
-> FROM Jokes, Categories, JokeLookup
-> WHERE Name="Knock-Knock" AND
-> CID=Categories.ID AND JID=Jokes.ID;



  下面這個查詢列舉了以“How many lawyers...”開頭的笑話所屬的所有目錄:


mysql> SELECT Categories.Name
-> FROM Jokes, Categories, JokeLookup
-> WHERE JokeText LIKE "How many lawyers%"
-> AND CID=Categories.ID AND JID=Jokes.ID;



  下面的查詢,同時使用了我們的Authors表形成了一個四個表的連接(。。。,列舉了寫過 Knock-Knock笑話的所有作者的名字:


mysql> SELECT Authors.Name
-> FROM Jokes, Authors, Categories, JokeLookup
-> WHERE Categories.Name="Knock-Knock"
-> AND CID=Categories.ID AND JID=Jokes.ID
-> AND AID=Authors.ID;



結(jié)語

  這一章中,我們學(xué)習(xí)了正確的數(shù)據(jù)庫設(shè)計的基本原則,以及MySQL(實際上,對其他關(guān)系型數(shù)據(jù)庫同樣適用)如何對描述事件之間的不同類型的關(guān)系提供支持。我們不僅僅探討了一對一的關(guān)系,還詳細(xì)討論了多對一、一對多以及多對多的關(guān)系。

  在這一過程中,我們還學(xué)習(xí)了一些有關(guān)SQL命令的新的東西。特別的,我們學(xué)習(xí)了如何使用一個SELECT去連接多個表中的數(shù)據(jù)并將其反映到一個結(jié)果集中。

  在第六章中,我們將使用我們已經(jīng)獲得的知識,并加上很少的一些新知識,去用PHP構(gòu)建一個內(nèi)容管理系統(tǒng)。我們希望這個系統(tǒng)可以提供一個可定制的、安全的、基于Web的界面來管理數(shù)據(jù)庫的內(nèi)容,而不再是在MySQL命令行中來解決問題。