一 了解SQL
1 數據庫基礎
- 數據庫(database):保存有組織的數據容器。
- 表(table):某種特定類型數據的結構化清單。表名實際由數據庫名和表名共同組成唯一字符串。
- 列(column):表中的一個字段。所有表都是由一個或多個列組成。
- 數據類型(datatype):所允許的數據類型。每個列都有相應的數據類型,限制或允許了該列中存儲的數據。
- 行(row):表中的一個記錄。
- 主鍵(primary key):一列或一組列,值能夠唯一標識表中的每一行。
- 唯一:任意兩行不能具有相同主鍵值
- 非空:每一行都必須有一個主鍵值且不為NULL
- final:主鍵列的值不允許修改或更新
- 非重用:主鍵值不能重用
2 什么是SQL
SQL為結構化查詢語言(Structured Query Language)的縮寫。SQL是一種專門用來與數據庫溝通的語言。
二 檢索數據
1 相關概念
- 關鍵字(keyword):作為SQL組成部分的保留字。不能作為表或列名。
- 大小寫:SQL語句不區分大小寫,可以將關鍵字用大寫表示區分。
- 結束:SQL語句的結束為分號(;)。
2 檢索列
-- 檢索單個列
SELECT prod_name FROM Products;
-- 檢索多個列
SELECT prod_id,prod_name,prod_price FROM Products;
-- 檢索所有列
SELECT * FROM Products;
注:檢索所有列會降低檢索和應用程序的性能。
3 檢索不同的值(去重)
SELECT DISTINCT vend_id FROM Products;
-- DISTINCT不能部分使用,下面的SQL只有當vend_id和prod_name都相同的兩行才不會被檢出。
SELECT DISTINCT vend_id,prod_name FROM Products;
4 限制結果(各種數據庫的實現不相同)
-- 前5條
-- MYSQL SQLite PostgreSQL
SELECT prod_name FROM Products LIMIT 5;
-- Oracle
SELECT prod_name FROM Products WHERE ROWNUM <= 5
/*
分隔條
*/
-- 跳過前5條
-- MYSQL SQLite PostgreSQL
SELECT prod_name FROM Products LIMIT 5 OFFSET 5;
-- MYSQL
SELECT prod_name FROM Products LIMIT 5, 5;
-- Oracle
SELECT prod_name FROM Products WHERE ROWNUM > 5 and ROWNUM <= 10;
三 排序檢索數據
1相關概念
- 子句(clause):有些子句是必需的(SELECT ... FROM tableName;),有些則不是(ORDER BY)。
2 排序數據(默認升序)
SELECT prod_name FROM Products ORDER BY prod_name;
注1:ORDER BY只能在SELECT語句最后一條子句。
注2:ORDER BY所使用的列不需要是顯示而選擇的列,可以選擇非檢索的列。
3 按照多個列排序
-- 先按照prod_price排序,當prod_price相同時,才按照prod_name排序。
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;
-- 先按照prod_price降序排,當prod_price相同時,才按照prod_name升序排。
SELECT prod_id,prod_price,prod_name FROM Products ORDER BY prod_price DESC,prod_name;
四 過濾數據
1 相關概念
- 過濾條件:數據庫中包含大量數據,很少需要檢索表中的所有行,所以通常會只檢索部分數據,檢索部分數據的條件。
2 WHERE子句
SELECT prod_name,prod_price FROM Products WHERE prod_price = 3.49;
3 WHERE子句操作符
(1)子句操作符
| 操作符 | 說明 |
|---|---|
| = | 等於 |
| <> 或 != | 不等於 |
| < | 小於 |
| > | 大於 |
| !< | 不小於 |
| !> | 不大於 |
| >= | 大於等於 |
| <= | 小於等於 |
| BETWEEN | 兩值之間 |
| IS NULL | 為NULL |
(2)過濾數據
-- 檢查單個值
SELECT prod_name,prod_price FROM Products WHERE prod_price < 10;
-- 不匹配檢查
SELECT vend_id,prod_name FROM Products WHERE vend_id != 'DLL01';
-- 范圍檢查
SELECT prod_name,prod_price FROM Products WHERE prod_price BETWEEN 5 and 10;
-- 空值檢查
SELECT prod_name FROM Products WHERE prod_price IS NULL;
五 高級數據過濾
1 相關概念
- 操作符(operator):用來連接或改變WHERE子句中子句的關鍵字。
- AND:檢索滿足所有給定過濾條件的行。
- OR:檢索滿足任一給定過濾條件的行。
- 圓括號:任何時候使用具有AND和OR操作符的WHERE子句,都應該使用圓括號明確地分組操作符。
- IN:WHERE子句中用來指定要匹配值的清單的關鍵字。
- NOT:WHERE子句中用來指定非匹配關鍵字。
2 組合WHERE語句
-- AND
SELECT prod_id,prod_price,prod_name FROM Products WHERE vend_id = 'DLLL01' AND prod_price <=4;
-- OR
SELECT prod_id,prod_price,prod_name FROM Products WHERE vend_id = 'DLLL01' OR vend_id = 'BRS01';
-- 求值順序
SELECT prod_name,prod_price WHERE (vend_id = 'DLL01' OR vend_id = 'BRS01') AND prod_price >=10;
/*
使用IN的好處
1.在很多合法選項時,IN操作符更清晰。
2.在很多AND和OR操作組合使用IN時,求值順序更容器管理
3.IN操作符比一組OR操作符執行更快
4.IN可以包含其他SELECT語句,能夠更動態建立WHERE語句。
*/
-- IN
SELECT prod_name,prod_price FROM Products WHERE vend_id IN ('DLL01','BRS01') ORDER BY prod_name;
-- NOT
SELECT prod_name,prod_price FROM Products WHERE vend_id NOT IN ('DLL01','BRS01') ORDER BY prod_name;
六 用通配符進行過濾
1 相關概念
- 通配符(wildcard):用於匹配值得一部分的特殊字符。
- 搜索模式(search pattern):由字面量、通配符或兩者組合構成的搜索條件。
- %通配符:0、1或多個字符,不匹配NULL。
- _通配符:只匹配單個字符。
- []通配符:指定一個字符集,必需匹配指定位置的一個字符。
2 LIKE操作符
-- % 通配符:0、1或多個字符,不匹配NULL
SELECT prod_id,prod_name FROM Products WHERE prod_name LIKE 'Fish%';
SELECT prod_id, prod_name ROM Products WHERE prod_name LIKE '%bean bag%';
-- 有些數據庫會用空格來填補字段的內容。如:prod_name為5個字符,'Fly' -> 'Fly ',這樣'F%y'就無法匹配到,解決方法為函數去除空格或'F%y%'
SELECT prod_name FROM Products WHERE prod_name LIKE 'F%y';
-- 搜索電子郵件地址
WHERE email LIKE 'b%@gmail.com';
-- _通配符:只匹配單個字符。
SELECT prod_id, prod_name FROM Products WHERE prod_name LIKE '__ inch teddy bear';
-- []通配符
SELECT cust_contact FROM Customers WHERE cust_contact LIKE '[JM]%' ORDER BY cust_contact;
七 創建計算字段
1 相關概念
- 字段(field):基本上與列的意思相同,字段一般與計算字段一起使用。
- 拼接(concatenate):將值聯結到一起(將一個值附加到另一個值)構成單個值。
- 刪除空格函數
- TRIM():刪除左右空格
- LTRIM():刪除左空格
- RTRIM():刪除右空格
- AS:別名的關鍵字
2 拼接字段
-- 大部分數據庫
SELECT vend_name + ' (' + vend_country + ')' FROM Vendors ORDER BY vend_name;
SELECT vend_name || ' (' || vend_country || ')' FROM Vendors ORDER BY vend_name;
-- MYSQL
SELECT Concat(vend_name,'(',vend_country,')') FROM Vendors ORDER BY vend_name;
3 別名
SELECT Concat(vend_name,'(',vend_country,')') AS vend_title FROM Vendors ORDER BY vend_name;
注1:別名可以是字符串,如:‘vend title’,不夠最后不使用多單詞的字符串,而是使用_分隔的單詞。
4 執行算術計算
-- 匯總價格
SELECT prod_id,quantity,item_price,quantity*item_price AS expanded_price FROM OrderItems WHERE order_num = 20008;
八 使用數據處理函數
1 相關概念
- 可移植性(portable):所編寫的代碼可以在多個系統上運行。
- 函數是否應該使用?可以使用,但需要有相應的注釋。
2 函數差異
| 函數 | 語法 |
|---|---|
| 截取字符串 | Oracle使用SUBSTR();MySQL使用SUBSTRING(str,start,end) |
| 數據類型轉換 | Oracle使用多個函數;MySQL使用CONVERT() |
| 獲取當前時間 | Oracle使用SYSDATE;MySQL使用CURDATE() |
3 使用
(1)文本處理函數
| 函數 | 說明 |
|---|---|
| LEFT(str,len) | 返回字符串左邊字符 |
| LENGTH(str) 或 DATALENGTH() 或 LEN() | 返回字符串的長度 |
| LOWER(st) UPPER(str) | 小寫 大寫 |
| LTRIM(str) TRIM(str) RTRIM(str) | 刪除左空格 刪除左右空格 刪除右空格 |
| SOUNDEX() | 返回字符串的SOUNDEX值 |
注1:上述加粗的部分代表MySQL可用函數。
注2:soundex是一個將任何文本串轉換為描述其語音表示的字母數字模式的算法。
SELECT SUBSTRING(prod_name,5,6) AS prod_new_id FROM products;
SELECT LEFT(prod_name,2) AS left_name FROM products;
SELECT LENGTH(prod_name) AS name_length FROM products;
SELECT LTRIM(prod_name) FROM products;
-- 尋找顧客聯系方式發音為Y San的顧客
SELECT * FROM customers WHERE soundex(cust_contact) = soundex('Y San');
(2)日期和時間處理函數
-- 尋找order_date為2012年的數據
-- Oracle
-- 方法1
SELECT order_num FROM Orders WHERE to_number(to_char(order_date, 'YYYY')) = 2012;
-- 方法2
SELECT order_num FROM Orders WHERE order_date BETWEEN to_date('01-01-2012') AND to_date('12-31-2012');
-- MySQL
SELECT order_num FROM Orders WHERE YEAR(order_date) = 2012;
具體列出MySQL的部分日期函數
| 函數 | 說明 |
|---|---|
| now() | 獲取當前日期+時間(date + time) |
| current_timestamp() | 獲取當前時間戳 |
| date_format(str,format);str_to_date(str,format) | date -> str;str -> date |
| to_days(date);from_days(days) | date -> 天數;天數 -> date |
| time_to_sec(time);sec_to_time(sec) | time -> sec;sec -> time |
| timediff(time1,time2) datediff(date1,date2) | 日期相減 |
| date_add(date,interval expr UNIT) | 加exper UNIT |
SELECT now()
SELECT CURRENT_TIMESTAMP
-- 2019-02-14 15:22:00
SELECT DATE_FORMAT(now(),'%Y-%m-%d %H:%i:%s')
SELECT STR_TO_DATE('20190214 15:22:00','%Y%m%d %H:%i:%s');
SELECT DATE_ADD(now(),INTERVAL 1 day)
(3)數值處理函數
| 函數 | 說明 |
|---|---|
| ABS(x) | 絕對值 |
| COS(x) SIN(x)TAN(x) | 余弦值 正弦值 正切值 |
| EXP(x) | 指數值 |
| PI() | 圓周率 |
| SQRT(x) | 平方根 |
| CEIL(x) FLOOR(x) ROUND(x) | 最大整數值 最小整數值 四舍五入 |
| TRUNCATE(x,y) | 保留y位小數值 |
| RAND() | 0~1隨機數 |
| MOD(x,y) | x mod y |
SELECT ABS(-11);
-- 0.5
SELECT COS(PI()/180*60)
SELECT SIN(PI()/180*30)
-- 6
SELECT ceil(5.1)
-- 5
SELECT FLOOR(5.1)
-- 5
SELECT ROUND(5.1)
-- 1.12
SELECT TRUNCATE(1.12345,2)
九 匯總數據
1 聚合函數
| 函數 | 說明 |
|---|---|
| AVG() | 某列的平均值,忽略NULL值 |
| COUNT() | 某列的行數 |
| MAX() MIN() | 某列最大值 最小值 |
| SUM() | 某列求和 |
SELECT AVG(prod_price) AS avg_price FROM Products WHERE vend_id = 'DLL01';
-- COUNT(*):計算NULL值
-- COUNT(column):忽略NULL值
SELECT COUNT(*) AS num_cust FROM Customers;
-- MAX用於找出最大數值或日期值,如果用於文本則找出排序后的最后一行,忽略NULL值。
SELECT MAX(prod_price) AS max_price FROM Products;
-- 返回訂單中所有物品數量之和,忽略NULL值。
SELECT SUM(quantity) AS items_ordered FROM OrderItems WHERE order_num = 20005;
2 聚合不同值
- 默認聚合所有值
- DISTINCT則聚合不同的值
SELECT AVG(DISTINCT prod_price) AS avg_price FROM Products WHERE vend_id = 'DLL01';
3 組合聚合函數
SELECT COUNT(*) AS num_items,MIN(prod_price) AS price_min,MAX(prod_price) AS price_max, AVG(prod_price) AS price_avgFROM Products;
十 分組數據
1 創建分組
- GROUP BY子句可包含任意數目的列,因而可以對分組進行嵌套,進行更細致的分組。
- 如果在GROUP BY子句中嵌套了分組,數據將在最后指定的分組上進行匯總。
- GROUP BY子句中列出的每一列都必須是檢索列或有效的表達式(但不能是聚集函數)。
- 大多數SQL實現不允許GROUP BY列帶有長度可變的數據類型。
- SELECT語句中的每一列都必須在GROUP BY子句中給出。
- 如果分組列中包含具有NULL值的行,則NULL將作為一個分組返回。
- GROUP BY子句必須出現在WHERE子句之后,ORDER BY子句之前。
-- 安裝vend_id分組
SELECT vend_id, COUNT(*) AS num_prods FROM Products GROUP BY vend_id;
SELECT vend_id,prod_name,COUNT(vend_id) AS count FROM products GROUP BY vend_id,prod_name;
2 過濾分組
-- 對分組后的結果進行排序
SELECT cust_id, COUNT(*) AS orders FROM Orders WHERE prod_price >= 4 GROUP BY cust_id HAVING COUNT(*) >= 2;
3 分組和排序
| ORDER BY | GROUP BY |
|---|---|
| 對產出的輸出排序 | 對行分組,但輸出可能不是分組的順序 |
| 任意列都可以(甚至非檢出的列也行) | 只可能使用選擇列或表達式列,而且必須使用每個選擇列表達式 |
| 不一定需要 | 如果與聚集函數一起使用列(或表達式),則必須使用 |
-- 由於分組后的順序不確定,所以最后加上ORDER BY排序
SELECT order_num, COUNT(*) AS items FROM OrderItems GROUP BY order_num HAVING COUNT(*) >= 3 ORDER BY items, order_num;
4 SELECT子句順序
| 子句 | 說明 | 是否必須使用 |
|---|---|---|
| SELECT | 返回列或表達式 | 是 |
| FROM | 從中檢索數據的表 | 僅從表中檢索數據時需要 |
| WHERE | 行級過濾 | 否 |
| GROUP BY | 分組說明 | 僅在按組計算聚合時使用 |
| HAVING | 分組過濾 | 否 |
| ORDER BY | 輸出排序 | 否 |
十一 子查詢
1 相關概念
- 查詢(Query):任何SQL語句都是查詢,一般指的是SELECT。
- 子查詢(SubQuery):嵌套在其他查詢中的查詢。
- 完全限定列名:表明.列名
2 子查詢
-- 查看哪位顧客購買了RGAN01商品
SELECT cust_id FROM Orders WHERE order_num
IN (SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01');
注1:子查詢的SELECT語句只能查詢單個列。
注2:子查詢可能影響性能。
3 計算字段使用子查詢
-- 查詢顧客購下了多少訂單
SELECT cust_name,
cust_state,
(SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
ORDER BY cust_name;
十二 聯結表
1 相關概念
(1)關系表
- Vendors表包含所有供應商信息,每個供應商占一行,具有唯一的標識。此標識稱為主鍵(primary key),可以是供應商ID或任何其他唯一值。Products表只存儲產品信息,除了存儲供應商ID(Vendors表的主鍵)外,它不存儲其他有關供應商的信息。Vendors表的主鍵將Vendors表與Products表關聯,利用供應商ID能從Vendors表中找出相應供應商的詳細信息。
- 好處:
- 供應商信息不重復,節省時間和空間。
- 如果供應商信息變動只需要更新Venders表中的單個記錄。
- 由於數據不重復,數據一致,處理數據和生成報表更簡單。
(2)可伸縮
能夠適應不斷增加的工作量而不失敗。設計良好的數據庫或應用程序稱為可伸縮性好(scale well)。
2 創建聯結
(1)笛卡爾積
- 笛卡兒積(cartesian product):由沒有聯結條件的表關系返回的結果為笛卡兒積。檢索出的行的數目將是第一個表中的行數乘以第二個表中的行數。
SELECT vend_name,prod_name,prod_price
FROM Venders,Products
WHERE Venders.vend_id = Products.vend_id;
(2)內聯積
-- 等同於上面,只是語法不同
SELECT vend_name, prod_name, prod_price
FROM Vendors INNER JOIN Products
ON Vendors.vend_id = Products.vend_id;
(3)聯結多個表
-- 顯示訂單20007中的物品
SELECT prod_name, vend_name, prod_price, quantity
FROM OrderItems, Products, Vendors
WHERE Products.vend_id = Vendors.vend_id
AND OrderItems.prod_id = Products.prod_id
AND order_num = 20007;
-- 子查詢
SELECT cust_name, cust_contact
FROM Customers
WHERE cust_id IN (SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01'));
-- 聯結方式消除子查詢
SELECT cust_name,cust_contact
FROM Customers,Orders,OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND Orders.order_num = OrderItems.order_num
AND OrderItems.prod_id = 'RGAN01
注1:性能:DBMS在運行時關聯指定的每個表,以處理聯結。這種處理可能非常耗費資源,因此應該注意,不要聯結不必要的表。
注2:聯結中表的最大數目:實際上許多DBMS都有限制
十三 創建高級聯結
1 概念
- 表別名:使用AS關鍵字。
- 聯結(join):自聯結(self-join)、自然聯結(natural join)和外聯結(outer join)。
2 自聯結
-- 子查詢
SELECT cust_id, cust_name, cust_contact
FROM Customers
WHERE cust_name = (SELECT cust_name
FROM Customers
WHERE cust_contact = 'Jim Jones');
-- 子聯結
SELECT cust_id,cust_name,cust_contact
FROM Customers AS c1,Customers AS c2
WHERE c1.cust_name = c2.cust_name
AND c2.cust_contact = 'Jim Jones';
注:子聯結比子查詢查詢快得多。
3 自然聯結
-- 去除重復列
SELECT C.*, O.order_num, O.order_date,
OI.prod_id, OI.quantity, OI.item_price
FROM Customers AS C, Orders AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id
AND OI.order_num = O.order_num
AND prod_id = 'RGAN01';
4 外聯結
-- 對每個顧客下的訂單進行計數,包括那些至今尚未下訂單的顧客;
-- 列出所有產品以及訂購數量,包括沒有人訂購的產品;
-- 計算平均銷售規模,包括那些至今尚未下訂單的顧客。
-- 檢索所有顧客及其訂單,不包含沒有訂單的顧客
SELECT Customers.cust_id, Orders.order_num
FROM Customers INNER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
-- 要檢索包括沒有訂單顧客在內的所有顧客
SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
-- 全外聯結(full outer join),它檢索兩個表中的所有行並關聯那些可以關聯的行(可惜MySQL不支持)
SELECT Customers.cust_id, Orders.order_num
FROM Orders FULL OUTER JOIN Customers
ON Orders.cust_id = Customers.cust_id;
5 集合函數的聯結
-- 檢索所有顧客及每個顧客所下的訂單數
SELECT Customers.cust_id,
COUNT(Orders.order_num) AS num_ord
FROM Customers INNER JOIN Orders
ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
十四 組合查詢
1 相關概念
- 並(union):SQL也允許執行多個查詢(多條SELECT語句),並將結果作為一個查詢結果集返回。
- 任何具有多個WHERE子句的SELECT語句都可以作為一個組合查詢。
2 創建組合查詢
(1)使用UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
OR cust_name = 'Fun4All';
-- 修改為UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
(2)UNION規則
- UNION必須由兩條或兩條以上的SELECT語句組成,語句之間用關鍵字UNION分隔。
- UNION中的每個查詢必須包含相同的列、表達式或聚集函數。
- 列數據類型必須兼容:類型不必完全相同,但必須是DBMS可以隱含轉換的類型。
(3)包含或取消重復的行
-- 取消重復:UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
-- 包含重復:UNION ALL
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION ALL
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
(4)對組合結果排序
在用UNION組合查詢時,只能使用一條ORDER BY子句,它必須位於最后一條SELECT語句之后。
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All'
ORDER BY cust_name, cust_contact;
十五 插入數據
1 插入數據
-- 插入完整行
INSERT INTO Customers
VALUES('1000000006',
'Toy Land',
'123 Any Street'
'New York',
'NY',
'11111',
'USA',
NULL,
NULL);
-- 更安全的方式,同樣是填充所有列
INSERT INTO
Customers(cust_id,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email)
VALUES('1000000006',
'Toy Land',
'123 Any Street',
'New York',
'NY',
'11111',
'USA',
NULL,
NULL);
-- 插入部分行
-- 省略了cust_contact和cust_email,這兩列允許為NULL或者有默認值。
INSERT INTO
Customers(cust_id,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country)
VALUES('1000000006',
'Toy Land',
'123 Any Street',
'New York',
'NY',
'11111',
'USA');
-- 插入檢索出的數據,從CustNew檢索出來並插入到Customers
INSERT INTO
Customers(cust_id,
cust_contact,
cust_email,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country)
SELECT cust_id,
cust_contact,
cust_email,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country
FROM CustNew;
2 從一個表復制到另一個表
當需要測試新的SQL時,可以進行該操作復制一張新表進行測試。
-- 創建一個名為CustCopy的新表,並把Customers表的整個內容復制到新表中
CREATE TABLE CustCopy AS
SELECT * FROM Customers;
-- 只復制表結構
CREATE TABLE CustCopy LIKE Customers;
CREATE TABLE CustCopy AS SELECT * FROM Customers where 1=2
十六 更新和刪除數據
1 更新
UPDATE Customers
SET cust_contact = 'Sam Roberts',
cust_email = 'kim@thetoystore.com'
WHERE cust_id = '100000000005';
注:更新時不要省略過濾條件,除非你確定要更新所有行。
2 刪除
DELETE FROM Customers
WHERE cust_id = '100000000005';
-- 清空表
TRUNCATE TABLE Customers;
注:刪除時不要省略過濾條件,除非你確定要刪除所有行。
十七 創建和操縱表
1 創建表
(1)MySQL數據格式
| MySQL數據類型 | 含義 |
|---|---|
| tinyint(m) | 1個字節 范圍(-128~127) |
| smallint(m) | 2個字節 范圍(-32768~32767) |
| mediumint(m) | 3個字節 范圍(-8388608~8388607) |
| int(m) | 4個字節 范圍(-2147483648~2147483647) |
| float(m.d) | 單精度浮點型 8位精度(4字節) m總個數,d小數位,如:float(5,3) 123.4567 => 123.457 |
| double(m,d) | 雙精度浮點型 16位精度(8字節) m總個數,d小數位 |
| decimal(m,d) | 定點數為精確值,浮點值為近似值,m<65 是總個數,d<30 且 d<m 是小數位 |
| char(n) | 固定長度,最多255個字符,空格填充,查詢時取出空格,占n字節。 |
| varchar(n) | 可變長度,最多65535個字符,占實際+1(<=255)或2(大於255)字節但不超過n,可直接創建索引。 |
| tinytext | 可變長度,最多255個字符 |
| text | 可變長度,最多65535個字符,占實際+2字節,需要指定前多少字符創建索引。 |
| mediumtext | 可變長度,最多2的24次方-1個字符 |
| longtext | 可變長度,最多2的32次方-1個字符 |
| date | 日期 '2008-12-2' |
| time | 時間 '12:25:36' |
| datetime | 日期時間 '2008-12-2 22:06:44' |
| timestamp | 自動存儲記錄修改時間 |
注:整型取值范圍如果加了 unsigned,則最大值翻倍
(2)創建
-- 基礎
CREATE TABLE Products
(
prod_id CHAR(10) NOT NULL,
vend_id CHAR(10) NOT NULL,
prod_name CHAR(254) NOT NULL,
prod_price DECIMAL(8,2) NOT NULL,
prod_des TEXT NULL
);
-- 使用NULL
CREATE TABLE Orders
(
order_num INT NOT NULL,
order_date DATETIME NOT NULL,
cust_id CHAR(10) NOT NULL
);
CREATE TABLE Vendors
(
vend_id CHAR(10) NOT NULL,
vend_name CHAR(50) NOT NULL,
vend_address CHAR(50) ,
vend_city CHAR(50) ,
vend_state CHAR(5) ,
vend_zip CHAR(10) ,
vend_country CHAR(50)
);
-- 指定默認值
CREATE TABLE OrderItems
(
order_num INT NOT NULL,
order_item INT NOT NULL,
prod_id CHAR(10) NOT NULL,
quantity INT NOT NULL DEFAULT 1,
item_price DECIMAL(8,2) NOT NULL
);
-- 創建Tasks表
CREATE TABLE IF NOT EXISTS Tasks (
task_id INT(11) NOT NULL AUTO_INCREMENT,
subject VARCHAR(45),
start_date DATE,
end_date DATE,
description VARCHAR(200),
PRIMARY KEY (task_id)
) ENGINE = InnoDB;
2 更新表
-- 新增字段
ALTER TABLE Venders
ADD vend_phone CHAR(20);
-- 刪除字段
ALTER TABLE Venders
DROP COLUMN vend_phone;
3 刪除表
DROP TABLE CustCopy;
4 重命名
ALTER TABLE CustCopy RENAME TO CustCopyTest;
十八 使用視圖
1 視圖
(1)基礎
SELECT cust_name, cust_contact
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num
AND prod_id = 'RGAN01';
-- ProductCustomers為視圖
SELECT cust_name, cust_contact
FROM ProductCustomers
WHERE prod_id = 'RGAN01';
(2)好處
- 重用SQL語句
- 簡化復雜的SQL操作。
- 使用表的一部分而不是整個表。
- 保護數據。授予用戶訪問表的特定部分的權限,而不是整個表的訪問權限。
- 更改數據格式和表示。
(3)視圖的規則和限制
- 與表一樣,視圖必須唯一命名
- 創建視圖,必須具有足夠的訪問權限。
- 視圖可以嵌套,即可以利用從其他視圖中檢索數據的查詢來構造視圖。
- 許多DBMS禁止在視圖查詢中使用ORDER BY子句。
- 有些DBMS要求對返回的所有列進行命名,如果列是計算字段,則需要使用別名。
- 視圖不能索引,也不能有關聯的觸發器或默認值
- 有些DBMS把視圖作為只讀的查詢,這表示可以從視圖檢索數據,但不能將數據寫回底層表。
- 有些DBMS允許創建這樣的視圖,它不能進行導致行不再屬於視圖的插入或更新。
2 創建視圖
-- 創建視圖簡化聯結
CREATE VIEW ProductCustomers AS
SELECT cust_name, cust_contact, prod_id
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
AND OrderItems.order_num = Orders.order_num;
SELECT cust_name, cust_contact
FROM ProductCustomers
WHERE prod_id = 'RGAN01';
-- 重新格式化檢索出的數據
SELECT CONCAT(RTRIM(vend_name),' (',RTRIM(vend_country),')')
AS vend_title
FROM Vendors
ORDER BY vend_name;
SELECT *
FROM VendorLocations;
-- 使用視圖和計算字段
CREATE VIEW OrderItemsExpanded AS
SELECT order_num,
prod_id,
quantity,
item_price,
quantity*item_price AS expanded_price
FROM OrderItems;
SELECT *
FROM OrderItemsExpanded
WHERE order_num = 20008;
十九 存儲過程
1 使用場景
經常會有一些復雜的操作需要多條語句才能完成。例如以下的情形:
- 為了處理訂單,必須核對以保證庫存中有相應的物品。
- 如果物品有庫存,需要預定,不再出售給別的人,並且減少物品數據以反映正確的庫存量。
- 庫存中沒有的物品需要訂購,這需要與供應商進行某種交互。
- 關於哪些物品入庫(並且可以立即發貨)和哪些物品退訂,需要通知相應的顧客。
2 好處
- 通過把處理封裝在一個易用的單元中,可以簡化復雜的操作。
- 由於不要求反復建立一系列處理步驟,因而保證了數據的一致性。
- 簡化對變動的管理。如果表名、列名或業務邏輯(或別的內容)有變化,那么只需要更改存儲過程的代碼。
- 因為存儲過程通常以編譯過的形式存儲,所以DBMS處理命令的工作較少,提高了性能。
- 存在一些只能用在單個請求中的SQL元素和特性,存儲過程可以使用它們來編寫功能更強更靈活的代碼。
簡言之,簡單、安全、高性能。
3 執行存儲過程
-- EXECUTE 存儲過程(args)
-- 驗證傳遞數據、生成主鍵唯一ID、將新產品插入到Products表中
EXECUTE AddNewProduct(
'JTS01',
'Stuffed Eiffel Tower',
6.49,
'Plush stuffed toy with the text La➥Tour Eiffel in red white and blue'
);
4 創建存儲過程
(1)基礎創建和調用
-- 創建刪除商品的存儲過程
mysql> delimiter $$
mysql> CREATE PROCEDURE delete_product(IN p_prod_id int)
-> BEGIN
-> DELETE FROM products
-> WHERE prod_id = p_prod_id;
-> END $$
-- 調用存儲過程
mysql> CALL delete_product(1);
(2)進階
-- 存儲過程體
label1: BEGIN
label2: BEGIN
label3: BEGIN
statements;
END label3 ;
END label2;
END label1
-- 參數說明
CREATE PROCEDURE 存儲過程名([[IN |OUT |INOUT ] 參數名 數據類形...])
-- demo
mysql > DELIMITER //
mysql > CREATE PROCEDURE update_and_return(INOUT p_prod_id int,IN p_test int)
-> BEGIN
-> UPDATE products
-> SET vend_id = p_test
-> WHERE prod_id = p_prod_id;
-> SELECT vend_id
-> FROM products
-> WHERE prod_id = p_prod_id;
-> SET p_prod_id = 4;
-> END
-> //
mysql > DELIMITER ;
mysql > SET @Y = 3;
mysql > CALL update_and_return(@Y,100); // 100
mysql > SELECT @Y; // 4
-- 條件語句
mysql > DELIMITER //
mysql > CREATE PROCEDURE proc2(IN parameter int)
-> begin
-- declare 聲明變量
-> declare var int;
-> set var=parameter+1;
-> if var=0 then
-> insert into t values(17);
-> end if;
-> if parameter=0 then
-> update t set s1=s1+1;
-> else
-> update t set s1=s1+2;
-> end if;
-> end;
-> //
mysql > DELIMITER ;
-- 循環語句
mysql > DELIMITER //
mysql > CREATE PROCEDURE proc4()
-> begin
-> declare var int;
-> set var=0;
-> while var<6 do
-> insert into t values(var);
-> set var=var+1;
-> end while;
-> end;
-> //
mysql > DELIMITER ;
二十 管理事務處理
1 事務處理
(1)定義
事務處理是一種機制,用來管理必須成批執行的SQL操作,保證數據庫不包含不完整的操作結果。
(2)相關術語
- 事務(transaction)指一組SQL語句
- 回退(rollback)指撤銷指定SQL語句的過程
- 提交(commit)指未存儲的SQL語句結果寫入數據庫表
- 保留點(savepoint)指事務處理中設置的臨時占位符(placeholder),可以對它發布回退
(3)可回退的語句
事務處理用來管理INSERT、UPDATE和DELETE語句。
不能回退SELECT語句,也不能回退CREATE或DROP操作。
(4)事務的隔離級別
- 原子性:一個事務中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節。
- 一致性:在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞。
- 隔離性:數據庫允許多個並發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務並發執行時由於交叉執行而導致數據的不一致。
- 持久性:事務處理結束后,對數據的修改就是永久的,即便系統故障也不會丟失。
(5)事務並發問題
- 臟讀:事務A讀取了事務B更新的數據,然后B回滾操作,那么A讀取到的數據是臟數據
- 不可重復讀:事務 A 多次讀取同一數據,事務 B 在事務A多次讀取的過程中,對數據作了更新並提交,導致事務A多次讀取同一數據時,結果不一致。
- 幻讀:系統管理員A將數據庫中所有學生的成績從具體分數改為ABCDE等級,但是系統管理員B就在這個時候插入了一條具體分數的記錄,當系統管理員A改結束后發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀。
小結:不可重復讀的和幻讀很容易混淆,不可重復讀側重於修改,幻讀側重於新增或刪除。解決不可重復讀的問題只需鎖住滿足條件的行,解決幻讀需要鎖表(設置串行化隔離級別)。
(6)MySQL事務隔離級別
InnoDB支持下列四種隔離級別;Myisam不支持事務,只支持表鎖。
| 事務隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
|---|---|---|---|
| 讀未提交(read-uncommitted) | 是 | 是 | 是 |
| 不可重復讀(read-committed) | 否 | 是 | 是 |
| 可重復讀(repeatable-read) | 否 | 否 | 是 |
| 串行化(serializable) | 否 | 否 | 否 |
(7)InnoDB引擎鎖機制
-
表鎖:
- 寫鎖:用寫鎖鎖表,會阻塞其他事務讀和寫。
- 讀鎖:用讀鎖鎖表,會阻塞其他事務修改表數據。
-
行鎖:
- 共享鎖(S):允許事務去讀一行,阻止其他事務對該數據進行修改,select ... lock in share mode
- 排它鎖(X):允許事務去讀取更新數據,阻止其他事務對數據進行查詢或者修改,select ... for update
-
意向鎖:搭配行鎖,阻塞表鎖;申請S鎖時,先申請IS鎖;申請X鎖,先申請IS鎖,由數據庫自動完成。
- 意向共享鎖(IS):當一個事務要給一條數據加S鎖的時候,會先對數據所在的表先加上IS鎖,成功后才能加上S鎖
- 意向排它鎖(IX):當一個事務要給一條數據加X鎖的時候,會先對數據所在的表先加上IX鎖,成功后才能加上X鎖
注:意向鎖搭配行鎖使用來阻塞表鎖,如:
- 會話A申請了S鎖。
- 會話B希望申請整個表的寫鎖。
- 但是由於S鎖會申請IS鎖,所以會話B發現有IS鎖存在則阻塞直到會話A結束。
(8)鎖算法
- Record Lock(以下簡稱RL):單行鎖定。
- Gap Lock(以下簡稱GL):范圍鎖定,不包括當前行。
- Next-Key Lock(以下簡稱NKL):Record+Gap,鎖定一個范圍,包括范圍本身。
2 控制事務處理
- BEGIN或START TRANSACTION:顯式開啟一個事務
- COMMIT:提交事務,並已對數據庫進行的所有修改成為永久性的
- ROLLBACK:回滾將結束事務並撤銷正在進行的所有未提交的修改
- SAVEPOINT identifier:事務中創建保留點
- RELEASE SAVEPOINT identifier:刪除一個事務的保存點
- ROLLBACK TO identifier:回滾到一個事務保存點
- SET TRANSACTION:設置事務的隔離級別。其中innoDB存儲引擎的隔離級別4個。
/*
BEGIN:開啟一個事務
ROLLBACK:發生異常回滾
COMMIT:事務正常提交
*/
/*
SET AUTOCOMMIT=0:禁止自動提交
SET AUTOCOMMIT=1:開啟自動提交
*/
二十一 使用游標
1 游標
游標(cursor)是一個存儲在DBMS服務器上的數據庫查詢,它不是一條SELECT語句,而是被該語句檢索出來的結果集。
特性:
- 能夠標記游標為只讀。
- 能控制可以執行的定向操作(向前、向后、第一、最后、絕對位置、相對位置等)。
- 能標記某些列為可編輯,某些列為不可編輯。
- 規定范圍,使游標對創建它的特定請求(如存儲過程)或對所有請求可訪問。
- 指示DBMS對檢索出的數據(而不是指出表中活動數據)進行復制,使數據在游標打開和訪問期間不變化。
2 使用游標
-- 創建游標
DECLARE CustCursor CURSOR
FOR
SELECT * FROM Customers
WHERE cust_email IS NULL
-- 使用游標
DECLARE @cust_id CHAR(10),
@cust_name CHAR(50),
@cust_address CHAR(50),
@cust_city CHAR(50),
@cust_state CHAR(5),
@cust_zip CHAR(10),
@cust_country CHAR(50),
@cust_contact CHAR(50),
@cust_email CHAR(255)
-- 開啟游標
OPEN CustCursor
-- 獲取下一行
FETCH NEXT FROM CustCursor
INTO @cust_id, @cust_name, @cust_address,
@cust_city, @cust_state, @cust_zip,
@cust_country, @cust_contact, @cust_email
WHILE @@FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM CustCursor
INTO @cust_id, @cust_name, @cust_address,
@cust_city, @cust_state, @cust_zip,
@cust_country, @cust_contact, @cust_email
END
-- 關閉游標
CLOSE CustCursor
二十二 高級SQL特性
1 約束
(1)主鍵
- 任意兩行的主鍵值不相同
- 每行都具有一個主鍵值(不為NULL)
- 包含主機鍵值得列不修改或更新。
- 主鍵值不重用。
(2)外鍵
- 外鍵為表中一列,值為另一個表的主鍵
- 外鍵可以防止意外刪除,不過部分DBMS支持級聯刪除(cascading delete)
create table stu(
sid int UNSIGNED primary key auto_increment,
name varchar(20) not null)
TYPE=InnoDB charset=utf8;
create table sc(
scid int UNSIGNED primary key auto_increment,
sid int UNSIGNED not null,
score varchar(20) default '0',
index (sid), --外鍵必須加索引
FOREIGN KEY (sid) REFERENCES stu(sid)
ON DELETE CASCADE -- 級聯刪除
ON UPDATE CASCADE -- 級聯更新
)TYPE=InnoDB charset=utf8;
(3)唯一約束(UNIQUE)
- 表中包含多個唯一約束
- 唯一約束列可以包含NULL
- 唯一約束列可修改或更新
- 唯一約束列的值可重復使用
(4)默認值約束(DEFAULT value)
指定列的默認值
(5)非空約束(NOT NULL)
指定列不為空
2 索引
(1)概述
索引用來排序數據以加快搜索和排序操作的速度。
可以在一個或多個列上定義索引,使DBMS保存其內容的一個排過序的列表。
開始創建索引前,應該記住以下內容:
- 索引改善檢索操作的性能,但降低了數據插入、修改和刪除的性能。
- 索引數據可能要占用大量的存儲空間。
- 並非所有數據都適合做索引。取值不多的數據(如州)不如具有更多可能值的數據(如姓或名),能通過索引得到那么多的好處。
- 索引用於數據過濾和數據排序。
- 可以在索引中定義多個列(例如,州 + 城市)。這樣的索引僅在以州加城市的順序排序時有用。如果想按城市排序,則這種索引沒有用處。
注:索引的效率隨表數據的增加或改變而變化。許多數據庫管理員發現,過去創建的某個理想的索引經過幾個月的數據處理后可能變得不再理想了。最好定期檢查索引,並根據需要對索引進行調整。
(2)使用
CREATE INDEX prod_name_ind
ON PRODUCTS (prod_name);
(3)索引分類
- 普通索引index :加速查找
- 唯一索引
- 主鍵索引:primary key :加速查找+約束(不為空且唯一)唯
- 一索引:unique:加速查找+約束 (唯一)
- 聯合索引
- primary key(id,name):聯合主鍵索引
- unique(id,name):聯合唯一索引
- index(id,name):聯合普通索引
- 全文索引fulltext :用於搜索很長一篇文章的時候,效果最好。
(4)索引類型
- hash類型的索引:查詢單條快,范圍查詢慢
- btree類型的索引:b+樹,層數越多,數據量指數級增長(我們就用它,因為innodb默認支持它)
注:指定全文索引時,無需指定索引類型。
3 觸發器
觸發器是特殊的存儲過程,它在特定的數據庫活動發生時自動執行。觸發器可以與特定表上的INSERT、UPDATE和DELETE操作(或組合)相關聯。
觸發器內的代碼具有以下數據的訪問權:
- INSERT操作中的所有新數據;
- UPDATE操作中的所有新數據和舊數據;
- DELETE操作中刪除的數據。
常見用途:
- 保證數據一致
- 基於某個表的變動在其他表上執行活動
- 進行額外的驗證並根據需要回退數據
- 計算 計算列的值或更新時間戳等
-- 模板
DROP TRIGGER IF EXISTS triggerName;
create trigger triggerName
after/before insert/update/delete on tableName
for each row
begin
sql語句;
end;
-- 例:更新時間戳
DROP TRIGGER IF EXISTS `upd_info`;
create trigger upd_info
after insert on StuCost for each row
begin
update StuCostbyHour set HourCost = HourCost + new.Cost
where (TimeJD = hour(new.RecordTime) + 1) and date_format(new.RecordTime, '%Y-%m-%d') = date_format(RecordTime, '%Y-%m-%d');
end;
4 安全
- 對數據庫管理功能(創建表、更改或刪除已存在的表等)的訪問;
- 對特定數據庫或表的訪問;
- 訪問的類型(只讀、對特定列的訪問等);
- 僅通過視圖或存儲過程對表進行訪問;
- 創建多層次的安全措施,從而允許多種基於登錄的訪問和控制;
- 限制管理用戶賬號的能力。
二十三 更多
-- 刪除重復行:只有last_name、first_name和sex都相同才視為相同
CREATE TABLE tmp SELECT last_name, first_name, sex FROM person_tbl GROUP BY last_name, first_name, sex;
DROP TABLE person_tbl;
ALTER TABLE tmp RENAME TO person_tbl;
參考:
-
《SQL必知必會》
-
Runoob的MySQL教程
