全面瞭解SQL

不少程序員認爲SQL是一頭難以馴服的野獸。它是爲數很少的聲明性語言之一,也由於這樣,其展現了徹底不一樣於其餘的表現形式、命令式語言、 面嚮對象語言甚至函數式編程語言(雖然有些人以爲SQL 仍是有些相似功能)。java

  我天天都寫SQL,個人開源軟件JOOQ中也包含SQL。所以我以爲有必要爲還在爲此苦苦掙扎的你呈現SQL的優美!下面的教程面向於:程序員

  • 已經使用過但沒有徹底理解SQL的讀者sql

  • 已經差很少了解SQL但從未真正考慮過它的語法的讀者數據庫

  • 想要指導他人學習SQL的讀者編程

  本教程將重點介紹SELECT 語句。其餘 DML 語句將在另外一個教程中在作介紹。接下來就是…安全

  一、SQL是聲明性語言編程語言

  首先你須要思考的是,聲明性。你惟一須要作的只是聲明你想得到結果的性質,而不須要考慮你的計算機怎麼算出這些結果的。ide

SELECT first_name, last_name FROM employees WHERE salary > 100000

  這很容易理解,你無須關心員工的身份記錄從哪來,你只須要知道誰有着不錯的薪水。函數式編程

  從中咱們學到了什麼呢?函數

  那麼若是它是這樣的簡單,會出現什麼問題嗎?問題就是咱們大多數人會直觀地認爲這是命令式編程。如:「機器,作這,再作那,但在這以前,若是這和那都發生錯誤,那麼會運行一個檢測」。這包括在變量中存儲臨時的編寫循環、迭代、調用函數,等等結果。

  把那些都忘了吧,想一想怎麼去聲明,而不是怎麼告訴機器去計算。

  二、SQL語法不是「有序的」

  常見的混亂源於一個簡單的事實,SQL語法元素並不會按照它們的執行方式排序。語法順序以下:

  • SELECT [DISTINCT]

  • FROM

  • WHERE

  • GROUP BY

  • HAVING

  • UNION

  • ORDER BY

  爲簡單起見,並無列出全部SQL語句。這個語法順序與邏輯順序基本上不一樣,執行順序以下: 

  • FROM

  • WHERE

  • GROUP BY

  • HAVING

  • SELECT

  • DISTINCT

  • UNION

  • ORDER BY

  這有三點須要注意:

  一、第一句是FROM,而不是SELECT。首先是將數據從磁盤加載到內存中,以便對這些數據進行操做。

  二、SELECT是在其餘大多數語句後執行,最重要的是,在FROM和GROUP BY以後。重要的是要理解當你以爲你能夠從WHERE語句中引用你定義在SELECT語句當中的時候,。如下是不可行的:

SELECT A.x + A.y AS z FROM A WHERE z = 10 -- z is not available here!

  若是你想重用z,您有兩種選擇。要麼重複表達式: 

SELECT A.x + A.y AS z FROM A WHERE (A.x + A.y) = 10

  或者你使用派生表、公用表表達式或視圖來避免代碼重複。請參閱示例進一步的分析:

  三、在語法和邏輯順序裏,UNION都是放在ORDER BY以前,不少人認爲每一個UNION子查詢均可以進行排序,但根據SQL標準和大多數的SQL方言,並非真的可行。雖然一些方言容許子查詢或派生表排序,但卻不能保證這種順序能在UNION操做後保留。

  須要注意的是,並非全部的數據庫都以相同的形式實現,例如規則2並不徹底適用於MySQL,PostgreSQL,和SQLite上

  從中咱們學到了什麼呢?

  要時刻記住SQL語句的語法順序和邏輯順序來避免常見的錯誤。若是你能明白這其中的區別,就能明確知道爲何有些能夠執行有些則不能。

  若是能設計一種在語法順序上實際又體現了邏輯順序的語言就更好了,由於它是在微軟的LINQ上實現的。

  三、SQL是關於數據表引用的 

  由於語法順序和邏輯順序的差別,大多數初學者可能會誤認爲SQL中列的值是第一重要的。其實並不是如此,最重要的是數據表引用。

  該SQL標準定義了FROM語句,以下:

<from clause> ::= FROM &lt;table reference&gt; [ { &lt;comma&gt; &lt;table reference&gt; }... ]

  ROM語句的"output"是全部表引用的結合程度組合表引用。讓咱們慢慢地消化這些。 

FROM a, b

  上述產生一個a+b度的組合表引用,若是a有3列和b有5列,那麼"輸出表"將有8(3+5)列。

  包含在這個組合表引用的記錄是交叉乘積/笛卡兒積的axb。換句話說,每一條a記錄都會與每一條b記錄相對應。若是a有3個記錄和b有5條記錄,而後上面的組合表引用將產生15條記錄(3×5)。

  在WHERE語句篩選後,GROUP BY語句中"output"是"fed"/"piped",它已轉成新的"output",咱們會稍後再去處理。

  若是咱們從關係代數/集合論的角度來看待這些東西,一個SQL表是一個關係或一組元素組合。每一個SQL語句將改變一個或幾個關係,來產生新的關係。

  從中咱們學到了什麼呢?

  一直從數據表引用角度去思考,有助於理解數據怎樣經過你的sql語句流水做業的

  四、SQL數據表引用能夠至關強大

  表引用是至關強大的東西。舉個簡單的例子,JOIN關鍵字其實不是SELECT語句的一部分,但倒是"special"表引用的一部分。鏈接表,在SQL標準中有定義(簡化的):

複製代碼
<table reference> ::= <table name> | <derived table> | <joined table>
複製代碼

  若是咱們又拿以前的例子來分析: 

FROM a, b

  a能夠做爲一個鏈接表,如:

a1 JOIN a2 ON a1.id = a2.id

  這擴展到前一個表達式,咱們會獲得:

FROM a1 JOIN a2 ON a1.id = a2.id, b

  雖然結合了數據表引用語法與鏈接表語法的逗號分隔表讓人很無語,但你確定還會這樣作的。結果,結合數據表引用將有a1+a2+b度。

  派生表甚至比鏈接表更強大,咱們接下來將會說到。

  從中咱們學到了什麼呢?

  要時時刻刻考慮表引用,重要的是這不只讓你理解數據怎樣經過你的sql語句流水做業的,它還將幫助你瞭解複雜表引用是如何構造的。

  並且,重要的是,瞭解JOIN是構造鏈接表的關鍵字。不是的SELECT語句的一部分。某些數據庫容許JOIN在插入、更新、刪除中使用。

  五、應使用SQL JOIN的表,而不是以逗號分隔表 

  前面,咱們已經看到這語句: 

FROM a, b

  高級SQL開發人員可能會告訴你,最好不要使用逗號分隔的列表,而且一直完整的表達你的JOINs。這將有助於改進你的SQL語句的可讀性從而防止錯誤出現。

  一個很是常見的錯誤是忘記某處鏈接謂詞。思考如下內容:

複製代碼
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來查詢表的語法

  • 更安全,你能夠把鏈接謂詞與鏈接表放一塊兒,從而防止錯誤。

  • 更富於表現力,你能夠區分外部鏈接,內部鏈接,等等。​​

  從中咱們學到了什麼呢?

  使用JOIN,而且永遠不在FROM語句中使用逗號分隔表引用。 

  六、SQL的不一樣類型的鏈接操做

  鏈接操做基本上有五種

  • EQUI JOIN

  • SEMI JOIN

  • ANTI JOIN

  • CROSS JOIN

  • DIVISION

  這些術語一般用於關係代數。對上述概念,若是他們存在,SQL會使用不一樣的術語。讓咱們仔細看看:

  EQUI JOIN(同等鏈接)

  這是最多見的JOIN操做。它有兩個子操做:

  • INNER JOIN(或者只是JOIN)

  • OUTER JOIN(能夠再次拆分爲LEFT, RIGHT,FULL OUTER JOIN)

  例子是其中的區別最好的解釋:

複製代碼
-- 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 LEFT OUTER JOIN book ON author.id = book.author_id
複製代碼

  SEMI JOIN(半鏈接)

  這種關係的概念在SQL中用兩種方式表達:使用IN謂詞或使用EXISTS謂語。"Semi"是指在拉丁語中的"half"。這種類型的鏈接用 於鏈接只有"half"的表引用。再次考慮上述加入的做者和書。讓咱們想象,咱們想要做者/書的組合,但只是那些做者實際上也有書。而後咱們能夠這樣寫:

複製代碼
-- Using IN FROM author WHERE author.id IN (SELECT book.author_id FROM book) -- Using EXISTS FROM author WHERE EXISTS (SELECT 1 FROM book WHERE book.author_id = author.id)
複製代碼

  雖然不能確定你究竟是更加喜歡IN仍是EXISTS,並且也沒有規則說明,但能夠這樣說:

  • IN每每比EXISTS更具可讀性

  • EXISTS每每比IN更富表現力(如它更容易表達複雜的半鏈接)

  • 通常狀況下性能上沒有太大的差別,但,在某些數據庫可能會有巨大的性能差別。

  由於INNER JOIN有可能只產生有書的做者,由於不少初學者可能認爲他們可使用DISTINCT刪除重複項。他們認爲他們能夠表達一個像這樣的半聯接:

-- Find only those authors who also have books SELECT DISTINCT first_name, last_name FROM author

  這是很是很差的作法,緣由有二:

  • 它很是慢,由於該數據庫有不少數據加載到內存中,只是要再刪除重複項。

  • 它不徹底正確,即便在這個簡單的示例中它產生了正確的結果。可是,一旦你JOIN更多的表引用,,你將很難從你的結果中正確刪除重複項。

  更多的關於DISTINCT濫用的問題,能夠訪問這裏的博客

  ANTI JOIN(反鏈接)

  這個關係的概念跟半鏈接恰好相反。您能夠簡單地經過將 NOT 關鍵字添加到IN 或 EXISTS中生成它。在下例中,咱們選擇那些沒有任何書籍的做者:

複製代碼
-- Using IN FROM author WHERE author.id NOT IN (SELECT book.author_id FROM book) -- Using EXISTS FROM author WHERE NOT EXISTS (SELECT 1 FROM book WHERE book.author_id = author.id)
複製代碼

  一樣的規則對性能、可讀性、表現力都適用。然而,當使用NOT IN時對NULLs會有一個小警告,這個問題有點超出本教程範圍。

  CROSS JOIN(交叉鏈接)

  結合第一個表中的內容和第二個表中的內容,引用兩個join表交叉生成一個新的東西。咱們以前已經看到,這能夠在FROM語句中經過逗號分隔表引用來實現。在你確實須要的狀況下,能夠在SQL語句中明確地寫一個CROSS JOIN。

-- Combine every author with every book  author CROSS JOIN book

  DIVISION(除法)

  關係分割就是一隻真正的由本身親自餵養的野獸。簡而言之,若是JOIN是乘法,那麼除法就是JOIN的反義詞。在SQL中,除法關係難以表達清楚。因爲這是一個初學者的教程,解釋這個問題超出了咱們的教程範圍。固然若是你求知慾爆棚,那麼就看這裏這裏還有這裏

  從中咱們學到了什麼呢?

  讓咱們把前面講到的內容再次牢記於心。SQL是表引用。鏈接表是至關複雜的表引用。但關係表述和SQL表述仍是有點區別的,並非全部的關係連 接操做都是正規的SQL鏈接操做。對關係理論有一點實踐與認識,你就能夠選擇JOIN正確的關係類型並能將其轉化爲正確的SQL。

  七、SQL的派生表就像表的變量

  前文,咱們已經瞭解到SQL是一種聲明性語言,所以不會有變量。(雖然在一些SQL語句中可能會存在)但你能夠寫這樣的變量。那些野獸通常的表被稱爲派生表。

  派生表只不過是包含在括號裏的子查詢。

-- A derived table FROM (SELECT * FROM author)

  須要注意的是,一些SQL方言要求派生表有一個關聯的名字(也被稱爲別名)。

-- A derived table with an alias FROM (SELECT * FROM author) a

  當你想規避由SQL子句邏輯排序形成的問題時,你會發現派生表能夠用帥呆了來形容。例如,若是你想在SELECT和WHERE子句中重用一個列表達式,只寫(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
複製代碼

  注意,一些數據庫和SQL:1999標準裏已將派生錶帶到下一級別,,引入公共表表達式。這將容許你在單一的SQL SELECT中重複使用相同的派生表。上面的查詢將轉化爲相似這樣的:

複製代碼
WITH a AS ( SELECT first_name, last_name, current_date - date_of_birth age FROM author ) SELECT * FROM a WHERE age > 10000
複製代碼

  很明顯,對普遍重用的常見SQL子查詢,你也能夠灌輸具體"a"到一個獨立視圖中。想要了解更多就看這裏

  從中咱們學到了什麼呢?

  再溫習一遍,SQL主要是關於表引用,而不是列。好好利用這些表引用。不要懼怕寫派生表或其餘複雜的表引用。

  八、SQL GROUP BY轉換以前的表引用

  讓咱們從新考慮咱們以前的FROM語句:

FROM a, b

  如今,讓咱們來應用一個GROUP BY語句到上述組合表引用

GROUP BY A.x, A.y, B.z

  這會產生一個只有其他三個列(!)的新的表引用。讓咱們再消化一遍。若是你用了GROUP BY,那麼你在全部後續邏輯條款-包括選擇中減小可用列的數量。這就是爲何你只能夠從SELECT語句中的GROUP BY語句引用列語法的緣由。

  • 請注意,其餘列仍然可能可做爲聚合函數的參數:
SELECT A.x, A.y, SUM(A.z) FROM A GROUP BY A.x, A.y

 

  • 值得注意並很不幸的是,MySQL不遵照這一標準,只會形成混亂。不要陷入MySQL的把戲。GROUP BY轉換表引用,所以你能夠只引用列也引用GROUPBY語句。

從中咱們學到了什麼呢?

GROUP BY,在表引用上操做,將它們轉換成一個新表。

  九、SQL SELECT在關係代數中被稱爲投影

  當它在關係代數中使用時,我我的比較喜歡用"投影"一詞中。一旦你生成你的表引用,過濾,轉換它,你能夠一步將它投影到另外一個表中。SELECT語句就像一個投影機。表函數利用行值表達式將以前構造的表引用的每一個記錄轉化爲最終結果。

  在SELECT語句中,你終於能夠在列上操做,建立複雜的列表達式做爲記錄/行的一部分。

  有不少關於可用的表達式,函數性質等的特殊規則。最重要的是,你應該記住這些:

  一、你只能使用從「output」表引用產生的列引用

  二、若是你有GROUP BY語句,你只可能從該語句或聚合函數引用列

  三、當你沒有GROUP BY語句時,你能夠用窗口函數替代聚合函數

  四、若是你沒有GROUP BY語句,你就不能將聚合函數與非聚合函數結合起來

  五、這有一些關於在聚合函數包裝常規函數的規則,反之亦然

  六、還有…

  嗯,這有不少複雜的規則。他們能夠填補另外一個教程。例如,之因此你不能將聚合函數與非聚合函數結合起來投影到沒有GROUP BY的SELECT語句中是由於:

  一、憑直覺,沒有任何意義。

  二、對一個SQL初學者來講,直覺仍是毫無幫助的,語法規則則能夠。SQL:1999年引入了分組集,SQL:2003引入了空分組集 GROUP BY()。每當存在沒有顯式GROUP BY語句的聚合函數時就會應用隱式的空分組集(規則2)。所以,最初關於邏輯順序的那個規則就不徹底正確了,SELECT的投影也會影響前面的邏輯結果, 但語法語句GROUP BY卻不受影響。

是否是有點迷糊?其實我也是,讓咱們看一些簡單的吧。

  從中咱們學到了什麼呢?

  在SQL語句中,SELECT語句多是最複雜的語句之一,即便它看起來是那麼的簡單。全部其餘語句只不過是從這個到另外一個表引用的的輸送管道。經過將它們徹底轉化,後期地對它們應用一些規則,SELECT語句完徹底全地攪亂了這些表引用的美。

  爲了瞭解SQL,重要的是要理解其餘的一切,都要嘗試在SELECT以前解決。即使SELECT在語法順序中排第一的語句,也應該將它放到最後。

  10.相對簡單一點的SQL DISTINCT,UNION,ORDER BY,和OFFSET

  看完複雜的SELECT以後,咱們看回一些簡單的東西。

  • 集合運算(DISTINCT和UNION)

  • 排序操做(ORDER BY,OFFSET..FETCH)

  集合運算

  集合運算在除了表其實沒有其餘東西的「集」上操做。嗯,差很少是這樣,從概念上講,它們仍是很容易理解的

  • DISTINCT投影后刪除重複項。

  • UNION求並集,刪除重複項。

  • UNION ALL求並集,保留重複項。

  • EXCEPT求差集(在第一個子查詢結果中刪除第二個子查詢中也含有的記錄刪除),刪除重複項。

  • INTERSECT求交集(保留全部子查詢都含有的記錄),刪除重複項。

  全部這些刪除重複項一般是沒有意義的,不少時候,當你想要鏈接子查詢時,你應該使用UNION ALL。

  排序操做

  排序不是一個關係特徵,它是SQL僅有的特徵。在你的SQL語句中,它被應用在語法排序和邏輯排序以後。保證能夠經過索引訪問記錄的惟一可靠方法是使用ORDER BY a和OFFSET..FETCH。全部其餘的排序老是任意的或隨機的,即便它看起來像是可再現的。

  OFFSET..FETCH是惟一的語法變體。其餘變體包括MySQL'和PostgreSQL的LIMIT..OFFSET,或者SQL Server和Sybase的TOP..START AT(這裏)。

  讓咱們開始應用吧

  跟其餘每一個語言同樣,要掌握SQL語言須要大量的實踐。上述10個簡單的步驟將讓你天天編寫SQL時更有意義。另外一方面,你也能夠從常見的錯誤中學習到更多。下面的兩篇文章列出許多Java(和其餘)開發者寫SQL時常見的錯誤:

  · 10 Common Mistakes Java Developers Make when Writing SQL

  · 10 More Common Mistakes Java Developers Make when Writing SQL

相關文章
相關標籤/搜索