sql語句基礎

一、SQL是聲明式的

開頭就直接聲明你要查詢的數據,也就是你想顯示的結果java

SELECT first_name, last_name

FROM employees

WHERE salary > 100000

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

形成混淆的一個常見緣由是SQL的語法元素不是按照他們的執行順序排列的,常見語句順序以下:程序員

  • SELECT [ DISTINCT ]:指定要顯示的屬性列
  • FROM:說明要查詢的數據來自那個/些表
  • WHERE:指定查詢條件
  • GROUP BY:對查詢結果進行分組
  • HAVING:篩選出知足指定條件的組
  • UNION:用於合併兩個或多個 SELECT 語句的結果集
  • ORDER BY: 對查詢結果表按指定列值得升序或降序排序

(爲簡單起見,這裏並無列出全部的SQL語句)這個語句順序是不一樣於其邏輯順序:(也可能不一樣於其執行順序,這取決於優化器的選擇)正則表達式

  • FROM
  • WHERE
  • GROUP BY
  • HAVING
  • SELECT
  • DISTINCT
  • UNION
  • ORDER BY

這裏有三點須要注意的:sql

(1)FROM語句必定是首先執行的,而不是SELECT語句,由於系統要先將磁盤中的數據加載到緩存當中,以便執行接下來操做。數據庫

(2)SELECT語句在大多數語句以後執行,尤爲是FROM與GROUP語句。當你認爲你能夠在WHERE語句中引用SELECT語句聲明的元素時,理解這一點就顯得很重要了。好比下面的例子是不容許的:緩存

SELECT A.x + A.y AS zFROM AWHERE z = 10 -- 執行WHERE語句時,z根本不存在,見上面的邏輯順序

若是你想在這裏再次使用z,有兩種方法,一是用A.x + A.y代替,以下安全

SELECT A.x + A.y AS zFROM AWHERE (A.x + A.y) = 10

或者你也能夠採用派生表,通用數據表達式,或者視圖,以免代碼的重複,參見後續的例子。ide

(3) UNION語句無論在語法上仍是邏輯上,都在ORDER BY以前,許多人認爲SELECT的結果能夠先排序後合併,但根據SQL標準和許多SQL方言(注:不一樣數據庫的SQL語句略有差異,姑且稱之爲方言),這是不正確的,儘管一些方言容許對子查詢的結果或者派生表進行排序,但這種結果在執行完UNION語句以後是不必定能保持的。函數

注意,不一樣數據庫執行語句的方式是不一樣的,好比上面第二條就不適用於MySQL, PostgreSQL, 和SQLite。性能

記住SQL語句的語法順序和邏輯順序以免一些常見的錯誤,若是你理解他們的區別,你就能清楚地知道爲何有些程序有些倒是錯的。

三、SQL是關於表的引用的語言

因爲語法順序和邏輯順序之間的差別,大多數初學者可能被騙,覺得列值是SQL中最重要的元素,事實上他們不是,最重要的是表的引用。

SQL標準中是這樣定義FROM語句的:

<from clause> ::=     FROM <table reference>         [ { <comma> <table reference> }... ]

可見FROM語句的輸出結果是一張組合表。下面咱們慢慢分析這一點:

FROM a, b

上面的語句根據表a、b的維度生成了一張組合表,例如若是a有3列,b有5列,那麼輸出表應當有3+5=8列,這張表中的數據是a與b的笛卡兒積(設A,B爲集合(SQL中稱一組具備相同數據類型的值的集合爲「域」,表中的每一列對應一個域),用A中任一元素爲第一元素,B中任一元素爲第二元素構成有序對,全部這樣的有序對組成的集合叫作A與B的「笛卡爾積」)。

這個結果通過WHERE語句過濾後進入GROUP BY語句,而後被轉化爲一個新的輸出,稍後咱們會講到這一點。

若是咱們從關係代數或者集合論的角度來看這個問題,一個SQL表就是一個關係或元組的集合,每一個SQL語句都會改變一個或多個關係併產生新的關係。

要多從表的引用角度來思考問題,這樣才能理解各個SQL語句都是如何處理表中數據的。

四、表的引用功能十分強大

用一個簡單的例子證實這一點: JOIN關鍵字(事實上它並非SELECT語句的一部分,而是屬於一種特殊的表的引用),在SQL標準中,JOIN定義(簡化的)以下:

<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語句的一部分,一些數據庫容許在INSERT, UPDATE, DELETE中使用JOIN。

五、SQL的鏈接操做中應多使用JOIN而不是逗號分隔表

以前,咱們分析了下面的語句:

FROM a, b

高級SQL程序員可能會告訴你,最好不要使用逗號分隔表,要充分利用JOIN這個關鍵字,這不只會提升SQL語句的可讀性,並且會減小錯誤的發生。

好比下面的例子:

FROM a, b, c, d, e, f, g, hWHERE a.a1 = b.bxAND a.a2 = c.c1AND d.d1 = b.bc-- etc...

可見,使用join:

  • 更加安全:你能夠將join放置在靠近要鏈接的表的地方,以此來避免錯誤;
  • 更具表達力:你能夠區分外鏈接、內鏈接。

必定要多使用JOIN。在FROM語句中儘可能不使用逗號分隔表。

六、SQL中不一樣的鏈接操做

鏈接操做主要分爲五種:

  • EQUI JOIN(等值鏈接)
  • SEMI JOIN(半鏈接)
  • ANTI JOIN(ANTI-SEMI JOIN)
  • CROSS JOIN(交叉鏈接)
  • DIVISION(除法鏈接)

這些術語是關係代數中經常使用的術語。SQL對以上概念使用的術語略有不一樣,讓咱們詳細探討一下:

EQUI JOIN

最多見的鏈接操做,它又分爲:

  • INNER JOIN (JOIN)
  • OUTER JOIN (進一步分爲LEFT, RIGHT, FULL JOIN)

(注:

LEFT JOIN: 即便右表中沒有匹配,也從左表返回全部的行

RIGHT JOIN: 即便左表中沒有匹配,也從右表返回全部的行

FULL JOIN: 只要其中一個表中存在匹配,就返回行)

它們的區別能夠用下面這個例子很好的說明:

— 引用了「做者」表以及「著做」表–

每一個做者對應一個著做–

不包含沒有著做的做者

author JOIN book ON author.id = book.author_id

-- 引用了「做者」表以及「著做」表

-- 每一個做者對應一個著做-- ... 

存在無著做的做者,記錄爲空--

 「空「意味着著做所在列爲空

author LEFT OUTER JOIN book ON author.id = book.author_id

SEMI JOIN

這種關係的概念在SQL中能夠用的兩種方式表達:IN或者EXISTS。「Semi」 在拉丁語中意味着「一半」。這種類型的鏈接只引用表中的「一半」。這是什麼意思?(注:一般出如今使用了exists或in的sql中,所謂semi-join即在兩表關聯時,當第二個表中存在一個或多個匹配記錄時,返回第一個表的記錄;與普通join的區別在於semi-join時,第一個表裏的記錄最多隻返回一次)再考慮一下上面做者和書的鏈接。讓咱們想象一下,若是咱們不想要做者/書籍的組合,而只想要有著書的做者的信息。那麼咱們能夠這樣寫:

-- 用 IN

FROM author

WHERE author.id IN (SELECT book.author_id FROM book)

-- 用 EXISTS

FROM author

WHERE EXISTS (SELECT 1 FROM book WHERE book.author_id = author.id)

儘管沒有規定何時用IN,何時用EXISTS,下面的事實仍是要知道的:

  • IN比EXISTS更具可讀性
  • EXISTS比IN更具表現力(即更容易表達很是複雜的半鏈接)
  • 二者性能上沒有顯著差別(可是,也許在一些數據庫中有巨大性能差別,這一點取決於具體數據庫,上面的表述是通常狀況)

由於INNER JOIN也能夠生成有著做的做者的信息,許多初學者可能認爲,他們可使用DISTINCT刪除重複內容,他們認爲他們能夠表達SEMI JOIN:

-- Find only those authors who also have books

SELECT DISTINCT first_name, last_name

FROM author

JOIN book ON author.id = book.author_id

這是很是糟糕的作法,緣由有兩點:

  • 性能低下,由於數據庫須要將大量的數據加載到內存中,來刪除重複的數據。
  • 這不是徹底正確的,即便在這個簡單的例子它會產生正確的結果,可是當你引用更多的表格,那時想從結果中去重就變得十分困難了。

更多有關DISTINCT的內容能夠看這篇博文:

http://blog.jooq.org/2013/07/30/10-common-mistakes-java-developers-make-when-writing-sql/

ANTI JOIN

這個關係的概念與SEMI JOIN剛好相反,你能夠經過簡單地在IN或者EXISTS前添加NOT來實現它(注:而anti-join則與semi-join相反,即當在第二張表沒有發現匹配記錄時,纔會返回第一張表裏的記錄;當使用not exists/not in的時候會用到,二者在處理null值的時候會有所區別,見下文所附連接),例如,咱們來查詢那些沒有對應著做的做者信息:

-- Using IN

FROM author

WHERE author.id NOT IN (SELECT book.author_id FROM book)

-- Using EXISTSF

ROM author

WHERE NOT EXISTS (SELECT 1 FROM book WHERE book.author_id = author.id)

在性能、可讀性、表現力方面,上面的規則一樣適用。然而,在使用NOT IN時遇到NULLs就有點麻煩了,這個有點超出本教程的範圍了。參看:

http://blog.jooq.org/2012/01/27/sql-incompatibilities-not-in-and-null-values/

CROSS JOIN

這個結果是產生兩個表的笛卡兒積,咱們以前說過,也能夠用逗號分隔兩個表來實現。在極少數狀況下,若是確實須要的話,你也能夠這樣明白地來寫一個CROSS JOIN:

-- Combine every author with every book

author CROSS JOIN book

DIVISION

DIVISION是個奇葩,你姑且這樣理解吧,若是JOIN是乘法,DIVISION就是乘法的逆運算,DIVISION很難表述,由於這是初學者的教程,就不說它了,若是你夠勇敢,看這裏:

http://blog.jooq.org/2012/03/30/advanced-sql-relational-division-in-jooq/

看這裏:

http://en.wikipedia.org/wiki/Relational_algebra#Division

和這裏:

https://www.simple-talk.com/sql/t-sql-programming/divided-we-stand-the-sql-of-relational-division/

SQL是關於表引用的,JOIN是至關複雜的表引用,SQL表述與關係表述之間是有區別的,不是全部的關係鏈接操做也正規SQL的鏈接操做。經過一點點的實踐和對關係理論知識的進一步瞭解,你能夠選擇使用正確的JOIN類型,並能把它轉化成正確的SQL語句。

七、SQL的派生表(能夠看做SQL中的表變量)

再此以前,咱們已經瞭解到,SQL是聲明性語言,這種語言中變量是沒有地位的,但你能夠寫一些相似於變量的奇葩東西,這中奇葩東西就被稱爲派生表,派生表其實不過是一個包含在括號內的子查詢。

-- 一個派生表

FROM (SELECT * FROM author)

值得注意的是,一些SQL方言須要派生表有一個相關名(也稱爲別名)。

-- 帶別名的派生表

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標準已經將派生表發展到一個新的水平,引入了通用表表達式,容許你在一個SELECT語句中屢次使用一個派生表。那麼上面的查詢就至關於:

WITH a AS (  

   SELECT first_name, last_name, current_date - date_of_birth age  

   FROM author

)

SELECT *FROM a

WHERE age > 10000

固然你也能夠爲表「a」建立一個獨立的視圖以便在更普遍的範圍內重複使用子查詢的結果。更多關於視圖的講解請看這裏:

http://en.wikipedia.org/wiki/View_%28SQL%29

再次強調,SQL是關於表引用的,要充分利用這一點,不要懼怕寫派生表或者其餘更復雜的表引用語句。

八、GROUP BY語句能夠用來改變表引用的結果

咱們再來看一下前面的例子:

FROM a, b

如今讓咱們對上面的聯合表(FROM語句的操做結果)使用GROUP BY操做:

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

上述操做將會生成一個只有三列的新表,咱們來整理一下思路,若是你使用GROUP,那麼隨後的邏輯語句中可操做的列的數目將會減小,包括SELECT語句,這就是爲何你在SELECT語句中只能引用GROUP BY語句產生的列的緣由。

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

FROM A

GROUP BY A.x, A.y

SELECT A.x, A.y, SUM(A.z)

FROM A

GROUP BY A.x, A.y
  • 不幸的是MySQL沒有使用這個標準,所以也形成了一些問題。不要被MySQL戲弄了,GROUP BY會改變表引用的方式,您只能引用GROUP BY操做產生的列。

GROUP BY也是創建在表引用的基礎上的,而且將它轉化成了一個新表。

九、SQL的SELECT在關係代數中被稱爲投影(也稱映射)

我我的更喜歡「投影」這個詞,關係代數中就是用的這種說法。一旦你生成了新表(FROM)、而且通過了過濾(WHERE)、轉化(GROUP BY),你就能夠將結果投影到另外一張表上,SELECT語句就像一個投影儀:使用某種行值表達式將先前創建的表中的數據映射到最終的結果表中。

使用SELECT語句,你能夠對每一列進行操做,構建複雜的列表達式。

一些經常使用表達式和函數都有不少特殊的使用規則,你應當記住如下這些:

  1. 只能引用輸出表(以前操做產生的表)中的列。
  2. 若是你使用了GROUP BY語句,那麼你只能引用GROUP BY操做產生的列,聚合函數除外。
  3. 若是你的程序中沒有GROUP BY語句,你可使用窗口函數來代替聚合函數。
  4. 若是你的程序中沒有GROUP BY,那麼禁止同時使用聚合函數和非聚合函數。
  5. 在聚合函數中使用正則表達式也有一些特殊要求,反之亦然。
  6. 等等…

還有不少複雜的規則,足以用來寫另外一個教程。例如,在沒有GROUP BY的SELECT語句中不能將聚合函數與非聚合函數結合起來使用的緣由是:

  • 直覺告訴我,沒意義。
  • 若是你沒有這樣的直覺(這對初學者來講很難),那麼讓語法來告訴你:SQL 1999標準引入了GROUPING SETS,SQL 2003標準引入了空分組集:GROUP BY ()。當聚合函數出現而且沒有GROUP BY時,一個隱含的,空的GROUPING SET會被使用。所以,原有的關於邏輯順序的規則將再也不適用,即投影(SELECT)操做會先影響邏輯關係,而後影響到語法關係(GROUP BY)。

儘管看上去簡單,SELECT語句實際上是SQL中最複雜的部分,其餘操做都不過是引用完這個表引用那個表。SELECT徹底打亂了這些表,而且對它們應用了一些特殊的規則。

爲了進一步瞭解SQL,應當在搞定SELECT語句以前掌握其餘全部內容,儘管寫程序時咱們把它放在最前面,但事實上這是最難啃的部分。

十、DISTINCT, UNION, ORDER BY, 和OFFSET相對來講就容易不少了

  • 集合運算 (DISTINCT 和 UNION)
  • 排序操做 (ORDER BY, OFFSET .. FETCH)

集合運算:

集合運算是基於「集合」的操做,實際上,那就是……表!概念上也很容易理解:

  • DISTINCT :刪除投影操做後的重複項
  • UNION:鏈接兩個子查詢而且刪除重複項
  • UNION ALL : 鏈接兩個子查詢而且保留重複項
  • EXCEPT:刪除第一個子查詢中已在第二個子查詢中存在的元素(而後刪除重複項)
  • INTERSECT :只保留兩個子查詢中都存在的元素並刪除重複項

一般來講,這些被刪除的重複項都是無心義的,大多數時候,使用UNION ALL來鏈接兩個子查詢就夠了。

排序操做

排序不具備關係理論的特徵,它是SQL特有的特徵,一般應用在語法順序和邏輯順序的最後,使用ORDER BY、OFFSET .. FETCH是保證數據可以經過索引訪問的惟一方式,其餘排序都是任意和隨機的。

OFFSET .. FETCH的語法規則略有不一樣,其餘的變體包括MySQL和PostgreSQL的 LIMIT .. OFFSET, SQL Server 和Sybase的 TOP .. START AT。更多關於OFFSET .. FETCH的內容能夠看這裏:

http://www.jooq.org/doc/3.1/manual/sql-building/sql-statements/select-statement/limit-clause/

相關文章
相關標籤/搜索