1. 程式人生 > 程式設計 >《SQL初學者指南》讀書筆記

《SQL初學者指南》讀書筆記

關係型資料庫和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關鍵字之後
  • SELECTORDER BY後面指定的列,可能是完全不同的一些列
  • 使用關鍵字ASCDESC來升序/降序排列
  • 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子句總是在FROMORDER BY 子句之間,實際上任何“子句”都必須按照這個順序來
      SELECT columnList
      FROM tableList
      WHERE condition
      ORDER BY columnList
      複製程式碼
  • 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'
    )
    複製程式碼
  • 還有兩個表示範圍的操作符BETWEENIN,用於替代column >= range_bottom AND column <= range_topcolumn = value1 OR column = value2這樣的特例,簡化SQL語句的編寫
    # BETWEEN,等價於 age >= 18 AND age <= 60
    SELECT
    first_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 NULLWHERE子句中判斷一個列是否為空,也可以與函式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中沒有)
  • 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 數量
    # 總值、均值、最大值、最小值
    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    
      複製程式碼
    • 在沒有分組的情況下,聚合函式(SUMAVGMINMAXCOUNT)統計的是所有行的資料,在有分組的情況下,這些聚合函式則僅會統計組內的資料,當然實際上也是最終顯示的表的每一行的聚合
    • 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
      複製程式碼
    • 內連線只會返回關聯的兩個表之間相匹配的資料,表在FROMINNER JOIN之間的順序僅會影響哪個表的列先顯示,不會影響行的順序
    • SQL不是過程式語言,不會指定任務的先後順序,而只是指定需要的邏輯並讓資料庫內部機制去決定如何執行任務。
    • 僅使用FROMWHERE也可以指定表的內連線,這是內連線的另一種正規化,但因其沒有顯示地表示出連線的邏輯,所以不推薦使用(所以其實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並不是必須的。
  • 所以總結起來有4種型別的連線:
    連線型別 全稱 簡寫 用途
    內連線 INNER JOIN JOIN 兩個表都是主表(primary table),共同列中所有的行都必須同時在這兩個表中才會被選中
    左連線 LEFT OUTER JOIN LEFT JOIN 左表為主表,右表為從表(secondary table),選中共同列中所有在主表中的行,不管它是否出現在從表
    右連線 RIGHT OUTER JOIN RIGHT JOIN 左表為從表,右表為主表,規則同左連線
    全連線 FULL OUTER JOIN FULL JOIN 兩個表都是從表,共同列中的行只要出現在任意一個表中都會被選中
  • 在實體關係圖中,單向箭頭表示表之間的連線是單向的,箭頭終點的表中有一列所有行都能在箭頭起點的表中找到,但反過來則不一定,比如,不是所有的客戶都有訂單,且一個客戶可能有多個訂單,但所有的訂單都會有客戶資訊(甚至可以說所有的訂單有且只有一個客戶資訊),退貨資訊與訂單的關係類似
  • 當連線主表和從表時,我們需要主表中所有的行,即使在從表中的共同列沒有任何行與之匹配
  • 使用IS NOT NULLIS 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',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來刪除檢視

子查詢

  • 包含在其他查詢中的查詢叫做子查詢,子查詢可以用在SELECTINSERTUPDATEDELETE語句
  • 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,state) -- 只要列名是正確的,它們的順序無所謂
      -- 當這裡的列名順序與資料庫中的物理順序一致時可省略它們,但強烈不建議這麼做
      VALUES
      ('Virginia','Jones','OH'),-- VALUES關鍵字後的資料列,要與INSERT INTO後的列相對應
      ('Clark','Woodland','CA')
      複製程式碼
    • 插入用一條SELECT語句指定的資料
      -- 將customer_transaction中的RI州的使用者插入到customer表中
      INSERT INTO customer
      (first_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
    CREATE 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》