如果 SQL 語(yǔ)言這么簡(jiǎn)單,那么是什么讓人們“聞 SQL 色變”?主要的原因是:我們潛意識(shí)中的是按照命令式編程的思維方式思考問(wèn)題的。就好像這樣:“電腦,先執(zhí)行這一步,再執(zhí)行那一步,但是在那之前先檢查一下是否滿足條件 A 和條件 B ”。例如,用變量傳參、使用循環(huán)語(yǔ)句、迭代、調(diào)用函數(shù)等等,都是這種命令式編程的思維慣式。
1、 FROM 才是 SQL 語(yǔ)句執(zhí)行的第一步,并非 SELECT 。數(shù)據(jù)庫(kù)在執(zhí)行 SQL 語(yǔ)句的第一步是將數(shù)據(jù)從硬盤加載到數(shù)據(jù)緩沖區(qū)中,以便對(duì)這些數(shù)據(jù)進(jìn)行操作。(譯者注:原文為“The first thing that happens is loading data from the disk into memory, in order to operate on such data.”,但是并非如此,以 Oracle 等常用數(shù)據(jù)庫(kù)為例,數(shù)據(jù)是從硬盤中抽取到數(shù)據(jù)緩沖區(qū)中進(jìn)行操作。)
2、 SELECT 是在大部分語(yǔ)句執(zhí)行了之后才執(zhí)行的,嚴(yán)格的說(shuō)是在 FROM 和 GROUP BY 之后執(zhí)行的。理解這一點(diǎn)是非常重要的,這就是你不能在 WHERE 中使用在 SELECT 中設(shè)定別名的字段作為判斷條件的原因。
SELECT A.x + A.y AS z FROM A WHERE z = 10-- z 在此處不可用,因?yàn)镾ELECT是最后執(zhí)行的語(yǔ)句!
如果你想重用別名z,你有兩個(gè)選擇。要么就重新寫一遍 z 所代表的表達(dá)式:
SELECT A.x + A.y AS z FROM A WHERE (A.x + A.y) = 10
3、 無(wú)論在語(yǔ)法上還是在執(zhí)行順序上, UNION 總是排在在 ORDER BY 之前。很多人認(rèn)為每個(gè) UNION 段都能使用 ORDER BY 排序,但是根據(jù) SQL 語(yǔ)言標(biāo)準(zhǔn)和各個(gè)數(shù)據(jù)庫(kù) SQL 的執(zhí)行差異來(lái)看,這并不是真的。盡管某些數(shù)據(jù)庫(kù)允許 SQL 語(yǔ)句對(duì)子查詢(subqueries)或者派生表(derived tables)進(jìn)行排序,但是這并不說(shuō)明這個(gè)排序在 UNION 操作過(guò)后仍保持排序后的順序。
FROM 語(yǔ)句的“輸出”是一張聯(lián)合表,來(lái)自于所有引用的表在某一維度上的聯(lián)合。我們們慢慢來(lái)分析:
FROM a, b
上面這句 FROM 語(yǔ)句的輸出是一張聯(lián)合表,聯(lián)合了表 a 和表 b 。如果 a 表有三個(gè)字段, b 表有 5 個(gè)字段,那么這個(gè)“輸出表”就有 8 ( =5+3)個(gè)字段。
這個(gè)聯(lián)合表里的數(shù)據(jù)是 a*b,即 a 和 b 的笛卡爾積。換句話說(shuō),也就是 a 表中的每一條數(shù)據(jù)都要跟 b 表中的每一條數(shù)據(jù)配對(duì)。如果 a 表有3 條數(shù)據(jù), b 表有 5 條數(shù)據(jù),那么聯(lián)合表就會(huì)有 15 ( =5*3)條數(shù)據(jù)。
FROM 輸出的結(jié)果被 WHERE 語(yǔ)句篩選后要經(jīng)過(guò) GROUP BY 語(yǔ)句處理,從而形成新的輸出結(jié)果。我們后面還會(huì)再討論這方面問(wèn)題。
(譯者注:原文這里用詞為 degree ,譯為維度。如果把一張表視圖化,我們可以想象每一張表都是由橫縱兩個(gè)維度組成的,橫向維度即我們所說(shuō)的字段或者列,英文為columns;縱向維度即代表了每條數(shù)據(jù),英文為 record ,根據(jù)上下文,作者這里所指的應(yīng)該是字段數(shù)。)
FROM a, b, c, d, e, f, g, h WHERE a.a1 = b.bx AND a.a2 = c.c1 AND d.d1 = b.bc -- etc...
我們不難看出使用 JOIN 語(yǔ)句的好處在于:
安全。JOIN 和要連接的表離得非常近,這樣就能避免錯(cuò)誤。
更多連接的方式,JOIN 語(yǔ)句能去區(qū)分出來(lái)外連接和內(nèi)連接等。
我們學(xué)到了什么?
記著要盡量使用 JOIN 進(jìn)行表的連接,永遠(yuǎn)不要在 FROM 后面使用逗號(hào)連接表。
6、 SQL 語(yǔ)句中不同的連接操作
SQL 語(yǔ)句中,表連接的方式從根本上分為五種:
EQUI JOIN
SEMI JOIN
ANTI JOIN
CROSS JOIN
DIVISION
EQUI JOIN
這是一種最普通的 JOIN 操作,它包含兩種連接方式:
INNER JOIN(或者是 JOIN )
OUTER JOIN(包括:LEFT 、 RIGHT、 FULL OUTER JOIN)
用例子最容易說(shuō)明其中區(qū)別:
-- This table reference contains authors and their books. -- There is one record for each book and its author. -- authors without books are NOT included author JOIN book ON author.id = book.author_id
-- This table reference contains authors and their books -- There is one record for each book and its author. -- ... OR there is an "empty" record for authors without books -- ("empty" meaning that all book columns are NULL) author LEFTOUTERJOIN book ON author.id = book.author_id
-- A derived table with an alias FROM (SELECT * FROM author) a
派生表可以有效的避免由于 SQL 邏輯而產(chǎn)生的問(wèn)題。舉例來(lái)說(shuō):如果你想重用一個(gè)用 SELECT 和 WHERE 語(yǔ)句查詢出的結(jié)果,這樣寫就可以(以 Oracle 為例):
-- Get authors' first and last names, and their age in days SELECT first_name, last_name, age FROM ( ?SELECT first_name, last_name, current_date - date_of_birth age ?FROM author ) -- If the age is greater than 10000 days WHERE age > 10000
需要我們注意的是:在有些數(shù)據(jù)庫(kù),以及 SQL :1990 標(biāo)準(zhǔn)中,派生表被歸為下一級(jí)——通用表語(yǔ)句( common table experssion)。這就允許你在一個(gè) SELECT 語(yǔ)句中對(duì)派生表多次重用。上面的例子就(幾乎)等價(jià)于下面的語(yǔ)句:
WITH a AS ( ?SELECT first_name, last_name, current_date - date_of_birth age ?FROM author ) SELECT * FROM a WHERE age > 10000
當(dāng)然了,你也可以給“ a ”創(chuàng)建一個(gè)單獨(dú)的視圖,這樣你就可以在更廣泛的范圍內(nèi)重用這個(gè)派生表了。
上面語(yǔ)句的結(jié)果就是產(chǎn)生出了一個(gè)包含三個(gè)字段的新的表的引用。我們來(lái)仔細(xì)理解一下這句話:當(dāng)你應(yīng)用 GROUP BY 的時(shí)候, SELECT 后沒(méi)有使用聚合函數(shù)的列,都要出現(xiàn)在 GROUP BY 后面。(譯者注:原文大意為“當(dāng)你是用 GROUP BY 的時(shí)候,你能夠?qū)ζ溥M(jìn)行下一級(jí)邏輯操作的列會(huì)減少,包括在 SELECT 中的列”)。
需要注意的是:其他字段能夠使用聚合函數(shù):
SELECT A.x, A.y, SUM(A.z) FROM A GROUPBY A.x, A.y
還有一點(diǎn)值得留意的是:MySQL 并不堅(jiān)持這個(gè)標(biāo)準(zhǔn),這的確是令人很困惑的地方。(譯者注:這并不是說(shuō) MySQL 沒(méi)有 GROUP BY 的功能)但是不要被 MySQL 所迷惑。GROUP BY 改變了對(duì)表引用的方式。你可以像這樣既在 SELECT 中引用某一字段,也在 GROUP BY 中對(duì)其進(jìn)行分組。
我們學(xué)到了什么?
GROUP BY,再次強(qiáng)調(diào)一次,是在表的引用上進(jìn)行了操作,將其轉(zhuǎn)換為一種新的引用方式。
如果你有 GROUP BY 語(yǔ)句,你只能夠使用 GROUP BY 語(yǔ)句后面的字段或者聚合函數(shù);
當(dāng)你的語(yǔ)句中沒(méi)有 GROUP BY 的時(shí)候,可以使用開(kāi)窗函數(shù)代替聚合函數(shù);
當(dāng)你的語(yǔ)句中沒(méi)有 GROUP BY 的時(shí)候,你不能同時(shí)使用聚合函數(shù)和其它函數(shù);
有一些方法可以將普通函數(shù)封裝在聚合函數(shù)中;
……
一些更復(fù)雜的規(guī)則多到足夠?qū)懗隽硪黄恼铝恕1热纾簽楹文悴荒茉谝粋€(gè)沒(méi)有 GROUP BY 的 SELECT 語(yǔ)句中同時(shí)使用普通函數(shù)和聚合函數(shù)?(上面的第 4 條)
原因如下:
憑直覺(jué),這種做法從邏輯上就講不通。
如果直覺(jué)不能夠說(shuō)服你,那么語(yǔ)法肯定能。SQL : 1999 標(biāo)準(zhǔn)引入了 GROUPING SETS,SQL:2003 標(biāo)準(zhǔn)引入了 group sets : GROUP BY() 。無(wú)論什么時(shí)候,只要你的語(yǔ)句中出現(xiàn)了聚合函數(shù),而且并沒(méi)有明確的 GROUP BY 語(yǔ)句,這時(shí)一個(gè)不明確的、空的 GROUPING SET 就會(huì)被應(yīng)用到這段 SQL 中。因此,原始的邏輯順序的規(guī)則就被打破了,映射(即 SELECT )關(guān)系首先會(huì)影響到邏輯關(guān)系,其次就是語(yǔ)法關(guān)系。(譯者注:這段話原文就比較艱澀,可以簡(jiǎn)單理解如下:在既有聚合函數(shù)又有普通函數(shù)的 SQL 語(yǔ)句中,如果沒(méi)有 GROUP BY 進(jìn)行分組,SQL 語(yǔ)句默認(rèn)視整張表為一個(gè)分組,當(dāng)聚合函數(shù)對(duì)某一字段進(jìn)行聚合統(tǒng)計(jì)的時(shí)候,引用的表中的每一條 record 就失去了意義,全部的數(shù)據(jù)都聚合為一個(gè)統(tǒng)計(jì)值,你此時(shí)對(duì)每一條 record 使用其它函數(shù)是沒(méi)有意義的)。
排序運(yùn)算跟邏輯關(guān)系無(wú)關(guān)。這是一個(gè) SQL 特有的功能。排序運(yùn)算不僅在 SQL 語(yǔ)句的最后,而且在 SQL 語(yǔ)句運(yùn)行的過(guò)程中也是最后執(zhí)行的。使用 ORDER BY 和 OFFSET…FETCH 是保證數(shù)據(jù)能夠按照順序排列的最有效的方式。其他所有的排序方式都有一定隨機(jī)性,盡管它們得到的排序結(jié)果是可重現(xiàn)的。
OFFSET…SET是一個(gè)沒(méi)有統(tǒng)一確定語(yǔ)法的語(yǔ)句,不同的數(shù)據(jù)庫(kù)有不同的表達(dá)方式,如 MySQL 和 PostgreSQL 的 LIMIT…OFFSET、SQL Server 和 Sybase 的 TOP…START AT 等。