《SQL 必知必會》讀書筆記


第1課 了解 SQL

這章主要介紹了數據庫,表,字段類型,行,列,主鍵和SQL等基本概念。

  • 數據庫:以某種形式存儲的數據集合,在計算機上的表現形式可能是一個文件或者一組文件。我們平時所說的數據庫,往往指MySQL或者Oracle這些數據庫管理系統。數據庫管理系統用來創建操作數據庫。
  • 數據表:一個二維數據結構。
  • 列和數據類型
  • 數據行
  • 主鍵:能夠唯一標識一條記錄的一列(或幾列)數據,主鍵具有下面的特點:
    • 任意兩行都不具有相同的主鍵值;
    • 每一行都必須具有一個主鍵值(主鍵列不允許NULL值);
    • 主鍵列中的值不允許修改或更新;
    • 主鍵值不能重用(如果某行從表中刪除,它的主鍵不能賦給以后的新行)。

主鍵通常定義在表的一列上,但並不是必需這么做,也可以一起使用多個列作為主鍵。在使用多列作為主鍵時,上述條件必須應用到所有列,所有列值的組合必須是唯一的。

  • SQL語言:結構化查詢語言,是一種專門用來和數據庫交互的語言。標准SQL由ANSI標准委員會管理,從而稱為ANSI SQL。主流的數據庫管理系統,比如MySQL,Oracle等都支持標准的SQL。但是數據庫廠商都會對ANSL SQL進行擴展,如PL/SQL、Transact-SQL等。

第2課 檢索數據(select)

關於SQL語句的一些說明:

有些數據庫管理系統需要在語句末尾加上;有的不需要加,當然,如果願意可以總是加上分號。事實上,即使不一定需要,加上分號也肯定沒有壞處。

SQL語句和大小寫:SQL語句不區分大小寫,因此SELECT與select是相同的。同樣,寫成Select也沒有關系。許多SQL開發人員喜歡對SQL關鍵字使用大寫,而對列名和表名使用小寫,這樣做使代碼更易於閱讀和調試。不過,一定要認識到雖然SQL是不區分大小寫的,但是表名、列名和值可能有所不同(這有賴於具體的DBMS及其如何實現)。

在處理SQL語句時,其中所有空格都被忽略。SQL語句可以寫成長長的一行,也可以分寫在多行。

選擇多個數據列

SELECT prod_id, prod_name, prod_price
FROM Products;

查詢所有列

SELECT * FROM Products;

一般而言,除非你確實需要表中的每一列,否則最好別使用*通配符。雖然使用通配符能讓你自己省事,不用明確列出所需列,但檢索不需要的列通常會降低檢索和應用程序的性能。

檢索不同的值(distinct)

SELECT DISTINCT vend_id FROM Products;

DISTINCT關鍵字作用於所有的列,不僅僅是跟在其后的那一列。例如,你指定 SELECT DISTINCT vend_id, prod_price,因為指定的兩列不完全相同,所以所有的行都會被檢索出來。

限制查詢返回結果數

在SQL Server和Access中使用SELECT時,可以使用TOP關鍵字來限制最多返回多少行。

SELECT TOP 5 prod_name FROM Products;

如果你使用Oracle,需要基於ROWNUM(行計數器)來計算行

SELECT prod_name FROM Products
WHERE ROWNUM <=5;

如果你使用MySQL、MariaDB、PostgreSQL或者SQLite,需要使用LIMIT子句,像這樣:

SELECT prod_name FROM Products
LIMIT 5;

這些數據庫還支持offset關鍵字,用於指定從哪行數據開始返回。比如:limit n offset m 表示從m+1條開始返回,一共返回n條。

MySQL、MariaDB和SQLite支持簡化版的LIMIT 4 OFFSET 3語句,即LIMIT 3,4。使用這個語法,逗號之前的值對應OFFSET,逗號之后的值對應LIMIT。

使用注釋

在sql語句中,使用“--”來進行語句注釋

第3課 排序查詢數據

這一課講授如何使用SELECT語句的ORDER BY子句,根據需要排序檢索出的數據。

SELECT prod_name FROM Products
ORDER BY prod_name;

在指定一條ORDER BY子句時,應該保證它是SELECT語句中最后一條子句。如果它不是最后的子句,將會出現錯誤消息。
通常,ORDER BY子句中使用的列將是為顯示而選擇的列。但是,實際上並不一定要這樣,用非檢索的列排序數據是完全合法的。

按照多個列排序

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

對於上述例子中的輸出,僅在多個行具有相同的prod_price值時才對產品按prod_name進行排序。如果prod_price列中所有的值都是唯一的,則不會按prod_name排序。

按照列位置排序

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

ORDER BY 2表示按SELECT清單中的第二個列prod_price進行排序。ORDER BY 2, 3表示先按prod_price,再按prod_name進行排序。

不建議使用這個排序方式

指定排序方向

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

DESC關鍵字只應用到直接位於其前面的列名。在上例中,只對prod_price列指定DESC,對prod_name列不指定。因此,prod_price列以降序排序,而prod_name列(在每個價格內)仍然按標准的升序排序。

默認升序排序(aesc),降序使用desc

第4課 過濾數據

這一課將講授如何使用SELECT語句的WHERE子句指定搜索條件。

WHERE子句操作符

上面列出的某些操作符是冗余的(如< >與!=相同,! <相當於>=)。並非所有DBMS都支持這些操作符。想確定你的DBMS支持哪些操作符,請參閱相應的文檔。

SELECT prod_name, prod_price
FROM Products
WHERE prod_price BETWEEN 5 AND 10

問題:between是否包括邊界值

上面也可以寫成

prod_price > 5 and prod_price < 10

不能寫成

5<prod_price<10

在sql語句中,字符串用單引號包裹,而不是雙引號

第5課 高級數據過濾

這一課講授如何組合WHERE子句以建立功能更強、更高級的搜索條件(and 和 or的使用)。我們還將學習如何使用NOT和IN操作符

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

SQL(像多數語言一樣)在處理OR操作符前,優先處理AND操作符(and 的優先級高於or)。當SQL看到上述WHERE子句時,它理解為:由供應商BRS01制造的價格為10美元以上的所有產品,以及由供應商DLL01制造的所有產品,而不管其價格如何。

此問題的解決方法是使用圓括號對操作符進行明確分組。

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

圓括號具有比AND或OR操作符更高的求值順序,所以DBMS首先過濾圓括號內的OR條件。這時,SQL語句變成了選擇由供應商DLL01或BRS01制造的且價格在10美元及以上的所有產品,這正是我們希望的結果。

任何時候使用具有AND和OR操作符的WHERE子句,都應該使用圓括號明確地分組操作符。不要過分依賴默認求值順序,即使它確實如你希望的那樣。使用圓括號沒有什么壞處,它能消除歧義。

in操作的優點

  • 在有很多合法選項時,IN操作符的語法更清楚,更直觀。
  • 在與其他AND和OR操作符組合使用IN時,求值順序更容易管理。
  • IN操作符一般比一組OR操作符執行得更快(在上面這個合法選項很少的例子中,你看不出性能差異)。
  • IN的最大優點是可以包含其他SELECT語句,能夠更動態地建立WHERE子句。(子查詢)

NOT操作符

WHERE子句中的NOT操作符有且只有一個功能,那就是否定其后所跟的任何條件。

第6課 用通配符進行過濾

like 操作讀符

利用like可以構建比較特殊的查詢條件,比如查詢包含某個關鍵字的行。

通配符搜索只能用於文本字段(字符串),非文本數據類型字段不能使用通配符搜索。

百分號(%)通配符

%表示任何字符出現任意次數。%代表搜索模式中給定位置的0個、1個或多個字符。

根據DBMS的不同及其配置,搜索可以是區分大小寫的。如果區分大小寫,則’fish%’與Fish bean bag toy就不匹配。
通配符%看起來像是可以匹配任何東西,但有個例外,這就是NULL。子句WHERE prod_name LIKE '%’不會匹配產品名稱為NULL的行。

下划線(_)通配符

下划線的用途與%一樣,但它只匹配單個字符,而不是多個字符。

方括號([ ])通配符

方括號([])通配符用來指定一個字符集,它必須匹配指定位置(通配符的位置)的一個字符。

找出所有名字以J或M起頭的聯系人,可進行如下查詢:

SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[JM]%'

此通配符可以用前綴字符^(脫字號)來否定。例如,下面的查詢匹配以J和M之外的任意字符起頭的任意聯系人名:

SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[^JM]%'

當然,也可以使用NOT操作符得出類似的結果。

SELECT cust_contact
FROM Customers
WHERE NOT cust_contact LIKE '[JM]%'

使用通配符的技巧

SQL的通配符很有用。但這種功能是有代價的,即通配符搜索一般比前面討論的其他搜索要耗費更長的處理時間。

這里給出一些使用通配符時要記住的技巧。

  • 不要過度使用通配符。如果其他操作符能達到相同的目的,應該使用其他操作符。
  • 在確實需要使用通配符時,也盡量不要把它們用在搜索模式的開始處。把通配符置於開始處,搜索起來是最慢的
  • 仔細注意通配符的位置。如果放錯地方,可能不會返回想要的數據。

第7課 創建計算字段

這一課介紹什么是計算字段,如何創建計算字段,以及如何從應用程序中使用別名引用它們。

計算字段

存儲在數據庫表中的數據一般不是應用程序所需要的格式,下面舉幾個例子。

  • 需要顯示公司名,同時還需要顯示公司的地址,但這兩個信息存儲在不同的表列中。
  • 城市、州和郵政編碼存儲在不同的列中(應該這樣),但郵件標簽打印程序需要把它們作為一個有恰當格式的字段檢索出來。
  • 列數據是大小寫混合的,但報表程序需要把所有數據按大寫表示出來。
  • 物品訂單表存儲物品的價格和數量,不存儲每個物品的總價格(用價格乘以數量即可)。但為打印發票,需要物品的總價格。
  • 需要根據表數據進行諸如總數、平均數的計算。

在上述每個例子中,存儲在表中的數據都不是應用程序所需要的。我們需要直接從數據庫中檢索出轉換、計算或格式化過的數據,而不是檢索出數據,然后再在客戶端應用程序中重新格式化

這就是計算字段可以派上用場的地方了。與前幾課介紹的列不同,計算字段並不實際存在於數據庫表中。計算字段是運行時在SELECT語句內創建的。

在SQL語句內可完成的許多轉換和格式化工作都可以直接在客戶端應用程序內完成。但一般來說,在數據庫服務器上完成這些操作比在客戶端中完成要快得多。

拼接字段

關於字符串拼接

Access和SQL Server使用+號。DB2、Oracle、PostgreSQL、SQLite和Open Office Base使用||。在MySQL和MariaDB中,必須使用特殊的函數。

但是基本上,所有的數據庫都有拼接函數,建議使用拼接函數來進行字符串拼接。

SELECT Concat(vend_name, ' (', vend_country, ')')
FROM Vendors

說明:TRlM函數
大多數DBMS都支持RTRIM()(正如剛才所見,它去掉字符串右邊的空格)、LTRIM()(去掉字符串左邊的空格)以及TRIM()(去掉字符串左右兩邊的空格)。

別名的使用

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

Oracle 中沒有 as 關鍵字。

執行算術計算

計算字段的另一常見用途是對檢索出的數據進行算術計算。

SELECT prod_id,
    quantity,
    item_price,
    quantity*item_price AS expanded_price FROM OrderItems
WHERE order_num = 2008;

支持加減乘除等運算符。

第8課 使用函數處理數據

函數

SQL也可以用函數來處理數據。函數一般是在數據上執行的,為數據的轉換和處理提供了方便。

雖然使用函數可以給我們帶來方便,但是我們也應該要意識到函數給我們帶來的問題:每個數據庫都有自己的函數實現,只有很少幾個函數是可以在多數數據庫中通用的,也就是說你使用了一個特定的函數后,移植到其他數據庫就需要重新調整sql。

使用函數

大多數SQL實現支持以下類型的函數。

  • 用於處理文本字符串(如刪除或填充值,轉換值為大寫或小寫)的文本函數。
  • 用於在數值數據上進行算術操作(如返回絕對值,進行代數運算)的數值函數。
  • 用於處理日期和時間值並從這些值中提取特定成分(如返回兩個日期之差,檢查日期有效性)的日期和時間函數。
  • 返回DBMS正使用的特殊信息(如返回用戶登錄信息)的系統函數。

日期處理函數,每個數據庫的實現差異性都很大。

數值處理函數僅處理數值數據。這些函數一般主要用於代數、三角或幾何運算,因此不像字符串或日期-時間處理函數使用那么頻繁。

具有諷刺意味的是,在主要DBMS的函數中,數值函數是最一致、最統一的函數下面列出一些常用的數值處理函數。

第9課 匯總數據(聚合函數)重點

這一課介紹什么是SQL的聚集函數,如何利用它們匯總表的數據。

聚集函數(聚合函數)

我們經常需要匯總數據而不用把它們實際檢索出來,為此SQL提供了專門的函數。使用這些函數,SQL查詢可用於檢索數據,以便分析和報表生成。這種類型的檢索例子有:

  • 確定表中行數(或者滿足某個條件或包含某個特定值的行數);
  • 獲得表中某些行的和;
  • 找出表列(或所有行或某些特定的行)的最大值、最小值、平均值。

上面的列子中,需要原始數據參與計算,但是最后的結構又不需要這些原始數據,而是需要計算出來的匯總信息。

為方便這種類型的檢索,SQL給出了5個聚集函數。這些函數能進行上述檢索。與前一章介紹的數據處理函數不同,SQL的聚集函數在各種主要SQL實現中得到了相當一致的支持

AVG()函數

SELECT AVG(prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';

AVG()函數忽略列值為NULL的行。

COUNT()函數
COUNT()函數進行計數。可利用COUNT()確定表中行的數目或符合特定條件的行的數目。

COUNT()函數有兩種使用方式:

  • 使用COUNT(*)對表中行的數目進行計數,不管表列中包含的是空值(NULL)還是非空值。
  • 使用COUNT(column)對特定列中具有值的行進行計數,忽略NULL值。

MAX()函數

MAX()返回指定列中的最大值。MAX()要求指定列名。

雖然MAX()一般用來找出最大的數值或日期值,但許多(並非所有)DBMS允許將它用來返回任意列中的最大值,包括返回文本列中的最大值。在用於文本數據時,MAX()返回按該列排序后的最后一行。
MAX()函數忽略列值為NULL的行。

min函數*
用法和max函數類似

SUM()函數
SUM()用來返回指定列值的和(總計)。

SELECT SUM(item_price*quantity) AS total_price
FROM OrderItems
WHERE order_num = [20005](http://tel:20005/);

如本例所示,利用標准的算術操作符,所有聚集函數都可用來執行多個列上的計算。
SUM()函數忽略列值為NULL的行。

PS:我們寫sql語句時也要有判斷空值的意識,想着我們選擇的字段要是null,程序會不會報異常,會不會影響我們的業務邏輯

聚集不同

以上5個聚集函數都可以如下使用。

  • 對所有行執行計算,指定ALL參數或不指定參數(因為ALL是默認行為)。
  • 只包含不同的值,指定DISTINCT參數。
SELECT AVG(DISTINCT prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';

加了distinct參數后,如果有多個prod_price的值一樣,只取其中一個進行計算。

DISTINCT不能用於COUNT(*)。類似地,DISTINCT必須使用列名,不能用於計算或表達式。

組合聚集函數

目前為止的所有聚集函數例子都只涉及單個函數。但實際上,SELECT語句可根據需要包含多個聚集函數。

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;

第10課 分組數據

這一課介紹如何分組數據,以便匯總表內容的子集。這涉及兩個新SELECT語句子句:GROUP BY子句和HAVING子句。

數據分組

所謂數據分組,就是先對選出來的數據分組,再對這些分組數據進行聚合計算。

group by 一般和聚合函數配合使用。

創建分組

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

使用GROUP BY子句前,需要知道一些重要的規定。

  • GROUP BY子句可以包含任意數目的列,因而可以對分組進行嵌套,更細致地進行數據分組。
  • 如果在GROUP BY子句中嵌套了分組,數據將在最后指定的分組上進行匯總。換句話說,在建立分組時,指定的所有列都一起計算(所以不能從個別的列取回數據)。
  • GROUP BY子句中列出的每一列都必須是檢索列或有效的表達式(但不能是聚集函數)。如果在SELECT中使用表達式,則必須在GROUP BY子句中指定相同的表達式。不能使用別名。
  • 大多數SQL實現不允許GROUP BY列帶有長度可變的數據類型(如文本或備注型字段)。
  • 除聚集計算語句外,SELECT語句中的每一列都必須在GROUP BY子句中給出。
  • 如果分組列中包含具有NULL值的行,則NULL將作為一個分組返回。如果列中有多行NULL值,它們將分為一組。
  • GROUP BY子句必須出現在WHERE子句之后,ORDER BY子句之前。

過濾分組

過濾分組的作用:根據分組以后得到的統計數據,進一步對數據進行過濾。

比如:你可能想要列出至少有兩個訂單的所有顧客。

我們已經看到了WHERE子句的作用。但是,在這個例子中WHERE不能完成任務,因為WHERE過濾指定的是行而不分組。事實上,WHERE沒有分組的概念。

那么,不使用WHERE使用什么呢?SQL為此提供了另一個子句,就是HAVING子句。HAVING非常類似於WHERE。事實上,目前為止所學過的所有類型的WHERE子句都可以用HAVING來替代。唯一的差別是,WHERE過濾行,而HAVING過濾分組。

WHERE子句的條件(包括通配符條件和帶多個操作符的子句)。這些有關WHERE的所有技術和選項都適用於HAVING。它們的句法是相同的,只是關鍵字有差別。

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

HAVING和WHERE的差別
WHERE在數據分組前進行過濾,HAVING在數據分組后進行過濾。這是一個重要的區別,WHERE排除的行不包括在分組中。這可能會改變計算值,從而影響HAVING子句中基於這些值過濾掉的分組。

分組和排序

GROUP BY和ORDER BY經常完成相同的工作,但它們非常不同,理解這一點很重要。

我們經常發現,用GROUP BY分組的數據確實是以分組順序輸出的。但並不總是這樣,這不是SQL規范所要求的。

一般在使用GROUP BY子句時,應該也給出ORDER BY子句。這是保證數據正確排序的唯一方法。千萬不要僅依賴GROUP BY排序數據。

SELECT子句順序

第11課 使用子查詢

子查詢

即嵌套在其他查詢中的查詢。

利用子查詢進行過濾

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

在SELECT語句中,子查詢總是從內向外處理。

注意:只能是單列
作為子查詢的SELECT語句只能查詢單個列。企圖檢索多個列將返回錯誤。
子查詢的性能:如果子查詢嵌套的層次太多,性能會出問題。而且又是多層次的子查詢並不是解決問題的最好方法。

作為計算字段使用子查詢

前面介紹的功能是作為in操作的條件。

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

第12課 聯結表

聯結

將兩張表通過聯系字段拼接起來。

創建聯結

SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;

笛卡兒積(cartesian product)
由沒有聯結條件的表關系返回的結果為笛卡兒積。檢索出的行的數目將是第一個表中的行數乘以第二個表中的行數。

內聯結
上面使用的聯結稱為等值聯結(equijoin),它基於兩個表之間的相等測試。這種聯結也稱為內聯結(inner join)。其實,可以對這種聯結使用稍微不同的語法,明確指定聯結的類型。下面的SELECT語句返回與前面例子完全相同的數據:

SELECT vend_name, prod_name, prod_price
FROM Vendors INNER JOIN Products
 ON Vendors.vend_id = Products.vend_id;

ANSI SQL規范首選INNER JOIN語法,之前使用的是簡單的等值語法。其實,SQL語言純正論者是用鄙視的眼光看待簡單語法的。

聯結多個表
SQL不限制一條SELECT語句中可以聯結的表的數目。創建聯結的基本規則也相同。首先列出所有表,然后定義表之間的關系。

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](http://tel:20007/);

注意:性能考慮
DBMS在運行時關聯指定的每個表,以處理聯結。這種處理可能非常耗費資源,因此應該注意,不要聯結不必要的表。聯結的表越多,性能下降越厲害。
注意:聯結中表的最大數目
雖然SQL本身不限制每個聯結約束中表的數目,但實際上許多DBMS都有限制。

第13課 創建高級聯結

使用表別名

SQL除了可以對列名和計算字段使用別名,還允許給表名起別名。

注意:Oracle中沒有AS
Oracle不支持AS關鍵字。要在Oracle中使用別名,可以不用AS,簡單地指定列名即可(因此,應該是Customers C,而不是Customers AS C)。

使用不同類型的聯結

迄今為止,我們使用的只是內聯結或等值聯結的簡單聯結。現在來看三種其他聯結:自聯結(self-join)、自然聯結(natural join)和外聯結(outer join)。

自聯結
假如要給與Jim Jones同一公司的所有顧客發送一封信件。

常規做法:子查詢

SELECT cust_id, cust_name, cust_contact
FROM Customers
WHERE cust_name = (SELECT cust_name
          FROM Customers
          WHERE cust_contact = 'Jim Jones');

自鏈接的做法:

SELECT c1.cust_id, c1.cust_name, c1.cust_contact
FROM Customers AS c1, Customers AS c2
WHERE c1.cust_name = c2.cust_name
 AND c2.cust_contact = 'Jim Jones';

提示:用自聯結而不用子查詢
自聯結通常作為外部語句,用來替代從相同表中檢索數據的使用子查詢語句。雖然最終的結果是相同的,但許多DBMS處理聯結遠比處理子查詢快得多。應該試一下兩種方法,以確定哪一種的性能更好。

自然聯結
可以不用重點關注

外聯結
許多聯結將一個表中的行與另一個表中的行相關聯,但有時候需要包含沒有關聯行的那些行。例如,可能需要使用聯結完成以下工作:

  • 對每個顧客下的訂單進行計數,包括那些至今尚未下訂單的顧客;

在上述例子中,聯結包含了那些在相關表中沒有關聯行的行。這種聯結稱為外聯結。

SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;

與內聯結關聯兩個表中的行不同的是,外聯結還包括沒有關聯行的行。在使用OUTER JOIN語法時,必須使用RIGHT或LEFT關鍵字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右邊的表,而LEFT指出的是OUTER JOIN左邊的表)。

使用帶聚集函數的聯結

聚合函數一般用來對表中數據進行匯總統計,或者對分組數據進行分組統計。

這些函數也可以與聯結一起使用。

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;

第14課 組合查詢

本課講述如何利用UNION操作符將多條SELECT語句組合成一個結果集。

組合查詢

SQL允許執行多個查詢(多條SELECT語句),並將結果作為一個查詢結果集返回。這些組合查詢通常稱為並(union)或復合查詢(compound query)。
主要有兩種情況需要使用組合查詢:

  • 在一個查詢中從不同的表返回結構數據;
  • 對一個表執行多個查詢,按一個查詢返回數據。
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';

上面的列子,也可以使用下面的sql實現。

SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL', 'IN', 'MI')
 OR cust_name = 'Fun4All';

在這個簡單的例子中,使用UNION可能比使用WHERE子句更為復雜。但對於較復雜的過濾條件,或者從多個表(而不是一個表)中檢索數據的情形,使用UNION可能會使處理更簡單。

提示:UNION的限制
使用UNION組合SELECT語句的數目,SQL沒有標准限制。但是,最好是參考一下具體的DBMS文檔,了解它是否對UNION能組合的最大語句數目有限制。

理論上講,從性能上看使用多條WHERE子句條件還是UNION應該沒有實際的差別。不過我說的是理論上,實踐中多數查詢優化程序並不能達到理想狀態,所以最好測試一下這兩種方法,看哪種工作得更好。

進行組合時需要注意幾條規則。

  • UNION必須由兩條或兩條以上的SELECT語句組成,語句之間用關鍵字UNION分隔(因此,如果組合四條SELECT語句,將要使用三個UNION關鍵字)。
  • UNION中的每個查詢必須包含相同的列、表達式或聚集函數(不過,各個列不需要以相同的次序列出)。
  • 列數據類型必須兼容:類型不必完全相同,但必須是DBMS可以隱含轉換的類型(例如,不同的數值類型或不同的日期類型)。

如果查詢出來的數據完全相同,union 會默認只返回其中一條,如果想讓數據全部返回,可以使用 union all

提示:UNION與WHERE
這一課一開始我們說過,UNION幾乎總是完成與多個WHERE條件相同的工作。UNION ALL為UNION的一種形式,它完成WHERE子句完成不了的工作。如果確實需要每個條件的匹配行全部出現(包括重復行),就必須使用UNION ALL,而不是WHERE。

在用UNION組合查詢時,只能使用一條ORDER BY子句,它必須位於最后一條SELECT語句之后。但是它是對整個返回結果集的排序。

第15課 插入數據

顧名思義,INSERT用來將行插入(或添加)到數據庫表。插入有幾種方式:

  • 插入完整的行;
  • 插入行的一部分;
  • 插入某些查詢的結果。
INSERT INTO Customers(cust_id,
            cust_name,cust_address,
            cust_city,
            cust_state,
            cust_zip,
            cust_country,
            cust_contact,
            cust_email)
VALUES('[1000000006](http://tel:1000000006/)',
'Toy Land',
    '123 Any Street',
    'New York',
    'NY',
    '1111',
    'USA',
    NULL,
    NULL);

VALUES中的第一個值對應於第一個指定列名,第二個值對應於第二個列名,如此等等。
因為提供了列名,VALUES必須以其指定的次序匹配指定的列名,不一定按各列出現在表中的實際次序。其優點是,即使表的結構改變,這條INSERT語句仍然能正確工作。

注意:省略列
如果表的定義允許,則可以在INSERT操作中省略某些列。省略的列必須滿足以下某個條件。

  • 該列定義為允許NULL值(無值或空值)。
  • 在表定義中給出默認值。這表示如果不給出值,將使用默認值。

插入檢索出的數據

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;

從一個表復制到另一個表

要將一個表的內容復制到一個全新的表(運行中創建的表),可以使用SELECT INTO語句。

SELECT *
INTO CustCopy
FROM Customers;

MariaDB、MySQL、Oracle、PostgreSQL和SQLite使用的語法稍有不同:

CREATE TABLE CustCopy AS
SELECT * FROM Customers;

第16課 更新和刪除數據

這一課介紹如何利用UPDATE和DELETE語句進一步操作表數據。

注意:不要省略WHERE子句
在使用UPDATE時一定要細心。因為稍不注意,就會更新表中的所有行。

UPDATE Customers
SET cust_email = '[kim@thetoystore.com](http://mailto:kim@thetoystore.com/)'
WHERE cust_id = '[1000000005](http://tel:1000000005/)';

在更新多個列時,只需要使用一條SET命令,每個“列=值”對之間用逗號分隔(最后一列之后不用逗號)。

數據的刪除更簡單。

注意:不要省略WHERE子句
在使用DELETE時一定要細心。因為稍不注意,就會錯誤地刪除表中所有行。
提示:更快的刪除
如果想從表中刪除所有行,不要使用DELETE。可使用TRUNCATE TABLE語句,它完成相同的工作,而速度更快(因為不記錄數據的變動)。先刪除表再建立表結構

第17課 創建和操縱表

創建表

一般有兩種創建表的方法:

  • 多數DBMS都具有交互式創建和管理數據庫表的工具;
  • 表也可以直接用SQL語句操縱。
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_desc   VARCHAR(1000)   NULL
);

更新表

使用alter table關鍵字

刪除表

drop table xxx

重命名表

每個數據庫不太一樣

第18課 使用視圖

視圖

視圖是虛擬的表。與包含數據的表不一樣,視圖只包含使用時動態檢索數據的查詢。

為什么使用視圖

  • 重用SQL語句。
  • 簡化復雜的SQL操作。在編寫查詢后,可以方便地重用它而不必知道其基本查詢細節。
  • 使用表的一部分而不是整個表。
  • 保護數據。可以授予用戶訪問表的特定部分的權限,而不是整個表的訪問權限。
  • 更改數據格式和表示。視圖可返回與底層表的表示和格式不同的數據。

創建視圖之后,可以用與表基本相同的方式使用它們。可以對視圖執行SELECT操作,過濾和排序數據,將視圖聯結到其他視圖或表,甚至添加和更新數據(添加和更新數據存在某些限制)。

下面是關於視圖創建和使用的一些最常見的規則和限制。

  • 與表一樣,視圖必須唯一命名(不能給視圖取與別的視圖或表相同的名字)。
  • 對於可以創建的視圖數目沒有限制。
  • 創建視圖,必須具有足夠的訪問權限。這些權限通常由數據庫管理人員授予。
  • 視圖可以嵌套,即可以利用從其他視圖中檢索數據的查詢來構造視圖。所允許的嵌套層數在不同的DBMS中有所不同(嵌套視圖可能會嚴重降低查詢的性能,因此在產品環境中使用之前,應該對其進行全面測試)。
  • 許多DBMS禁止在視圖查詢中使用ORDER BY子句。
  • 有些DBMS要求對返回的所有列進行命名,如果列是計算字段,則需要使用別名(關於列別名的更多信息,請參閱第7課)。
  • 視圖不能索引,也不能有關聯的觸發器或默認值。
  • 有些DBMS把視圖作為只讀的查詢,這表示可以從視圖檢索數據,但不能將數據寫回底層表。詳情請參閱具體的DBMS文檔。
  • 有些DBMS允許創建這樣的視圖,它不能進行導致行不再屬於視圖的插入或更新。例如有一個視圖,只檢索帶有電子郵件地址的顧客。如果更新某個顧客,刪除他的電子郵件地址,將使該顧客不再屬於視圖。這是默認行為,而且是允許的,但有的DBMS可能會防止這種情況發生。

建議:能不用,就不用視圖

創建視圖

視圖用CREATE VIEW語句來創建。與CREATE TABLE一樣,CREATE VIEW 只能用於創建不存在的視圖。

說明:視圖重命名
刪除視圖,可以使用DROP語句,其語法為DROP VIEW viewname;。覆蓋(或更新)視圖,必須先刪除它,然后再重新創建。

利用視圖簡化復雜的聯結

一個最常見的視圖應用是隱藏復雜的SQL,這通常涉及聯結。

CREATE VIEW ProductCustomers ASSELECT cust_name, cust_contact, prod_id
FROM Customers, Orders, OrderItems
WHERE Customers.cust_id = Orders.cust_id
 AND OrderItems.order_num = Orders.order_num;

視圖極大地簡化了復雜SQL語句的使用。利用視圖,可一次性編寫基礎的SQL,然后根據需要多次使用。

用視圖重新格式化檢索出的數據
視圖的另一常見用途是重新格式化檢索出的數據。

用視圖過濾不想要的數據

使用視圖與計算字段

第19課 使用存儲過程

什么是存儲過程

一條或者多條sql語句的集合,可以將其看成是一個sql函數。

為什么要使用存儲過程

  • 通過把處理封裝在一個易用的單元中,可以簡化復雜的操作(如前面例子所述)。
  • 由於不要求反復建立一系列處理步驟,因而保證了數據的一致性。如果所有開發人員和應用程序都使用同一存儲過程,則所使用的代碼都是相同的。
    這一點的延伸就是防止錯誤。需要執行的步驟越多,出錯的可能性就越大。防止錯誤保證了數據的一致性。
  • 簡化對變動的管理。如果表名、列名或業務邏輯(或別的內容)有變化,那么只需要更改存儲過程的代碼。使用它的人員甚至不需要知道這些變化。這一點的延伸就是安全性。通過存儲過程限制對基礎數據的訪問,減少了數據訛誤(無意識的或別的原因所導致的數據訛誤)的機會。
  • 因為存儲過程通常以編譯過的形式存儲,所以DBMS處理命令所需的工作量少,提高了性能。
  • 存在一些只能用在單個請求中的SQL元素和特性,存儲過程可以使用它們來編寫功能更強更靈活的代碼。換句話說,使用存儲過程有三個主要的好處,即簡單、安全、高性能。顯然,它們都很重要。

不過,在將SQL代碼轉換為存儲過程前,也必須知道它的一些缺陷。

  • 不同DBMS中的存儲過程語法有所不同。事實上,編寫真正的可移植存儲過程幾乎是不可能的。不過,存儲過程的自我調用(名字以及數據如何傳遞)可以相對保持可移植。因此,如果需要移植到別的DBMS,至少客戶端應用代碼不需要變動。
  • 一般來說,編寫存儲過程比編寫基本SQL語句復雜,需要更高的技能,更豐富的經驗。因此,許多數據庫管理員把限制存儲過程的創建作為安全措施(主要受上一條缺陷的影響)。
    盡管有這些缺陷,存儲過程還是非常有用的,並且應該使用。事實上,多數DBMS都帶有用於管理數據庫和表的各種存儲過程。更多信息請參閱具體的DBMS文檔。

存儲過程執行

參考具體的數據庫

存儲過程創建

參考具體的數據庫

事務管理

提示:可以回退哪些語句?
事務處理用來管理INSERT、UPDATE和DELETE語句。不能回退SELECT語句(回退SELECT語句也沒有必要),也不能回退CREATE或DROP操作。事務處理中可以使用這些語句,但進行回退時,這些操作也不撤銷。

每個數據庫的事務實現可能不太一樣,針對具體的數據庫學習,效果比較好。

第21課 使用游標

游標(cursor)是一個存儲在DBMS服務器上的數據庫查詢,它不是一條SELECT語句,而是被該語句檢索出來的結果集。在存儲了游標之后,應用程序可以根據要滾動或瀏覽其中的數據。

第22課 高級sql特性

約束

  • 主鍵約束
  • 外鍵約束
  • 唯一約束
  • 檢查約束

索引

索引用來排序數據以加快搜索和排序操作的速度。

在開始創建索引前,應該記住以下內容。

  • 索引改善檢索操作的性能,但降低了數據插入、修改和刪除的性能。在執行這些操作時,DBMS必須動態地更新索引。
  • 索引數據可能要占用大量的存儲空間。
  • 並非所有數據都適合做索引。取值不多的數據(如州)不如具有更多可能值的數據(如姓或名),能通過索引得到那么多的好處。
  • 索引用於數據過濾和數據排序。如果你經常以某種特定的順序排序數據,則該數據可能適合做索引。
  • 可以在索引中定義多個列(例如,州加上城市)。這樣的索引僅在以州加城市的順序排序時有用。如果想按城市排序,則這種索引沒有用處。
    沒有嚴格的規則要求什么應該索引,何時索引。大多數DBMS提供了可用來確定索引效率的實用程序,應該經常使用這些實用程序。

觸發器

附件

附件A

從上面的地址獲取建表語句和數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM