“范老濕從不會SQL”

于是又到了梧桐絮飄滿校園的四月,又到了梧桐色相簿的季節,我們迎來了實習生招聘。

“范老濕從不寫代碼”,“范老濕從不會SQL”,說起來都是淚。去年就因為SQL掛了面了一下午的X團,然而今年仍然不會。近來又因為C能力低下掛掉了N多OJ,滿屏AC的日子一去不復返。所幸現在拯救也許還不晚,那讓我們來愉快的治療它吧~

參考書籍是Ben Forta的《SQL必知必會》

一、SQL相關概念

  • 數據庫(database):保存有組織的數據的容器(通常是一個文件或一組文件);
  • 表(table):某種特定類型數據的結構化清單;
    每一個數據庫中的表都有一個唯一的表名
  • 模式:關于數據庫和表的布局及特性的信息;
  • 列(column):表中的一個字段;
    所有表都是由一個或多個列組成的
    每一個表列都有一個數據類型
  • 行(row):表中的一個紀錄;
    有時稱紀錄(record),行才是正確的術語
  • 主鍵(primary key):一(組)列,其值存在且唯一,故能標識表中的每一行;
    主鍵列的值一般不允許修改與更新,且不能重用(即被刪除行的值刪除后不能用于以后的值)
范老濕總結:每一個database里面可能有很多table,既然是table那一定有column和row,起唯一標識作用的column被稱為primary key。Primary key中的值一定符合以下性質:存在性、唯一性、不可復用性。

以及:

  • 關鍵字:即SQL中的保留字,不能用做表或列的名字。
    SQL支持以下三種注釋:

#, --, /* */

二、SELECT

2.1 SELECT的基本使用

SELECT, like its name, 就是從表中選擇數據嘛。那怎樣才是SELECT的正確姿勢呢?

SELECT prod_name FROM Products;

相信機智的你一眼就能看出,這是從Products這個表中選擇了prod_name這個column。

從上面那條SQL語句中,可以(或者不可以)看出以下幾點:

  • SQL語句中,結束要加分號(半角的不用說了吧);
  • SQL語句不區分大小寫(看當地規范了,這個例子就是關鍵字大寫,表名首字母大寫,其它小寫);
  • SQL語句中,不區分空格與回車(所以上面那四個詞語寫每行一個也沒關系,你開心就好);
  • SQL語句的輸出,并不一定遵循某些順序。

那這樣呢:

SELECT prod_id, prod_name, prod_price FROM Products;

當然就是從Products這個表中同時選擇了prod_id, prod_name, prod_price這三列。
在語句中,列名之間以逗號分隔。

這樣呢:

SELECT * FROM Products;

這樣就是從Products里面選擇了所有的column。

有時候,我們需要去重:

SELECT DISTINCT vend_id FROM Products;

這樣,輸出的vend_id就實現了去重
需要注意的是,DISTINCT作用于所有的列,而并非跟在后面的那一列,比如:

SELECT DISTINCT vend_id, prod_price FROM Products;

我們也可以對輸出的內容作出限制,比如我們想取prod_name中的TOP 5:

SELECT prod_name FROM Products LIMIT 5;

(不同SQL實現的本條語句,語法可能不同,本文以MySQL為準,下同)
如果想看后面的呢?

SELECT prod_name FROM Products LIMIT 5 OFFSET 5;

這樣就返回了Products中prod_name這列從第5行起的5行數據。

2.2 排序:ORDER BY

可是,就像我們之前說的,SQL的輸出默認是沒有順序的。如果你問我啊,我可以說一句“無可奉告”。但是看你們這么熱情啊,一句話不說也不好。所以說啊,我們來學習一個,如何才能讓SQL的輸出有順序:

SELECT prod_name FROM Products ORDER BY prod_name;

這樣的話,就是按照prod_name的字母順序升序排列了。
需要注意的是,ORDER BY子句應被保證為SELECT語句中的最后一條子句。

不過這樣只能按一個列排序啊Orz如果我們想按多個列排序呢?

SELECT prod_id, prod_price, prod_name FROM Products ORDER BY prod_price, prod_name;

可是這樣寫語句是不是有點太長了呢?其實這樣也可以的:

SELECT prod_id, prod_price, prod_name FROM Products ORDER BY 2, 3;

就可以達到和上面語句一樣的效果,其實就是從那三個查看的列里面選擇了第二個和第三個吧。

上面的都是升序,那如果我們想降序排列呢?

SELECT prod_id, prod_price, prod_name FROM Products ORDER BY prod_price DESC;

要降序排列的列要后面加上DESC,反過來說,就是DESC只作用于它前面的那一列:

SELECT prod_id, prod_price, prod_name FROM Products ORDER BY prod_price DESC, prod_name;

這樣,prod_price列就是降序的,prod_name列則呈升序排列。

2.3 數據過濾:WHERE

但是這樣的話,似乎還是不夠方便啊。如果我們想查看某一個列的值符合特定規律的數據呢?這樣我們就用到WHERE

SELECT prod_name, prod_price FROM Products WHERE prod_price = 3.49;

這樣就輸出了所有 prod_price值為3.49的prod_name和prod_price

WHERE字句都資瓷哪些操作符呢?

=, <>, !=, <, <=, !<, >, >=, !>, BETWEEN, IS NULL

SQL有個特點,就是非常直觀,這些符號的含義也就不用做過多解釋了。

SQL還資瓷很多花式的過濾方法!是的!就算過濾條件更復雜一些也沒關系!比如像這樣:

SELECT prod_id, prod_price, prod_name FROM Products WHERE vend_id = 'DLL01' AND prod_price <= 4;

既然資瓷了AND,你問我資不資瓷OR?當然資瓷!我就這么明確告訴你:

SELECT prod_id, prod_price, prod_name FROM Products WHERE vend_id = 'DLL01' AND vend_id = 'BRS01';

既然說到ANDOR,那么不可避免的就是出現邏輯運算順序的問題了。SQL優先處理AND運算符。在需要指定順序時,也可以使用括號:

SELECT prod_name, prod_price FROM Products WHERE (vend_id = 'DLL01' OR vend_id = 'BRS01') AND prod_price >= 10;

以及ORDED BY子句,一般是放在WHERE后面。

其實OR操作也可以這樣實現

SELECT prod_name, prod_price FROM Products WHERE vend_id IN ('DLL01', 'BRS01') ORDER BY prod_name;

而且,IN的操作一般比OR更快哦~而且IN還可以用來實現更多更靈活的操作!

那么既然有了ANDOR,自然而然就有了NOT

SELECT prod_name FROM Products WHERE NOT vend_id = 'DLL01' ORDER BY prod_name;

2.4 更靈活的過濾:LIKE

這樣我們就學會了過濾,但是似乎好像資瓷的過濾方式還太少啊Orz連字符串匹配都不資瓷(就你事多(誤

好好好,這就說怎樣才是資瓷更高級過濾條件的正確姿勢:

SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE 'Fish%';

SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE '%bean bag%';

就是過濾出prod_name里面以Fish開頭或者含有bean bag的數據。

也可以這樣:

SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE 'F%y';

過濾出prod_name中以F開頭以y結尾的數據。

這樣存在一個問題:很多DBMS會在字符串后自動添加空格,以達到和字符串類型定義時相等的長度。

以及LIKE '%'不會匹配NULL。

當然,都說了是“更靈活的”過濾姿勢,怎么能只有%一種?

SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE ‘__ inch teddy bear';

_操作符相較于%更加嚴格?感覺這么說也不好,算是用法不同吧。%是匹配字符串,而一個_則是匹配單個字符,不許多也不許少。

有時候我們還會遇到要匹配的字符有多種可能性的情況,除了用OR,還有什么更優雅的方案么?

SELECT cust_contact FROM Customers WHERE cust_contact LIKE ‘[JM]%' ORDER BY cust_contact;

這樣就匹配了cust_contact里面以J或M開頭的數據。同樣的,[]也只是對應單個字符。

如果我們想否定,可以用NOT,當然也可以用更優雅的^

SELECT cust_contact FROM Customers WHERE cust_contact LIKE ‘[^JM]%' ORDER BY cust_contact;

主要的通配符就是%, _[]。不幸的是通配符一般速度會比較慢,建議不要的過度使用,使用的時候也盡量放在搜索模式的后面,以減少檢索時間。

2.5 計算字段與格式化輸出

看來僅僅簡單的把數據輸出還不行啊,有時候我們還需要將數據按照一定格式輸出,或者做一些數值計算。說白了就是字符串操作和數值計算嘛。比如這樣:

SELECT Concat(vend_name, ' (', vend_country, ')') FROM vendors ORDER BY vend_name;

即在vend_country字段兩側加了括號。Concat則是起連接作用的函數。

我們前面說過,有的DBMS(比如MySQL)會在字符串末尾補全空格。這時就需要用RTrim函數解決:

SELECT Concat(RTrim(vend_name), ' (', RTrim(vend_country), ')') FROM vendors ORDER BY vend_name;

除了去除右邊空格的RTrim以外,還有去掉左邊空格的LTrim和左右空格的Trim

有時候我們要把新生成的部分起一個名字,也就是別名:

SELECT Concat(RTrim(vend_name), ' (', RTrim(vend_country), ')') AS vend_title FROM vendors ORDER BY vend_name;

字符串的計算就是上面那樣,那么如何進行數學計算呢?

SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price FROM orderitems WHERE order_num = 20005;

這樣就計算了數量和單價的乘積,并將其別名設為expanded_price。

2.6 函數運算

是的,既然資瓷一般的字符串運算,那么更應該資瓷一下更復雜的函數運算,上一節我們提到了Trim等函數這一屆里面我們簡要的把函數分為文本處理函數、日子時間處理函數和數值處理函數。

首先說文本處理函數

函數 說明
Left() 返回串左邊的字符
Length() 返回串的長度
Locate() 找出串的一個子串
Lower() 將串轉化為小寫
LTrim() 去掉串左邊的空格
Right() 返回串右邊的字符
RTrim() 去掉串右邊的空格
Soundex() 返回串的SOUNDEX值
SubString() 返回子串的字符
Upper() 將串轉化為大寫

其中SOUNDEX的含義需要解釋一下,簡單來說就是讀音相似的,比如:

SELECT cust_name, cust_contact FROM Customers WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green')

cust_contact里面如果有'Michelle Green'這樣讀音相似的,就會返回。

接下來是日期時間處理函數:

函數 說明
AddDate() 增加一個日期
AddTime() 增加一個事件
CurDate() 返回當前日期
CurTime() 返回當前時間
Date() 返回一個日期時間的日期部分
DateDiff() 計算兩個日期之差
Date_Add() 高度靈活的日期運算函數
Date_Format() 返回一個格式化的日期或時間串
Day() 返回一個日期的天數部分
DayOfWeek() 返回一個日期是星期幾
Hour() 返回一個時間的小時部分
Minute() 返回一個時間的分鐘部分
Month() 返回一個日期的月份部分
Now() 返回當前的日期和時間
Second() 返回一個時間的秒部分
Time() 返回一個日期時間的時間部分
Year() 返回一個日期的年份部分

例子:

SELECT cust_id, order_num FROM orders WHERE Date(order_date) BETWEEN '2005-09-01' AND '2005-09-30';

即匹配的日期在2005年9月1日到2005年9月30日。需要注意的是,order_date是一個datetime類型,對它進行date部分的匹配時要用Date函數截取其date部分。

最后是數值處理函數:

函數 說明
Abs() 返回一個數的絕對值
Cos() 返回一個角度的余弦
Exp() 返回一個數的指數值
Mod() 返回除操作的余數
Pi() 返回圓周率
Rand() 返回一個隨機數
Sin() 返回一個角度的正弦
Sqrt() 返回一個數的平方根
Tan() 返回一個角度的正切

這個不用多說了吧

2.7 聚集函數

上面說的是對于一些數值的計算。有時候我們做數據分析時需要關注整個Dataset中某個特征的宏觀特點。(說人話:比如平均值、最大值最小值等)

函數 說明
Avg() 返回某列的平均值
Count() 返回某列的行數
Max() 返回某列的最大值
Min() 返回某列的最小值
Sum() 返回某列值之和

下面分別舉例說明:

SELECT AVG(prod_price) AS avg_price FROM products WHERE vend_id = 1003;

即得出vend_id為1003的產品的平均價格。

需要注意的是,Avg函數將自動忽略值為NULL的行。

Count函數也是類似

SELECT COUNT(cust_email) AS num_cust FROM Customers;

也將返回cust_email不為NULL的行數。Min,Max,Sum同理。

我們前面提到過用DISTINCT去重,這里也是一樣:

SELECT AVG(DISTINCT prod_price) AS avg_price FROM products WHERE vend_id = 1003;

得到的是每個商品的單價去重之后的平均價格。

聚集函數也可以組合使用:

SELECT COUNT(*) AS num_items, MIN(prod_price) AS price_min, MAX(prod_price) AS price_max, AVG(prod_price) AS price_avg FROM products;

2.8 分組

在前面的例子中,我們知道,如果想得到某一個vend_id的num_prods數據,我們可以這樣:

SELECT COUNT(*) AS num_prods FROM products WHERE vend_id = 1003;

如果我們想同時查看所有vend_id的num_prods,就需要分組處理:

SELECT vend_id, COUNT(*) AS num_prods FROM products GROUP BY vend_id;

輸出為兩列,一列是排序后的vend_id,一個是每個vend_id對應的num_prods數。

GROUP BY子句中需要注意的是:

  • GROUP BY子句可以包含任意數目的列,因而可以對分組進行嵌套。這種情況下數據將在最后指定的分組上進行匯總;
  • GROUP BY子句中的每一列都必須是檢索列或有效的表達式;
  • 大多數SQL實現不允許GROUP BY列帶有長度可變的數據類型;
  • 除聚集計算語句外,SELECT語句中的每一列都必須在GROUP BY子句中給出;
  • 如果分組列中包含具有NULL值的行,則NULL將作為一個單獨的分組;
  • GROUP BY分組必須出現在WHERE子句之后,ORDER BY子句之前。

除了用GROUP BY分組以外,SQL還支持過濾分組。這時候我們要用到HAVING子句:

SELECT cust_id, COUNT(*) AS orders FROM Orders GROUP BY cust_id HAVING COUNT(*) >= 2;

這樣就過濾出了訂單數>=2的分組。

  • HAVINGWHERE的區別:可以簡要的理解為HAVING在數據分組前進行過濾,WHERE在數據分組后進行過濾。

有些時候,HAVINGWHERE也可以同時使用:

SELECT vend_id, COUNT(*) AS num_prods FROM Products WHERE prod_price >= 4 GROUP BY vend_id HAVING COUNT(*) >= 2

WHERE子句先是過濾出了prod_price至少為4的行,HAVING子句又過濾出計數為2或2以上的分組。

既然分組支持過濾,那么一定支持排序:

SELECT order_num, COUNT(*) AS items FROM OrderItems GROUP BY order_num HAVING COUNT(*) >= 3;

如上過濾出了訂單數>=3的order_num。

SELECT order_num, COUNT(*) AS items FROM OrderItems GROUP BY order_num HAVING COUNT(*) >= 3 ORDER BY items, order_num;

加上一個ORDER BY子句,即按items、order_num從小到大排序。

到現在,我們總結一下SELECT子句使用的順序:

子句 說明 是否必須使用
SELECT 要返回的列或表達式
FROM 從中檢索數據的表 僅在表中選擇數據時使用
WHERE 行級過濾
GROUP BY 分組說明 僅在按組計算聚集時使用
HAVING 組級過濾
ORDER BY 輸出排序順序
2.9 子查詢

SQL還允許子查詢,即查詢中嵌套的查詢。

例如如下兩個SQL查詢:

SELECT order_num FROM OrderItems WHERE prod_id = 'RGAN01';
SELECT cust_id FROM Orders WHERE order_num IN (20007, 20008);

有兩個表,OrderItems和Orders。OrderItems中保存的是order_num和prod_id信息,而Orders中保存的是cust_id和order_num的信息。如果我們想根據prod_id查找cust_id的相關信息,除了使用兩次查找,還可以使用如下方式:

SELECT cust_id FROM Orders WHERE order IN (SELECT order_num FROM OrderItems WHERE prod_id = 'RGAN01');

除此之外,還可以作為計算字段使用子查詢。

SELECT cust_name, cust_state, (SELECT COUNT(*) FROM Orders WHERE Orders.cust_id = Customers.cust_id) AS orders FROM Customers ORDER BY cust_name;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容