關系型數據庫和SQL
- SQL語言的三個部分
- DML:Data Manipulation Language,數據操縱語言,檢索、修改、增加、刪除數據庫(表)中的數據
- DDL:Data Definition Language,數據定義語言,創建和修改數據庫(表)本身
- DCL:Data Control Language,維護數據庫安全
- 關系型數據庫基本術語
- relational(關系):表示各表彼此關聯
- record(記錄):表中的行(row)
- field(字段):表中的列(column)
- primary key:表的主鍵,通常為自增型(auto-increment),本身沒有特殊含義,只用于保證每一行都有一個唯一的值
- foreign key:表的外鍵,確保這一列有一個有效的值,通常會把某個其他表的共同列,通常是主鍵作為外鍵,比如訂單表中的客戶列
- column(field)的數據類型(更詳細的數據類型介紹參見SQL數據類型)
- 數字
- bit:位,只允許0和1
- integer:整數,不含小數位
- decimal:浮點數,含小數位
- real number:實數
- 字符:string/character string類型,在SQL語句中需要用引號括起來
- 日期/時間:用于表示日期和時間,在SQL語句中需要用引號括起來,允許對所涉及的日期進行特殊運算
- NULL:空值,非數據類型,而是在允許包含空值的列中表示空置
基本數據檢索
- SQL語句不區分大小寫,且可以寫成任意多行
- 可以把重要關鍵字作為單獨一行凸顯語義
- 從表中選擇所有:
SELECT * FROM tablename
- 指定列:
SELECT columnname FROM tablename
- 指定多個列:
SELECT column1, column2 FROM tablename
-
帶空格的列名:Microsoft SQL Server使用方括號
[]
,MySQL使用重音符`,Oracle使用雙引號
計算字段和別名
- 計算字段
- 直接量:與表中數據沒有任何關系的值叫做literal value(直接量),字符串直接量需要加引號,數字直接量不需要
- 算數運算:允許使用列數據與直接量或其它列數據進行加減乘除運算,比如
SELECT QuantityPurchased * PricePerItem FROM Orders
- 連接字段:把字符數據連接或組合到一起,Microsoft SQL Server使用加號
+
,MySQL使用CONCAT()
函數連接字符串,Oracle使用雙豎線||
- 別名
- 列的別名:用于修改列(表頭)標題或為計算字段提供列(表頭)標題,比如
SELECT f_n AS 'first_name' from customers
- 表的別名:通常有三種情況需要修改表名稱,a.不好理解或復雜的表名,b.從多個表中進行選擇,c.使用子查詢;
SELECE something from someshittablename AS another_table_name
- 列的別名:用于修改列(表頭)標題或為計算字段提供列(表頭)標題,比如
使用函數
- Scalar function:標量函數,針對單行中的數據執行
- Aggregate function:聚合函數,針對較大的數據集合進行操作
- 字符函數
-
LEFT/RIGHT (string, numberOfCharactors)
:從左/右取字符串數據的指定位數,在Oracle中以SUBSTR替代 -
SUBSTRING (string, start, end)
:取得字符串數據的子字符串,在Oracle中以SUBSTR替代 -
LTRIM/RTRIM (string)
:刪除字符串數據左側/右側的空格 -
CONCAT (string1, string2, string3 ...)
:拼接多個字符串,Oracle中只允許拼接兩個字符串 -
UPPER/LOWER (string)
:返回字符串的大/小寫
-
- 復合函數:函數的嵌套使用被稱為復合函數,比如
RIGHT(RTRIM(something)) AS 'something'
- 日期時間函數
-
GETDATE/NOW/CURRENT_DATE ()
:三個函數都用于獲取當前時間,對應Microsoft SQL Server/MySql/Oracle三家數據庫的實現 -
DATEPART (date_part, date_value)
:單獨返回某個部分的時間,date_part
為需要返回的時間部分,date_value
為原始時間,MySQL的實現為DATE_FORMAT(date_value, date_format)
,date_value
為原始時間,date_format
為類似于%d
這樣的格式用于告訴函數需要返回哪部分時間,date_part
的有效值為:year/quarter/month/dayofyear/day/month/hour/minute/second -
DATEDIFF (date_part, start_date, end_date)
:用于計算任意兩個不同日期間相差的時間,在MySQL中該函數之允許計算天數差異,所以date_part
參數不可用,僅需要傳入兩個日期即可
-
- 數值函數(數學函數)
-
ROUND (NumbericValue, DecimalPlaces)
:對任意數進行四舍五入,NumbericValue
表示要四舍五入的數,DecimalPlaces
表示從第幾位開始四舍五入(即需要保留到第幾位),以十分位為0,向左為負數,向右為正數 -
RAND ([seed])
:產生一個隨機數 ,可選的seed
參數為一個整數,用于每次返回相同的值 -
PI ()
:返回數學運算中的pi值
-
- 轉換函數
-
CAST (expression AS data_type)
:將數據從一種類型轉換為另一種類型,expression
表示數據或函數表達式,data_type
表示需要轉換到的數據類型,一般情況下SQL在做計算時會進行自動的類型轉換,所以很少用到這個函數,它的典型使用場景是當一個日期被保存成了字符串,需要轉換為真正的日期數據:CAST('2017-5-1', AS DATETIME)
,Oracle中該函數的參數形式會不一樣 -
ISNULL/IFNULL/NVL (column_data_maybe_null, if_null_will_use_this_data )
:將NULL值轉換為一個有意義的值,對應Microsoft SQL Server/MySql/Oracle三家數據庫的實現
-
排序數據
排序數據的語法如下:
SELECT
column1,
column2
FROM table1, table2
ORDER BY column3, column2 DESC
-
ORDER BY
句子總是在FROM
子句之后,FROM
子句總是在SELECT
關鍵字之后 -
SELECT
和ORDER BY
后面指定的列,可能是完全不同的一些列 - 使用關鍵字
ASC
和DESC
來升序/降序排列 -
ORDER BY
后指定了多列,則首先按照第一列排序,如遇相同則相同的行按第二列排序,以此類推 - 根據計算字段排序時,如果計算字段已經出現在
SELECT
關鍵字后,則只需要在ORDER BY
子句中指定計算字段的別名即可(經測試在MySQL中如果計算字段別名帶有空格則暫時無法在此引用,因為不能使用引號),如果計算字段沒有出現在SELECT
關鍵字后,則可直接在ORDER BY
子句中指定一個計算字段,例如:SELECT title, rental_duration, rental_rate FROM film ORDER BY rental_duration * rental_rate DESC
- 按升序排列時,大部分的SQL數據庫都會是按
NULL
(Oracle中排在最后,可使用NULLS FIRST
關鍵字來強制最先)-數字-字符(字符中首先顯示數字字符,再顯示普通字符,除Oracle外不區分大小寫)來進行排序,反之亦然。
基于列的邏輯
- 基于列的邏輯主要用于根據邏輯條件改變想要展現給用戶的輸出
- 簡單格式:判斷某列的值為某個具體值后將其轉換為一個其它值
SELECT column1, column2
CASE column3
WHEN value1 THEN result1
WHEN value2 THEN value2
(repeat WHEN-THEN any number of times)
[ELSE defaul_result]
END
column4
FROM tablename
- 查詢格式:判斷一列或多列中的某個值是否符合某個條件而將其轉換為一個其它值并顯示在一列中
SELECT
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
(repeat WHEN-THEN any number of times)
[ELSE defaul_result]
END AS custom_column_name,
FROM tablename
# 最新版的MySQL語法與書中的語法有細微差別:
# ELSE子句最后不需要逗號
SELECT
title,
CASE
WHEN rental_duration = 3 THEN 'Three Day Left'
WHEN rental_rate = 0.99 THEN 'Cheapest'
ELSE 'Normal'
END AS 'Rental Status'
FROM film
基于行的邏輯
- 基于行的邏輯主要用于獲取滿足特定條件的數據
- 應用查詢條件
- SQL中的查詢條件從
WHERE
子句開始 -
WHERE
子句總是在FROM
和ORDER BY
子句之間,實際上任何“子句”都必須按照這個順序來SELECT columnList FROM tableList WHERE condition ORDER BY columnList
- SQL中的查詢條件從
-
WHERE
子句操作符,以下這些操作符都可以在基于列的邏輯CASE WHEN condition
語句中使用- 等于:
=
- 不等于:
<>
- 大于:
>
- 小于:
<
- 大于等于:
>=
- 小于等于:
<=
SELECT first_name, last_name FROM actor WHERE age > 18
- 等于:
- 限制行
- 使用
TOP/LIMIT/ROWNUM
(對應Microsoft SQL Server、MySQL和Oracle)限制行數(關鍵字TOP返回的行,并不是真的隨機樣本,而是根據數據庫中的物理存儲方式限定了前幾行數據而已)# Microsoft SQL Server SELECT TOP number columnList FROM table # MySQL SELECT columnList FROM table LIMIT number # Oracle SELECT columnList FROM table WHERE ROWNUM <= number
- 結合
SORT BY
子句做“Top N”查詢(基于特定分類,得到帶有最大/小值的一定數量的行)# 本月賣得最好的莎士比亞的三本書 # MySQL SELECT title AS 'Book Title', current_month_sale AS 'Quantity Sold' FROM books WHERE author = 'Shakespear' LIMIT 3 ORDER BY current_month_sale DESC # Oracle中的TOP N查詢需要用到子查詢,后文會細講 SELECT * FROM (SELECT title AS 'Book Title', current_month_sale AS 'Quantity Sold' FROM books ORDER BY current_month_sale) WHERE ROWNUM <= 3
- 使用
布爾邏輯(更復雜的基于行的邏輯)
- 使用與
AND
、或OR
、非NOT
三個關鍵字在WHERE
子句中表示布爾邏輯。與其它語言的計算順序一樣,AND
的優先級最高,OR
其次,NOT
優先級最低,也可以使用()
來改變三者的計算順序# 這個例子僅為展示布爾邏輯,實際使用不應該這么繞 SELECT first_name, last_name, age FROM actors WHERE NOT( (age < 18 OR age > 60) AND last_name = 'Jhon' )
- 還有兩個表示范圍的操作符
BETWEEN
和IN
,用于替代column >= range_bottom AND column <= range_top
和column = value1 OR column = value2
這樣的特例,簡化SQL語句的編寫# BETWEEN,等價于 age >= 18 AND age <= 60 SELECT first_name, last_name, age FROM actors WHERE age BETWEEN 18 AND 60 # IN,等價于 state = 'IL' AND state = 'NY' SELECT customer_name, state FROM orders WHERE state IN ('IL', 'NY')
- 使用
IS NULL
在WHERE
子句中判斷一個列是否為空,也可以與函數ISNULL(column, value)
結合使用# 選取重量為0或者沒有填寫重量的產品 SELECT product_description, weight FROM products WHERE weight = 0 OR weight IS NULL # 使用ISNULL等價的寫法 SELECE product_description, weight FROM products WHERE ISNULL(weight, 0) = 0 # IS NULL和ISNULL SELECT product_description, ISNULL(weight, 0) AS 'weight' FROM products WHERE weight = 0 OR weight IS NULL
模糊匹配
-
WHERE
子句可以使用LIKE
操作符來查找針對列值的某部分匹配- 包含某關鍵字:
SELECT title FROM film WHERE title LIKE '%love%'
- 以某關鍵字開頭:
SELECT title FROM film WHERE title LIKE 'love%'
- 以某關鍵字結尾
SELECT title FROM film WHERE title LIKE '%love'
- 包含某關鍵字但不以其開頭也不以其結尾(未能在MySQL 4中驗證,只驗證通過了單獨的不以某字符串開頭,或者不以某字符串結尾兩種情況)
SELECT title FROM film WHERE title LIKE '% love %'
- 包含某關鍵字:
-
符號 含義 % 任意個任意字符 _ 一個任意字符 [characterlist] 一個指定字符列表中的字符(在MySQL和Oracle中沒有) [^charactorlist] 一個非指定字符列表中的字符(在MySQL和Oracle中沒有) 符號 含義 % 任意個任意字符 _ 一個任意字符 [characterlist] 一個指定字符列表中的字符(在MySQL和Oracle中沒有) [^charactorlist] 一個非指定字符列表中的字符(在MySQL和Oracle中沒有) -
NOT
操作符可以和LIKE
操作符組合使用,例如SELECT first_name, last_name FROM actor WHERE first_name LIKE '%ARY%' AND last_name NOT LIKE '[MG]ARY'
- 按照讀音匹配(不常用也不好用)
-
SOUNDEX
函數:能夠輸出一個表示字符讀音的四字代碼(以首字母開頭,然后刪去剩余字符中所有元音和字母y,最后轉換為一個三位數的數字用于表示讀音,最后輸出類似S530
) -
DIFFERENCE
函數:可以和SOUNDEX
函數一起使用(僅Microsoft SQL Server支持),檢查兩個字符的SOUNDEX
相似度并返回一個表示讀音相近度的數字,(兩個字符的SOUNDEX
值共有四個字符,每有一個位置的字符相等,則結果自增1,所以DIFFERENCE
函數的返回值只有0到4五個可能的數字,越大越相近,越小越不同
-
匯總數據
- 消除重復:使用
DISTINCT
關鍵字來刪除輸出重復的行# 查看所有藝術家(沒有顯示相同藝術家的行) SELECT DISTINCT artist FROM songs ORDER BY artist # 查看所有藝術家和專輯的唯一組合(沒有顯示同一藝術家和同一專輯的行,每一行中藝術家和專輯的組合是唯一的) SELECT DISTINCT artist, album FROM songs ORDER BY artist, album
-
函數 解釋 SUM
合計、加總 AVG
平均值 MIN
最小值 MAX
最大值 COUNT
數量 函數 解釋 SUM
合計、加總 AVG
平均值 MIN
最小值 MAX
最大值 COUNT
數量 # 總值、均值、最大值、最小值 SELECT SUN(fee) AS 'Total Gym Fees' AVG(grade) AS 'Average Quiz Score' MIN(grade) AS 'Minimum Quiz Score' MAX(grade) AS 'Maximum Quiz Score' FROM grades WHERE grade_type = 'quiz' # 返回所有選中行的數目 SELECT COUNT(*) AS 'Count of Homework Rows' FROM grades WHERE grade_type = 'homework' # 返回指定列中存在值的行的數目 SELECT COUNT(grade) AS 'Count of Homework Rows' FROM grades WHERE grade_type = 'homework' # 與DISTINCT配合返回指定列中唯一值的行數 SELECT COUNT(DISTINCT fee_type) AS 'Number of Fee Types' FROM Fees
- 分組數據:以指定列為依據對所有選中行進行分組,重新劃分了行的顯示規則
- 單列分組
# 統計每個分級下的電影數量 SELECT rating, COUNT(rating) AS 'Rating Count' FROM film GROUP BY rating
- columnlist中的所有列,要么是
GROUP BY
子句中的列,要么是在聚合函數中使用的列,因為所有內容都在組中出現,不在組中的內容沒有辦法處理,這種情況下MySQL與其它兩種數據庫不同,它只會得出錯誤的結果,而不會報錯 - 多列分組:組的概念可以擴展,從而根據多列進行分組
# 統計不同租金率下的不同分級電影的數量 SELECT rating, rental_rate, COUNT(rating) AS 'Rating Count' FROM film GROUP BY rating, rental_rate
- 在沒有分組的情況下,聚合函數(
SUM
、AVG
、MIN
、MAX
、COUNT
)統計的是所有行的數據,在有分組的情況下,這些聚合函數則僅會統計組內的數據,當然實際上也是最終顯示的表的每一行的聚合 -
GROUP BY
子句中的columnlist順序沒有意義,但ORDER BY
子句中的順序是有意義的,一般按照排序的優先順序來列出這些列會很有幫助(也即SELECT
中的columnlist與ORDER BY
中的columnlist保持一致)
- 單列分組
- 基于分組應用查詢條件:
WHERE
子句中的查詢條件是針對單獨的行來應用的,如果存在GROUP BY
分組,就需要使用HAVING
關鍵字了# 查看分級中所有電影平均時長大于100分鐘的分級中電影的數量 SELECT rating AS '分級', COUNT(title) AS '電影數量', AVG(length) AS '平均時長' FROM film GROUP BY rating HAVING AVG(length) > 100 ORDER BY 電影數量 DESC
- 至此,
SELECT
語句的一般格式如下:SELECT columnlist FROM tablelist WHERE condition GROUP BY columnlist HAVING condition ORDER BY COLUMNLIST
用內連接來組合表
- 關系型數據庫最重要的成就是能夠把數據組織到任意多個相互關聯的表中,但同時這些又是彼此獨立的;人們可以分析業務實體然后進行適當的數據庫設計,這樣就可以具有最大的靈活性;關系型數據庫可以以任何你想要的方式把代表業務實體的表連接到一起,從而實現“關系”
-
類似“客戶”和“訂單”這樣兩個獨立的實體信息,至少應該要拆分到兩個表中(訂單表很有可能需要繼續拆分成多個表),可以使用實體關系圖(entity-relationship diagram)來表示可視化地表示兩個表以及他們之間存在的隱性關系,實體(entity)指的是表,關系(relationship)指的是這些表中數據元素之間所畫的線
實體關系圖 - 內連接
- 使用關鍵字
INNER JOIN
來指定想要連接的第二個表,使用ON
來指定兩個表的共同列由于共同列名稱是一樣的,所以需要在列名前面使用表名作為命名空間來區分兩個表中獨立的列# 簡單地依據customer_id將顧客表和訂單表拼接到一個表中 SELECT * FROM customers INNER JOIN orders ON customers.customer_id = orders.customer_id
- 內連接只會返回關聯的兩個表之間相匹配的數據,表在
FROM
和INNER JOIN
之間的順序僅會影響哪個表的列先顯示,不會影響行的順序 - SQL不是過程式語言,不會指定任務的先后順序,而只是指定需要的邏輯并讓數據庫內部機制去決定如何執行任務。
- 僅使用
FROM
和WHERE
也可以指定表的內連接,這是內連接的另一種范式,但因其沒有顯示地表示出連接的邏輯,所以不推薦使用(所以其實INNER JOIN ON
的唯一作用僅僅是表達語義而已)SELECT * FROM customers, orders WHERE customers.customer_id = orders.customer_id
- 可以通過顯式地指定表的別名和列的別名(注意Oracle中表的別名與其他兩個庫的區別,前文有提及),來去除內連接后的重復列或者只顯示需要的列,這是推薦的做法:
SELECT c.customer_id AS 'Customer Id', c.first_name AS 'First Name', c.last_name AS 'Last Name', o.order_id AS 'Order Id', o.quantity AS 'Quantity', o.price_per_item AS 'Price' FROM customers AS 'c', INNER JOIN
- 使用關鍵字
用外連接來組合表
- SQL中表連接的默認類型就是內連接,所以可以只使用
JOIN
來指定一個內連接 - 外連接有三種類型:左連接
LEFT OUTER JOIN
,右連接RIGHT OUTER JOIN
,全連接FULL OUTER JOIN
,其中關鍵字OUTER
并不是必須的。 -
連接類型 全稱 簡寫 用途 內連接 INNER JOIN
JOIN
兩個表都是主表(primary table),共同列中所有的行都必須同時在這兩個表中才會被選中 左連接 LEFT OUTER JOIN
LEFT JOIN
左表為主表,右表為從表(secondary table),選中共同列中所有在主表中的行,不管它是否出現在從表 右連接 RIGHT OUTER JOIN
RIGHT JOIN
左表為從表,右表為主表,規則同左連接 全連接 FULL OUTER JOIN
FULL JOIN
兩個表都是從表,共同列中的行只要出現在任意一個表中都會被選中 連接類型 全稱 簡寫 用途 內連接 INNER JOIN
JOIN
兩個表都是主表(primary table),共同列中所有的行都必須同時在這兩個表中才會被選中 左連接 LEFT OUTER JOIN
LEFT JOIN
左表為主表,右表為從表(secondary table),選中共同列中所有在主表中的行,不管它是否出現在從表 右連接 RIGHT OUTER JOIN
RIGHT JOIN
左表為從表,右表為主表,規則同左連接 全連接 FULL OUTER JOIN
FULL JOIN
兩個表都是從表,共同列中的行只要出現在任意一個表中都會被選中 - 在實體關系圖中,單向箭頭表示表之間的連接是單向的,箭頭終點的表中有一列所有行都能在箭頭起點的表中找到,但反過來則不一定,比如,不是所有的客戶都有訂單,且一個客戶可能有多個訂單,但所有的訂單都會有客戶信息(甚至可以說所有的訂單有且只有一個客戶信息),退貨信息與訂單的關系類似
- 當連接主表和從表時,我們需要主表中所有的行,即使在從表中的共同列沒有任何行與之匹配
- 使用
IS NOT NULL
和IS NULL
來過濾空行或顯示空行# 過濾了沒有訂單的客戶和有退貨的訂單 SELECT customers.first_name AS 'First Name', customers.last_name AS 'Last Name', orders.order_date AS 'Order Date', orders.order_amount AS 'Order Amt' FROM customers LEFT JOIN orders ON orders.customer_id = customers.customre_id LEFT JOIN refunds ON orders.order_id = refunds.order_id WHERE orders.order_id IS NOT NULL AND refunds.refund_id IS NULL ORDER BY customers.customer_id, orders.order_id
- 右連接與左連接唯一的不同就是主從表在關鍵字前后的位置,所以基本上沒有必要使用右連接,建議只使用左連接,因為人直覺上往往認為先列出的表更為重要
- 當設計有多個表的復雜
FROM
子句時,建議僅使用關鍵字LEFT JOIN
并且避免使用圓括號 - 全連接會顯示所有行,即使沒有在其他表中找到任何一個匹配。但在實際工作中很少會用到全連接,因為表之間的這種類型的關系是非常少見的。
自連接和視圖
- 自連接:處理那些本質上是自引用的表(表中的一列指向自己的另一列,比如員工表中的manager列指向自己的employee_id,員工的經理也是員工),為其創建多個視圖
- 可以使用四種表連接中的任意一種來實現自連接,唯二的區別就是
ON
子句中,非自連接的共同列來自兩個表,自連接的共同列來自同一個表,所以這時候需要在FROM
關鍵字和JOIN
關鍵字后為該表各自創建一個別名用以在ON
子句中進行區分
# 列出personnel表中所有員工的經理名字 SELECT employees.employee_name AS 'Employee Name', managers.employee_name AS 'Maneger Name' FROM personnel AS 'employees' LEFT JOIN personnel AS 'managers' ON employees.manager_id = managers._employee_id ORDER BY employee.employee_id
- 可以使用四種表連接中的任意一種來實現自連接,唯二的區別就是
- 視圖
- 視圖只是保存在數據庫中的
SELECT
語句,它不包含任何數據。 - 隨著時間的流逝,訪問數據的需求會有所變化,但有時很難去重新組織數據庫中的數據以滿足新的需求。視圖允許為數據庫中已經存在的數據創建新的虛擬視圖(或理解為虛擬的表)而無需重新組織數據,這為我們增加了始終能保持數據庫設計不斷更新的能力。
- 因為視圖沒有保存物理數據,所以在視圖中不能包含
ORDER BY
子句
- 視圖只是保存在數據庫中的
- 創建視圖
# 創建視圖的語法: CREATE VIEW view_name AS select_statement # 一個創建視圖的例子,注意不能有ORDER BY子句 CREATE VIEW customers_orders_refunds AS SELECT customers.first_name AS 'First Name', customers.last_name AS 'Last Name', orders.order_date AS 'Order Date', orders.order_amount AS 'Order Amt' FROM customers LEFT JOIN orders ON orders.customer_id = customers.customre_id LEFT JOIN refunds ON orders.order_id = refunds.order_id WHERE orders.order_id IS NOT NULL AND refunds.refund_id IS NULL
- 引用視圖
# 創建視圖 CREATE VIEW view_name AS select_statement # 引用視圖 SELECT * from view_name
- 當引用視圖中的列的時候,需要指定列的別名,而列的別名是在創建視圖時指定的
# 創建視圖 CREATE VIEW customers_view AS SELECT first_name AS 'First Name', last_name AS 'Last Name' FROM customers # 引用視圖中的列 SELECT `First Name`, `Last Name`, FROM customers_view WHERE `Last Name` = 'Lopez'
- 當引用視圖中的列的時候,需要指定列的別名,而列的別名是在創建視圖時指定的
- 視圖的優點
- 視圖可以減少復雜度:將復雜的
SELECT
語句封裝為一個視圖 - 視圖可以增加復用性:封裝那些總是相連的表
- 視圖可以正確地格式化數據:如果一個表中的某些數據總是需要格式化,可以將其封裝到視圖中
- 視圖可以創建計算的列:如果需要一個含有大量的計算字段的表,也可將其封裝到視圖中
- 視圖可以用來重新命名列的名稱:如果一個表中的列名總是需要重命名,可以將其封裝到視圖中
- 視圖可以創建數據子集:如果總是只需要看到某個表的某些子集,可以將它們封裝到不同的視圖
- 視圖可以用來加強安全性限制:如果一個表中的某些數據希望對某些用戶做訪問限制,可以使用視圖將它們挑出來然后僅將視圖的權限給那些用戶而不是整個表的權限
- 視圖可以減少復雜度:將復雜的
- 修改視圖:使用
ALTER
關鍵字修改一個已經創建的視圖,重新指定被封裝到其中的SELECT
語句# 整個被封裝到視圖的select語句都需要重新指定 ALTER VIEW view_name AS new_select_statement # 與Microsoft SQL Server和MySQL不同,Oracle在修改視圖之前,需要使用DROP VIEW view_name先刪除視圖
- 同樣,修改視圖與創建視圖一樣,只是修改了視圖的定義,它本身不會返回任何數據
- 刪除視圖:使用
DROP VIEW view_name
來刪除視圖
子查詢
- 包含在其他查詢中的查詢叫做子查詢,子查詢可以用在
SELECT
、INSERT
、UPDATE
、DELETE
語句 - 在
SELECT
語句中子查詢可以有三種用法:- 一個一般的
SELECT
語句格式如下:SELECT column_list FROM table_list WHERE condition GROUP BY column_list HAVING condition ORDER BY column_list
- 當子查詢是
table_list
的一部分時,它指定了一個數據源 - 當子查詢時
condition
的一部分時,它成為查詢條件的一部分 - 當子查詢是
column_list
的一部分時,它創建了一個單個的計算的列
- 一個一般的
- 使用子查詢指定數據源:把一個子查詢指定為
FROM
子句的一部分時,它立即創建了一個新的數據源,并被當做一個獨立的表或視圖來引用,與視圖的區別是視圖是永久保存在數據庫中的,而子查詢只是臨時的# 使用子查詢指定數據源的一般格式 SELECT column_list FROM [table_list] [JOIN] subquery AS custom_subquery_name # 從address表,city表和country表中列出五個地址對應的城市和國家 SELECT address AS 'Address', city AS 'City', country AS 'Country' FROM address LEFT JOIN( SELECT city.city, city.city_id, country.country, country.country_id FROM city LEFT JOIN country ON city.country_id = country.country_id ) AS city_and_country ON address.city_id = city_and_country.city_id ORDER BY address LIMIT 5
- 使用子查詢指定查詢條件:把一個子查詢指定為
WHERE
子句中IN
操作符的右值,可以以更復雜的邏輯來為IN
操作符創建一個可選列表;注意,當子查詢用于指定查詢條件時,僅能返回單一的列# 使用子查詢指定查詢條件的一般格式 SELECT column_list FROM table_list WHERE column IN subquery SELECT column_list FROM table_list WHERE subquery match_some_comdition # 列出所有使用現金支付的客戶名稱 SEELCT customer_name AS 'Customer Name' FROM costomers WHERE customer_id IN ( SELECT customer_id FROM orders WHERE order_type = 'cash' ) # 列出訂單金額少于20美元的客戶列表 SELECT customer_name AS 'Customer Name' FROM customers WHERE ( SELECT SUM(orderAmount) FROM orders WHERE customers.customer_id = orders.customer_id ) < 20
- 使用子查詢作為計算列:把一個子查詢作為column_list中的一項,將其用作一個計算的列
# 使用子查詢作為計算列的一般格式 SELECT column_list, subquery_result AS 'Result Alia' FROM table_list # 查詢客戶及其訂單數量 SELECT customer_name AS 'Customer Name', ( SELECT COUNT(order_id) FROM orders WHERE customers.customer_id = orders.customer_id ) AS 'Number of Orders' FROM customers ORDER BY customers.customer_id
- 關聯子查詢:無法獨立運行的子查詢為關聯子查詢,可以獨立運行的子查詢為非關聯子查詢。非關聯子查詢完全獨立與外圍查詢語句,只會計算和執行一次,而關聯子查詢需要針對返回的每一行逐行計算,且每次執行子查詢的時候得到的結果可能都不一樣,上文中查詢客戶及其訂單數量中的子查詢即為關聯子查詢,它使用了外圍查詢的數據來源
customers
表 -
EXISTS
操作符:用于確定一個關聯子查詢中是否存在數據# 查詢下過訂單的用戶 SELECT customer_name AS 'Customer' FROM customers WHERE EXISTS ( SELECT * FROM orders WHERE customers.customer_id = orders.customer_id )
集合邏輯
在前文中,連接JOIN
可以將來自兩個表的列組合到一個表中,子查詢則是將一條SELECT
語句的結果提供給第另一條SELECT
語句使用。然而有時候我們希望將來自兩個表的行組合到一個表中,這時候就需要使用SQL中的集合邏輯UNION
,來做合并查詢。
-
UNION
-合并兩條SELECT
語句,選取在A或B中的數據,如果同時存在在A或B中,僅顯示一條
使用SELECT order_date AS 'Date', 'order' AS 'Type', order_amount AS 'amount' FROM orders WHERE custom_id = 2 UNION SELECT return_date AS 'Date', 'return' AS 'type', return_amount AS 'amount' FROM returns WHERE custom_id = 2 ORDER BY date
UNION
需要遵守3個規則(實際就一條規則:相同列):- 兩個
SELECT
語句中的列的數量必須相等 - 兩個
SELECT
語句中的列排列順序必須相同 - 兩個
SELECT
語句中的列數據類型必須相同
- 兩個
-
UNION ALL
-合并兩條SELECT
語句,選取在A或B中的數據,即使同時存在在A或B中,都將顯示在結果中SELECT DISTINCT order_date AS 'Date' FROM orders UNION ALL SELECT DISTINCT return_date AS 'Date' FROM returns ORDER BY Date # UNION 確保來自兩個表的行沒有重復數據,但 UNION ALL 允許來自兩個表的行可以有相同數據 # DISTINCT 確保來自同一個表(或者說同一個SELECT語句)的行沒有重復數據 # 所以上面的語句選取的數據可能會存在重復數據,但重復的數據并不來自兩個表而是來自同一個表,并且僅會重復一次
-
INTERSECT
-合并兩條SELECT
語句,選取同時出現在A和B中的行(MySql不支持該操作符)SELECT order_date AS 'Date' FROM orders INTERSECT SELECT return_date As 'Date' FROM returns ORDER BY Date
-
EXCEPT
-合并兩條SELECT
語句,選取僅出現在A或僅出現在B中的的數據(MySql和Oracle不支持該操作符,但Oracle提供了等價的MINUS
操作符)SELECT order_date AS 'Date' FROM orders EXCEPT SELECT return_date AS 'Date' FROM returns ORDER BY Date
存儲過程和參數
到目前為止,前文所有的SQL語句都是單獨使用,然而很多時候,你會希望SQL語句能夠像函數一樣,定義一次,然后重復調用,并且可以使用參數來增加靈活性。這時,你就可以使用存儲過程來實現這一目的。
- 創建存儲過程:創建存儲過程不會執行任何內容,只是直接創建了一個過程,以便后續執行它。與表和視圖一樣,創建好的存儲過程在管理工具中是可以查看的
-- Microsoft SQL Server CREATE PROCEDURE ProcedureName (OptionalPrameterDeclarations) AS BEGIN SQLStatements END -- MySQL DELIMITER $$ -- 規定END語句使用的分隔符,默認為分號 CREATE PROCEDURE ProcedureName (OptionalPrameterDeclarations) BEGIN SQLStatements; -- 每一條SQL語句都必須使用分號分隔,即使只有一條 END$$ DELIMITER ; -- 將分隔符改回分號
- 存儲過程的參數:例如存儲一個選取特定用戶的SQL過程,可以使用參數來指定用戶的ID
-- Microsoft SQL Server CREATE PROCUDURE CustomerProcudure (@custId INT) AS BEGIN SELECT * FROM customers WHERE customer_id = @custId END -- MySQL DELIMITER $$ CREATE PROCEDURE CustomerProcudure (custId INT) BEGIN SELECT * FROM customers WHERE CUSTOMER_ID = custId; END DELEMITER ;
- 執行存儲過程
-- Microsoft SQL Server EXEC CustomerProcudure @custId = 2 -- MySQL CALL CustomerProcudure (2)
- 修改和刪除存儲過程:在Microsoft SQL Server中,修改過程和創建過程幾乎一樣,只需要把
CREATE
關鍵字替換為ALTER
關鍵字即可;然而在MySQL中,雖然也存在ALTER
命令,但它的功能十分簡單,所以一般我們選擇先刪除存儲過程然后再重新創建-- 刪除存儲過程 DROP PROCEDURE ProcedureName
- 函數與存儲過程的兩點區別
- 存儲過程可以有任意數目的輸出,而函數只有單一的返回值
- 只能通過調用程序來執行存儲過程,而函數可以在SQL語句中使用
修改數據
- 修改策略:使用“軟刪除(使用表中特定的列來標記該行數據是否有效)”技術替代真正的刪除;插入新行時在特定列中標記準確的插入日期和時間以便出錯時對其進行刪除;使用單獨的表來保存事務所要更新的數據通常是明智的選擇。請永遠記住,SQL中沒有撤銷命令。
- 插入數據:使用
INSERT
命令來插入指定數據,注意不需要為自增型的列指定數據,數據庫會自動處理它;另外,Oracle不允許一次插入多行數據,需要分開書寫- 插入
INSERT
語句中指定的具體數據-- 向customer表插入兩條新數據 INSERT INTO customers (first_name, last_name, state) -- 只要列名是正確的,它們的順序無所謂 -- 當這里的列名順序與數據庫中的物理順序一致時可省略它們,但強烈不建議這么做 VALUES ('Virginia', 'Jones', 'OH'), -- VALUES關鍵字后的數據列,要與INSERT INTO后的列相對應 ('Clark', 'Woodland', 'CA')
- 插入用一條
SELECT
語句指定的數據-- 將customer_transaction中的RI州的用戶插入到customer表中 INSERT INTO customer (first_name, last_name, state) SELECT fn, ln, state -- 這里選中列的順序需要與INSERT INTO 語句中的順序一致 FROM customer_transactions WHERE state = 'RI'
- 插入
- 刪除數據:使用
DELETE
命令來刪除一條數據,通常是一整行(刪除某行中的列沒有意義,那屬于修改數據的范疇)-- 刪除數據的一般寫法 DELETE FROM table_name WHERE conditions -- 可以使用SELECT語句來驗證刪除結果 SELECT COUNT (*) -- 使用聚合函數COUNT來統計被刪除數據的數量以確認是否全部都被刪除了 FROM table_name WHERE conditions -- 清空一個表中的所有數據,可以使用TRUNCATE TABLE語句 TRUNCATE TABLE customers -- 上面的語句與下面的DELETE語句效果基本相同 DELETE FROM customers -- 唯一不同在于,TRUNCATE TABLE語句重置了自增列,而DELETE語句沒有
- 更新(修改)數據:刪除數據只需要指定刪除的行即可,但更新數據是針對具體行中的具體列,所以需要首先指定更新哪些列,然后指定更新這些列中的哪些行
- 使用指定的具體數據更新數據
-- 更新數據的一般格式 UPDATE table SET column1 = expression1, column2 = expression2 -- repeat any number of time WHERE conditions -- 如果沒有指定行,該句會把所有行的指定列都更新一次
- 使用子查詢中的數據修改數據(使用一個表中的數據來更新另一個表中的數據)
-- 一般格式 UPDATE table -- 指定要更新的表 SET table.column_1 = -- 指定需要更新的列1 ( SELECT another_table.column_1 -- 子查詢從另一表中獲取數據,并通過主鍵(也可是其它)來進行匹配 FROM another_table WHERE another_table.auto_increment_primary_key = table.auto_increment_primary_key ) SET table.column_2 = -- 指定需要更新的列2 ( SELECT another_table.column_2 FROM another_table WHERE another_table.auto_increment_primary_key = auto_increment_primary_key.column_2 ) WHERE EXISTS -- 指定需要更新的行,使用子查詢指定只更新table中存在于another_table中的行 ( SELECT * FROM another_table WHERE another_table.auto_increment_primary_key = table.auto_increment_primary_key )
- 使用指定的具體數據更新數據
維護表
- 回顧SQL語言的三種組成部分:數據操縱語言(Data Manipulation Language,DML,對數據庫中或者更詳細地說是表中的數據進行增刪改查操作)、數據定義語言(Data Definition Language,DDL,對數據庫中的表及其索引進行增刪改查)、和數據控制語言(Data Control Language,DCL,維護數據庫安全)。本章主要講述DDL,但前文也已經用到過DDL,視圖
VIEW
、過程PROCEDURE
需要用到的都是DDL - 添加或修改表和索引的SQL語句很復雜,但是我們無需了解細節,數據庫軟件通常提供了圖形化的工具來修改表的結構,而不一定需要使用SQL語句
- 表屬性:表(Table)是數據庫中最重要的對象,數據庫中所有數據物理地存儲在表中,沒有了表數據庫中的一切也就沒有意義了。前文已經介紹過一些表的屬性,主鍵、外鍵、數據類型、自增型列等等
- 表的列
- 列名:表中的每個列都必須有唯一的列名
- 數據類型:決定列可以包含什么樣的數據
- 是否自增型:表中每增加一行,該列會以升序序列自動賦值(術語auto-increment是MySQL中的的特定用法,Oracle沒有自增型屬性)
- 默認值
- 表的列
- 主鍵和索引
- 主鍵:只能指定一個列作為主鍵,目的是保證這個列包含唯一值(所以不允許它們包含
NULL
值);實際上主鍵可以跨越多個列,叫做復合主鍵(當希望使用電影名稱列來作為主鍵時可能會存在重復名稱, 這時可以使用電影名稱+年份兩個列作為復合主鍵來唯一地定義每部電影) - 索引:索引是一種物理結構,目的是當SQL語句中包含該列的時候,可以加速數據檢索,缺點是需要更多的磁盤空間,并且會減慢更新數據時的速度
- 主鍵:只能指定一個列作為主鍵,目的是保證這個列包含唯一值(所以不允許它們包含
- 外鍵:
- 外鍵定義:外鍵是從一個表中的一個列到另一個不同的表中的列的直接引用,含有外鍵的表為“子表”,被外鍵引用的表被稱為“父表”
- 外鍵級聯(Cascade):當父表有更新或刪除時,會自動更新或刪除子表中的關聯行
- Set Null:當父表有更新或刪除時,如果影響到子表,是否把子表中關聯行的外鍵設置為
NULL
- 創建表:使用
CREATE TABLE
語句來創建表及其屬性(列),不同數據庫之間存在差異:
使用-- Microsoft SQL Server CREATE TABLE my_table ( column_1 INT IDENTITY (1, 1) PRIMARY KEY NOT NULL, -- 列名column_1,INT類型,自增型,主鍵,不能為NULL column_2 NOT NULL REFERENCES related_table (first_column), -- 列名column_2,INT類型,不能為NULL,外鍵,關聯到related_table表的first_column列 column_3 VARCHAR (25) NULL, -- 列名column_3,VARCHART類型,可以是NULL column_4 FLOAT NULL DEFAULT (10) -- 列名column_4,FLOAT類型,可以是NULL ) -- My SQL CRAET TABLE my_table column_1 INT AUTO_INCREMENT PRIMARY KEY NOT NULL, column_2 INT NOT NULL, column_3 VARCHAR (25) NULL, column_4 FLOAT NULL DEFAULT 10 NULL, CONSTRAINT FOREIGN KEY (column_2) REFERENCE 'related_table' (first_column) -- 指定外鍵 -- Oracle CREATE TABLE my_table ( column_1 INT PRIMARY KEY NOT NULL, -- Oracle不允許有自增型的列 column_2 INT NOT NULL, column_3 VARCHAR2 (25) null, column_4 FLOAT DEFAULT 10 NULL CONSTRAINT "foreign_key" FOREIGN KEY (column_2) REFERENCES related_table (first_column) )
ALTER TABLE
語句修改表的具體屬性,該語句的復雜性及數據庫差異巨大,這里不再展開;使用DROP TABLE table_name
語句來刪除一個表-- 修改表 ALTER TABLE my_table DROP COLUMN column_3 -- 刪除表 DROP TABLE my_table
- 創建索引:使用
CREATE INDEX
語句,用來在創建表之后創建索引,使用ALTER INDEX
語句來添加和修改索引-- 創建索引 CREATE INDEX index_2 ON my_table (column_4) -- 刪除索引 DROP INDEX index_2 ON my_table
數據庫設計原理與顯示數據的策略(略)
- 關系型數據庫是一個數據集合,數據庫中的表以某些方式相互關聯。
- SQL語句僅僅是使用數據庫的工具,數據庫設計則是另外一個更為重要的話題。《SQL初學者指南》中對這個話題進行了簡單的概括:規范化及其替代方法,這里將不再展開。
- 關于這個話題建議閱讀另外的一些書籍:《SQL必知必會》、《高性能MySQL》