1. 程式人生 > 實用技巧 >【資料庫】《MySQL必知必會》筆記總結

【資料庫】《MySQL必知必會》筆記總結

MySQL必知必會

簡介

《MySQL必知必會》的學習筆記和總結。

書籍連結
電子書提取碼: 待更新

瞭解SQL

資料庫基礎

什麼是資料庫

資料庫(database):儲存有組織的資料的容器(通常是一個文
件或一組檔案)。

確切地說,資料庫軟體應稱為DBMS(資料庫管理系統)。資料庫是通過DBMS建立和操縱的容器。資料庫可以是儲存在硬裝置上的檔案,但也可以不是。

表(table):某種特定型別資料的結構化清單。

表名的唯一性取決於多個因素,如資料庫名和表名等的結合。這表示,雖然在相同資料庫中不能兩次使用相同的表名,但在不同的資料庫中卻可以使用相同的表名。

表具有一些特性,這些特性定義了資料在表中如何儲存,描述表的這些特性就是所謂的模式,模式可以用來描述資料庫中特定的表以及整個資料庫(和其中表的關係)。

模式(schema):關於資料庫和表的佈局及特性的資訊。

列和資料型別

列(column):表中的一個欄位。所有表都是由一個或多個列組成的。

資料型別(datatype):所容許的資料的型別。每個表列都有相應的資料型別,它限制(或容許)該列中儲存的資料。

行(row):表中的一個記錄。

行(row)有時也稱為資料庫記錄(record)。

主鍵

主鍵(primary key):一列(或一組列),其值能夠唯一區分表中每個行。

應該儘量應保證他們建立的每個表具有一個主鍵,以便於以後的資料操縱和管理。

表中的任何列都可以作為主鍵,只要它滿足以下條件:

  • 任意兩列都不具有相同的主鍵值;
  • 每個列都必須具有一個主鍵值(主鍵列不允許NULL值)。

除了以上的條件,使用主鍵幾個比較好的習慣:

  • 不更新主鍵列中的值;
  • 不重用主鍵列的值;
  • 不在主鍵列中使用可能會更改的值。

當然還有一種重要的鍵,即外來鍵,後面會介紹到。

什麼是SQL

SQL是一種專門用來與資料庫通訊的語言。設計SQL的目的是很好地完成一項任務,即提供一種從資料庫中讀寫資料的簡單有效的方法。

SQL有如下的優點:

  • SQL不是某個特定資料庫供應商專有的語言。幾乎所有重要的DBMS都支援SQL,所以,學習此語言使你幾乎能與所有資料庫打交道。
  • SQL簡單易學。它的語句全都是由描述性很強的英語單片語成,而且這些單詞的數目不多。
  • SQL儘管看上去很簡單,但它實際上是一種強有力的語言,靈活使用其語言元素,可以進行非常複雜和高階的資料庫操作。

值得注意的是,不要認為所有的SQL語法是完全可移植的。

MySQL簡介

什麼是MySQL

MySQL是一種DBMS(資料庫管理系統),即它是一種資料庫軟體。

MySQL版本

TODO: 待更新。

使用MySQL

選擇資料庫

# 建立資料庫
create database learnDB;
# 使用資料庫
use learnDB

瞭解資料庫和表

show databases;

返回可用資料庫的一個列表。包含在這個列表中的可能是MySQL內部使用的資料庫。

show tables;

返回當前選擇的資料庫內可用表的列表。

show columns from table;

要求給出一個表名,它對每個欄位返回一行,行中包含欄位名、資料型別、是否允許NULL、鍵資訊、預設值以及其他資訊。

describe table;

它是show columns from table;的一種快捷形式。

自動增量:某些表列需要唯一值。在每個行新增到表中時,MySQL可以自動地為每個行分配下一個可用編號,不用在新增一行時手動分配唯一值(這樣做必須記住最後一次使用的值)。這個功能就是所謂的自動增量。

其他show語句

  • show status; 用於顯示廣泛的伺服器狀態資訊。
  • show create database;show create table; 分別用來顯示建立特定資料庫或表。
  • show grants; 用來顯示授予使用者(所有使用者或者特定使用者)的安全許可權。
  • show errors;show warnings; 用來顯示伺服器錯誤或警告資訊。

檢索資料

select語句

為了使用SELECT檢索表資料,必須至少給出兩條資訊——想選擇什麼,以及從什麼地方選擇。

檢索單個列

select prod_name
from products;

從products表中檢索一個名為prod_name的列。

檢索多個列

select prod_id, prod_name, prod_price
from products;

從products表中檢索一個名為prod_id、prod_name、prod_price的列。

檢索所有列

select *
from products;

使用萬用字元有一個大優點。由於不明確指定列名(因為星號檢索每個列),所以能檢索出名字未知的列。

檢索不同的行

select distinct vend_id
from products;

只返回不同(唯一)的vend_id行,如果使用DISTINCT關鍵字,它必須直接放在列名的前面。

注意:不能部分使用DISTINCT。DISTINCT關鍵字應用於所有列而不僅是前置它的列。如果給出 SELECT DISTINCT vend_id, prod_price,除非指定的兩個列都不同,否則所有行都將被檢索出來。

限制結果

select distinct vend_id, prod_price
from products
limit 5;

LIMIT 5指示MySQL返回不多於5行。

select prod_name
from products
limit 4 offset 3;

意為從行3開始取4行,等效於 select prod_name from products limit 3, 4;

使用完全限定的表名

select products.prod_name
from products
limit 4 offset 3;

排序檢索資料

排序資料

其實,檢索出的資料並不是以純粹的隨機順序顯示的。如果不明確控制的話,不能(也不應該)依賴該排序順序。關係資料庫設計理論認為,如果不明確規定排序順序,則不應該假定檢索出的資料的順序有意義。

子句(clause):SQL語句由子句構成,有些子句是必需的,而有的是可選的。一個子句通常由一個關鍵字和所提供的資料組成。

select prod_name
from products
order by prod_name;

指示MySQL對prod_name列以字母順序排序資料。

按多個列排序

select prod_id, prod_price, prod_name
from products
order by prod_price, prod_name;

檢索3個列,並按其中兩個列對結果進行排序——首先按價格,然後再按名稱排序。

指定排序方向

資料排序預設為升序排序,為了進行降序排序,必須指定DESC關鍵字。

select prod_id, prod_price, prod_name
from products
order by prod_price desc;

按價格以降序排序產品。

注意:DESC關鍵字只應用到直接位於其前面的列名。如果想在多個列上進行降序排序,必須對每個列指定DESC關鍵字。

注意:在給出ORDER BY子句時,應該保證它位於FROM子句之後。如果使用LIMIT,它必須位於ORDER BY之後。使用子句的次序不對將產生錯誤訊息。

過濾資料

使用WHERE子句

只檢索所需資料需要指定搜尋條件(search criteria),搜尋條件也稱為過濾條件(filter condition)。WHERE子句在表名(FROM子句)之後給出。

select prod_id, prod_price, prod_name
from products
where prod_price = 2.50;

從products表中檢索兩個列,但不返回所有行,只返回prod_price值為2.50的行,

注意:在同時使用ORDER BY和WHERE子句時,應該讓ORDER BY位於WHERE之後,否則將會產生錯誤。

WHERE子句操作符

MySQL支援的所有條件操作符:

操作符 解釋
= 等於
<> 不等於
!= 不等於
< 小於
<= 小於等於
> 大於
>= 大於等於
BETWEEN 在指定的兩個值之間
select prod_id, prod_price, prod_name
from products
where prod_price between 5 and 10;

它檢索價格在5美元和10美元之間的所有產品。BETWEEN匹配範圍中所有的值,包括指定的開始值和結束值。

空值檢查

NULL 無值(no value):它與欄位包含0、空字串或僅僅包含空格不同。

SELECT語句有一個特殊的WHERE子句,可用來檢查具有NULL值的列。這個WHERE子句就是IS NULL子句。

select cust_id
from customers
where cust_email is null;

注意:在通過過濾選擇出不具有特定值的行時,你可能希望返回具有NULL值的行。但是,不行。因為未知具有特殊的含義,資料庫不知道它們是否匹配,所以在匹配過濾
或不匹配過濾時不返回它們。因此,在過濾資料時,一定要驗證返回資料中確實給出了被過濾列具有NULL的行。

資料過濾

組合WHERE子句

為了進行更強的過濾控制,MySQL允許給出多個WHERE子句。這些子句可以兩種方式使用:以AND子句的方式或OR子句的方式使用。

操作符(operator):用來聯結或改變WHERE子句中的子句的關鍵字。也稱為邏輯操作符(logical operator)。

AND:用在WHERE子句中的關鍵字,用來指示檢索滿足所有給定條件的行。

select vend_id, prod_price
from products
where vend_id = 1003 and prod_price <= 10;

此SQL語句檢索由供應商1003製造且價格小於等於10美元的所有產品的名稱和價格。

OR:WHERE子句中使用的關鍵字,用來表示檢索匹配任一給定條件的行。

select vend_id, prod_price
from products where vend_id = 1002 or vend_id = 1003;

此SQL語句檢索由任一個指定供應商製造的所有產品的產品名和價格。

注意:任何時候使用具有AND和OR操作符的WHERE子句,都應該使用圓括號明確地分組操作符。不要過分依賴預設計算次序,即使它確實是你想要的東西也是如此。使用圓括號沒有什麼壞處,它能消除歧義。

IN操作符

IN操作符用來指定條件範圍,範圍中的每個條件都可以進行匹配。IN取合法值的由逗號分隔的清單,全都括在圓括號中。

IN:WHERE子句中用來指定要匹配值的清單的關鍵字,功能與OR相當。

select prod_name, prod_price
from products
where vend_id in (1002, 1003) order by prod_name;

檢索供應商1002和1003製造的所有產品。

為什麼要使用IN操作符?它有如下優點:

  • 在使用長的合法選項清單時,IN操作符的語法更清楚且更直觀。
  • 在使用IN時,計算的次序更容易管理(因為使用的操作符更少)。
  • IN操作符一般比OR操作符清單執行更快。
  • IN的最大優點是可以包含其他SELECT語句,使得能夠更動態地建
    立WHERE子句。

NOT操作符

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

NOT:WHERE子句中用來否定後跟條件的關鍵字。

select prod_name, prod_price
from products
where vend_id not in (1002, 1003) order by prod_name;

檢索供應商1002和1003之外製造的所有產品。

MySQL支援使用NOT對IN、BETWEEN和EXISTS子句取反,這與多數其他DBMS允許使用NOT對各種條件取反有很大的差別。

用萬用字元進行過濾

LIKE操作符

萬用字元(wildcard):用來匹配值的一部分的特殊字元。它本身實際是SQL的WHERE子句中有特殊含義的字元

搜尋模式(search pattern):由字面值、萬用字元或兩者組合構成的搜尋條件。

為在搜尋子句中使用萬用字元,必須使用LIKE操作符。LIKE指示MySQL,後跟的搜尋模式利用萬用字元匹配而不是直接相等匹配進行比較。

操作符何時不是操作符?答案是在它作為謂詞(predicate)時。從技術上說,LIKE是謂詞而不是操作符。

百分號萬用字元

在搜尋串中,%表示任何字元出現任意次數。

select prod_id, prod_name
from products
where prod_name like 'jet%';

將檢索任意以jet起頭的prod_name。

注意:%代表搜尋模式中給定位置的0個、1個或多個字元。

尾空格可能會干擾萬用字元匹配。解決這個問題的一個簡單的辦法是在搜尋模式最後附加一個%。一個更好的辦法是使用函式去掉首尾空格。

雖然似乎%萬用字元可以匹配任何東西,但有一個例外,即NULL。即使是WHERE prod_name LIKE '%'也不能匹配用值NULL作為產品名的行。

下劃線萬用字元

下劃線只匹配單個字元而不是多個字元。

select prod_id, prod_name
from products
where prod_name like '_ ton anvil';

使用萬用字元的技巧

萬用字元搜尋的處理一般要比前面討論的其他搜尋所花時間更長。

一些使用萬用字元要記住的技巧:

  • 不要過度使用萬用字元。如果其他操作符能達到相同的目的,應該使用其他操作符。
  • 在確實需要使用萬用字元時,除非絕對有必要,否則不要把它們用在搜尋模式的開始處。把萬用字元置於搜尋模式的開始處,搜尋起來是最慢的。
  • 仔細注意萬用字元的位置。如果放錯地方,可能不會返回想要的資料。

用正則表示式進行搜尋

正則表示式是用來匹配文字的特殊的串(字元集合)。所有種類的程式設計語言、文字編輯器、作業系統等都支援正則表示式。

使用MySQL正則表示式

MySQL僅支援多數正則表示式實現的一個很小的子集。

基本字元匹配

select prod_name
from products
where prod_name regexp '1000' order by prod_name;

檢索列prod_name包含文字1000的所有行。

select prod_name
from products
where prod_name regexp '.000' order by prod_name;

.是正則表示式語言中一個特殊的字元。它表示匹配任意一個字元,因此,1000和2000等都匹配且返回。

LIKE和REGEXP的一個重要差別

select prod_name
from products
where prod_name like '1000'
order by prod_name;

select prod_name
from products
where prod_name regexp '1000' order by prod_name;

執行上述兩條語句,會發現第一條語句不返回資料,而第二條語句返回一行。為什麼?
因為LIKE匹配整個列。如果被匹配的文字在列值中出現,LIKE將不會找到它,相應的行也不被返回(除非使用萬用字元)。而REGEXP在列值內進行匹配,如果被匹配的文字在列值中出現,REGEXP將會找到它,相應的行將被返回。

要讓REGEXP用來匹配整個列值(從而起與LIKE相同的作用),使用^$定位符(anchor)即可。

MySQL中的正則表示式匹配(自版本3.23.4後)不區分大小寫,即大寫和小寫都匹配)。為區分大小寫,可使用BINARY關鍵字。

進行OR匹配

select prod_name
from products
where prod_name regexp '1000|2000' order by prod_name;

|為正則表示式的OR操作符。它表示匹配其中之一,因此1000和2000都匹配並返回。

使用|從功能上類似於在SELECT語句中使用OR語句,多個OR條件可併入單個正則表示式。

匹配幾個字元之一

select prod_name
from products
where prod_name regexp '[123] Ton' order by prod_name;

[123]定義一組字元,它的意思是匹配1或2或3,因此,1 ton和2 ton都匹配且返回(沒有3 ton)。

匹配範圍

select prod_name
from products
where prod_name regexp '[1-5] Ton' order by prod_name;

[1-5]定義了一個範圍,這個表示式意思是匹配1到5,因此返回3個匹配行。

匹配特殊字元

為了匹配特殊字元,必須用\\為前導。\\-表示查詢-\\.表示查詢.

匹配字元類

為更方便工作,可以使用預定義的字符集,稱為字元類。

字元類 說明
[:alnum:] 任意字母和數字(同[a-zA-Z0-9]
[:alpha:] 任意字元(同[a-zA-Z]
[:blank:] 空格和製表(同[\\t]
[:cntrl:] ASCII控制字元(ASCII 0到31和127)
[:digit:] 任意數字(同[0-9]
[:graph:] [:print:]相同,但不包括空格
[:lower:] 任意小寫字母(同[a-z]
[:print:] 任意可列印字元
[:punct:] 既不在[:alnum:]又不在[:cntrl:]中的任意字元
[:space:] 包括空格在內的任意空白字元(同[\\f\\n\\r\\t\\v]
[:upper:] 任意大寫字母(同[A-Z]
[:xdigit:] 任意十六進位制數字(同[a-fA-F0-9]

匹配多個例項

匹配多個例項可以用正則表示式重複元字元來完成。

正則表示式重複元字元:

元字元 說明
* 0個或多個匹配
+ 1個或多個匹配(等價於{1,})
? 0個或1個匹配(等價於{0, 1})
{n} 指定數目的匹配
{n,} 不少於指定數目的匹配
{n,m} 匹配數目的範圍(m不超過255
select prod_name
from products
where prod_name regexp '\\([0-9] sticks?\\)'
order by prod_name;

# 輸出
+----------------+
| prod_name      |
+----------------+
| TNT (1 stick)  |
| TNT (5 sticks) |
+----------------+
select prod_name
from products
where prod_name regexp '[[:digit:]]{4}'
order by prod_name;

# 輸出:匹配連在一起的任意4位數字。
+--------------+
| prod_name    |
+--------------+
| JetPack 1000 |
| JetPack 2000 |
+--------------+

定位符

為了匹配特定位置的文字,需要使用定位符。

定位元字元 說明
^ 文字的開始
$ 文字的結尾
[[:<:]] 詞的開始
[[:>:]] 詞的結尾
select prod_name
from products
where prod_name regexp '^[0-9\\.]'
order by prod_name;

# 輸出
+--------------+
| prod_name    |
+--------------+
| .5 ton anvil |
| 1 ton anvil  |
| 2 ton anvil  |
+--------------+

^的雙重用途

  • 在集合中(用[]定義),用它來否定該集合。
  • 否則,用來指串的開始處。

LIKE和REGEXP的不同在於,LIKE匹配整個串而REGEXP匹配子串。利用定位符,通過用^開始每個表示式,用$結束每個表示式,可以使REGEXP的作用與LIKE一樣。

建立計算欄位

在有的情況下,儲存在表中的資料都不是應用程式所需要的。我們需要直接從資料庫中檢索出轉換、計算或格式化過的資料;而不是檢索出資料,然後再在客戶機應用程式或報告程式中重新格式化。

計算欄位並不實際存在於資料庫表中。計算欄位是執行時在SELECT語句內建立的。

欄位(field):基本上與列(column)的意思相同,經常互換使用,不過資料庫列一般稱為列,而術語欄位通常用在計算欄位的連線上。

拼接欄位

拼接(concatenate):將值聯結到一起構成單個值。

在MySQL的SELECT語句中,可使用Concat()函式來拼接兩個列。

注意:多數DBMS使用+或||來實現拼接,MySQL則使用Concat()函式來實現。當把SQL語句轉換成MySQL語句時一定要把這個區別銘記在心。

select concat(vend_name, '(', vend_country, ')')
from vendors
order by vend_name;

# 輸出
+-------------------------------------------+
| concat(vend_name, '(', vend_country, ')') |
+-------------------------------------------+
| ACME(USA)                                 |
| Anvils R Us(USA)                          |
| Furball Inc.(USA)                         |
| Jet Set(England)                          |
| Jouets Et Ours(France)                    |
| LT Supplies(USA)                          |
+-------------------------------------------+

別名(alias)是一個欄位或值的替換名。別名用AS關鍵字賦予。

select concat(vend_name, '(', vend_country, ')') as vend_title
from vendors
order by vend_name;

# 輸出
+------------------------+
| vend_title             |
+------------------------+
| ACME(USA)              |
| Anvils R Us(USA)       |
| Furball Inc.(USA)      |
| Jet Set(England)       |
| Jouets Et Ours(France) |
| LT Supplies(USA)       |
+------------------------+

執行算術計算

計算欄位的另一常見用途是對檢索出的資料進行算術計算。

# 檢索訂單號20005中的所有物品:
select prod_id, quantity, item_price
from orderitems
where order_num = 20005;

# 輸出
+---------+----------+------------+
| prod_id | quantity | item_price |
+---------+----------+------------+
| ANV01   |       10 |       5.99 |
| ANV02   |        3 |       9.99 |
| TNT2    |        5 |      10.00 |
| FB      |        1 |      10.00 |
+---------+----------+------------+

# 下彙總物品的價格(單價乘以訂購數量)
select prod_id, quantity, item_price, quantity*item_price as expanded_price
from orderitems
where order_num = 20005;

# 輸出

+---------+----------+------------+----------------+
| prod_id | quantity | item_price | expanded_price |
+---------+----------+------------+----------------+
| ANV01   |       10 |       5.99 |          59.90 |
| ANV02   |        3 |       9.99 |          29.97 |
| TNT2    |        5 |      10.00 |          50.00 |
| FB      |        1 |      10.00 |          10.00 |
+---------+----------+------------+----------------+

使用資料處理函式

函式

SQL支援利用函式來處理資料,函式一般是在資料上執行的,它給資料的轉換和處理提供了方便。

由於函式的可移植性不是那麼強,如果決定使用函式,應該確保做好程式碼註釋,以便以後能確切知道其含義。

使用函式

大多數SQL實現支援以下型別的函式:

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

文字處理函式

select vend_name, upper(vend_name) as vend_name_upcase
from vendors
order by vend_name;

# 輸出
+----------------+------------------+
| vend_name      | vend_name_upcase |
+----------------+------------------+
| ACME           | ACME             |
| Anvils R Us    | ANVILS R US      |
| Furball Inc.   | FURBALL INC.     |
| Jet Set        | JET SET          |
| Jouets Et Ours | JOUETS ET OURS   |
| LT Supplies    | LT SUPPLIES      |
+----------------+------------------+

Upper()將文字轉換為大寫,因此本例子中每個供應商都列出兩次,第一次為vendors表中儲存的值,第二次作為列vend_name_upcase轉換為大寫。

常用的文字處理函式:

函式 解釋
left() 返回串左邊的字元
length() 返回串的長度
locate() 找出串的一個子串
lower() 將串轉換為小寫
upper() 將串轉換為大寫
ltrim() 去掉串左邊的空格
rtrim() 去掉串右邊的空格
soundex() 返回串的soundex
substring() 返回子串的字元

soundex是一個將任何文字串轉換為描述其語音表示的字母數字模式的演算法。

# customers表中有一個顧客Coyote Inc.,其聯絡名為Y.Lee。
# 但如果這是輸入錯誤,此聯絡名實際應該是Y.Lie,怎麼辦?
select cust_name, cust_contact
from customers
where cust_contact = 'Y. Lie';

# 輸出:顯然,按正確的聯絡名搜尋不會返回資料
# 使用Soundex()函式進行搜尋,它匹配所有發音類似於Y.Lie的聯絡名
select cust_name, cust_contact
from customers
where soundex(cust_contact) = soundex('Y. Lie');

# 輸出
+-------------+--------------+
| cust_name   | cust_contact |
+-------------+--------------+
| Coyote Inc. | Y Lee        |
+-------------+--------------+

日期和時間處理函式

日期和時間採用相應的資料型別和特殊的格式儲存,以便能快速和有效地排序或過濾,並且節省物理儲存空間。

常用的日期和時間處理函式:

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

值得注意的是,。無論你什麼時候指定一個日期,不管是插入或更新表值還是用WHERE子句進行過濾,日期必須為格式yyyy-mm-dd

select cust_id, order_num, order_date
from orders
where order_date = '2005-09-01';

# 輸出
+---------+-----------+---------------------+
| cust_id | order_num | order_date          |
+---------+-----------+---------------------+
|   10001 |     20005 | 2005-09-01 00:00:00 |
+---------+-----------+---------------------+

使用WHERE order_date = '2005-09-01'可靠嗎?order_date的資料型別為datetime。這種型別儲存日期及時間值。樣例表中的值全都具有時間值00:00:00,但實際中很可能並不總是這樣。

解決辦法是指示MySQL僅將給出的日期與列中的日期部分進行比較,而不是將給出的日期與整個列值進行比較。

select cust_id, order_num, order_date
from orders
where date(order_date) = '2005-09-01';
# 範圍比較
select cust_id, order_num, order_date
from orders
where year(order_date) = 2005 and month(order_date) = 9;

# 輸出:2005年9月的所有資料
+---------+-----------+---------------------+
| cust_id | order_num | order_date          |
+---------+-----------+---------------------+
|   10001 |     20005 | 2005-09-01 00:00:00 |
|   10003 |     20006 | 2005-09-12 00:00:00 |
|   10004 |     20007 | 2005-09-30 00:00:00 |
+---------+-----------+---------------------+

數值處理函式

數值處理函式僅處理數值資料。這些函式一般主要用於代數、三角或幾何運算。

常用的數值處理函式:

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

彙總資料

聚集函式

經常需要彙總資料而不用把它們實際檢索出來,為此MySQL提供了專門的函式。使用這些函式,MySQL查詢可用於檢索資料,以便分析和報表生成。

這種型別的檢索例子有以下幾種:

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

聚集函式(aggergate function):執行在行組上,計算和返回單個值的函式。

SQL聚集函式:

函式 解釋
AVG() 返回某列的平均值
COUNT() 返回某列的行數
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列值之和

AVG()函式

AVG()通過對錶中行數計數並計算特定列值之和,求得該列的平均值。AVG()可用來返回所有列的平均值,也可以用來返回特定列或行的平均值。

# 返回products表中所有產品的平均價格
select avg(prod_price) as avg_price
from products;

# 輸出
+-----------+
| avg_price |
+-----------+
| 16.133571 |
+-----------+
# 返回products表中1003產品的平均價格
select avg(prod_price) as avg_price
from products
where vend_id = 1003;

# 輸出
+-----------+
| avg_price |
+-----------+
| 13.212857 |
+-----------+

注意:AVG()只能用來確定特定數值列的平均值,而且列名必須作為函式引數給出。為了獲得多個列的平均值,必須使用多個AVG()函式。AVG()函式忽略列值為NULL的行。

COUNT()函式

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

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

  • 使用COUNT(*)對錶中行的數目進行計數,不管表列中包含的是空值(NULL)還是非空值。
  • 使用COUNT(column)對特定列中具有值的行進行計數,忽略NULL值。
# 返回customers表中客戶的總數
select count(*) as num_cust
from customers;

# 輸出
+----------+
| num_cust |
+----------+
|        5 |
+----------+
select count(cust_email) as num_cust
from customers;

# 輸出: NULL被忽略
+----------+
| num_cust |
+----------+
|        3 |
+----------+

MAX()、MIN()函式

MAX()、MIN()分別返回指定列中的最大值和最小值,它們都要求指定列名。

# 返回products表中最貴的物品的價格
select max(prod_price) as max_price
from products;

# 輸出
+-----------+
| max_price |
+-----------+
|     55.00 |
+-----------+
# 返回products表中最便宜的物品的價格
select min(prod_price) as min_price
from products;

# 輸出
+-----------+
| min_price |
+-----------+
|      2.50 |
+-----------+

SUM()函式

SUM()用來返回指定列值的和。

# 返回所有quantity值之和
select sum(quantity) as items_ordered
from orderitems
where order_num = 20005;

# 輸出
+---------------+
| items_ordered |
+---------------+
|            19 |
+---------------+
# 返回所有item_price*quantity值之和
select sum(item_price*quantity) as total_price
from orderitems
where order_num = 20005;

# 輸出
+-------------+
| total_price |
+-------------+
|      149.87 |
+-------------+

聚焦不同值

  • 對所有行執行計算,指定ALL引數或者不給引數(因為ALL是預設行為)。
  • 只包含不同的值,指定DISTINCT引數。
select avg(distinct prod_price) as avg_price
from products
where vend_id = 1003;

# 輸出
+-----------+
| avg_price |
+-----------+
| 15.998000 |
+-----------+

組合聚焦函式

但實際上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;

# 輸出
+-----------+-----------+-----------+-----------+
| num_items | price_min | price_max | price_avg |
+-----------+-----------+-----------+-----------+
|        14 |      2.50 |     55.00 | 16.133571 |
+-----------+-----------+-----------+-----------+

分組資料

資料分組

分組允許把資料分為多個邏輯組,以便能對每個組進行聚集計算。

建立分組

# 一個例子來理解分組
select vend_id, count(*) as num_prods
from products
group by vend_id;

# 輸出
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
|    1001 |         3 |
|    1002 |         2 |
|    1003 |         7 |
|    1005 |         2 |
+---------+-----------+

GROUP BY子句指示MySQL按vend_id排序並分組資料。這導致對每個vend_id而不是整個表計算num_prods一次。

在具體使用GROUP BY子句前,需要知道一些重要的規定:

  • GROUP BY子句可以包含任意數目的列。這使得能對分組進行巢狀,為資料分組提供更細緻的控制。
  • 如果在GROUP BY子句中嵌套了分組,資料將在最後規定的分組上進行彙總。換句話說,在建立分組時,指定的所有列都一起計算(所以不能從個別的列取回資料)。
  • GROUP BY子句中列出的每個列都必須是檢索列或有效的表示式(但不能是聚集函式)。如果在SELECT中使用表示式,則必須在GROUP BY子句中指定相同的表示式。不能使用別名。
  • 除聚集計算語句外,SELECT語句中的每個列都必須在GROUP BY子句中給出。
  • 如果分組列中具有NULL值,則NULL將作為一個分組返回。如果列中有多行NULL值,它們將分為一組。
  • GROUP BY子句必須出現在WHERE子句之後,ORDER BY子句之前。

使用WITH ROLLUP關鍵字,可以得到每個分組以及每個分組彙總級別(針對每個分組)的值。

select vend_id, count(*) as num_prods
from products
group by vend_id with rollup;

# 輸出
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
|    1001 |         3 |
|    1002 |         2 |
|    1003 |         7 |
|    1005 |         2 |
|    NULL |        14 |
+---------+-----------+

過濾分組

HAVING非常類似於WHERE。事實上,目前為止所學過的所有型別的WHERE子句都可以用HAVING來替代。唯一的差別是WHERE過濾行,而HAVING過濾分組。

有關WHERE的所有這些技術和選項都適用於HAVING。它們的句法是相同的,只是關鍵字有差別。WHERE在資料分組前進行過濾,HAVING在資料分組後進行過濾。 這是一個重要的區別,WHERE排除的行不包括在分組中。這可能會改變計算值,從而影響HAVING子句中基於這些值過濾掉的分組。

# 返回有三個以上的
select vend_id, count(*) as num_prods
from products
group by vend_id
having count(*) >= 3;

# 輸出
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
|    1001 |         3 |
|    1003 |         7 |
+---------+-----------+
# 列出具有2個(含)以上、價格為10(含)以上的產品的供應商
select vend_id, count(*) as num_prods
from products
where prod_price >= 10
group by vend_id
having count(*) >= 2;

# 輸出
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
|    1003 |         4 |
|    1005 |         2 |
+---------+-----------+

分組和排序

GROUP BY與ORDER BY的差別:

GROUP BY ORDER BY
排序產生的輸出 分組行。但輸出可能不是分組的順序
任意列都可以使用(甚至非選擇的列也可以使用) 只可能使用選擇列或表示式列,而且必須使用每個選擇列表達式
不一定需要 如果與聚集函式一起使用列(或表示式),則必須使用

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

# 檢索總計訂單價格大於等於50的訂單的訂單號和總計訂單價格
select order_num, sum(quantity*item_price) as ordertotal
from orderitems
group by order_num
having sum(quantity*item_price) >= 50;

# 輸出
+-----------+------------+
| order_num | ordertotal |
+-----------+------------+
|     20005 |     149.87 |
|     20006 |      55.00 |
|     20007 |    1000.00 |
|     20008 |     125.00 |
+-----------+------------+
# 為按總計訂單價格排序輸出,需要新增ORDER BY子句
select order_num, sum(quantity*item_price) as ordertotal
from orderitems
group by order_num
having sum(quantity*item_price) >= 50
order by ordertotal;

# 輸出
+-----------+------------+
| order_num | ordertotal |
+-----------+------------+
|     20006 |      55.00 |
|     20008 |     125.00 |
|     20005 |     149.87 |
|     20007 |    1000.00 |
+-----------+------------+

SELECT子句順序

SELECT子句及其順序:

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

使用子查詢

子查詢

查詢(query):任何SQL語句都是查詢。但此術語一般指SELECT語句。

SQL還允許建立子查詢(subquery),即巢狀在其他查詢中的查詢。

利用子查詢進行過濾

假如需要列出訂購物品TNT2的所有客戶,應該怎樣檢索?

# 第一步:檢索包含物品TNT2的所有訂單的編號。
select order_num
from orderitems
where prod_id = 'TNT2';

# 輸出
+-----------+
| order_num |
+-----------+
|     20005 |
|     20007 |
+-----------+
# 第二步:檢索具有前一步驟列出的訂單編號的所有客戶的ID。
select cust_id
from orders
where order_num in (20005, 20007);

# 輸出
+---------+
| cust_id |
+---------+
|   10001 |
|   10004 |
+---------+
# 第三步:檢索前一步驟返回的所有客戶ID的客戶資訊。
select cust_name, cust_contact
from customers
where cust_id in (10001, 10004);

# 輸出
+----------------+--------------+
| cust_name      | cust_contact |
+----------------+--------------+
| Coyote Inc.    | Y Lee        |
| Yosemite Place | Y Sam        |
+----------------+--------------+
# 將上面的三步進行合併
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 = 'TNT2'));

# 輸出
+----------------+--------------+
| cust_name      | cust_contact |
+----------------+--------------+
| Coyote Inc.    | Y Lee        |
| Yosemite Place | Y Sam        |
+----------------+--------------+

對於能巢狀的子查詢的數目沒有限制,不過在實際使用時由於效能的限制,不能巢狀太多的子查詢。使用子查詢並不總是執行這種型別的資料檢索的最有效的方法。

作為計算欄位使用子查詢

使用子查詢的另一方法是建立計算欄位。

# 如需要顯示customers表中每個客戶的訂單總數。
# 第一步:從customers表中檢索客戶列表。
# 第二步:對於檢索出的每個客戶,統計其在orders表中的訂單數目。

select cust_name,
       cust_state,
       (select count(*)
        from orders
        where orders.cust_id = customers.cust_id) as orders
from customers
order by cust_name;

# 輸出
+----------------+------------+--------+
| cust_name      | cust_state | orders |
+----------------+------------+--------+
| Coyote Inc.    | MI         |      2 |
| E Fudd         | IL         |      1 |
| Mouse House    | OH         |      0 |
| Wascals        | IN         |      1 |
| Yosemite Place | AZ         |      1 |
+----------------+------------+--------+

相關子查詢(correlated subquery):涉及外部查詢的子查詢。

聯結表

聯結

SQL最強大的功能之一就是能在資料檢索查詢的執行中聯結(join)表。

關係表

關係表的設計就是要保證把資訊分解成多個表,一類資料一個表。各表通過某些常用的值(即關係設計中的關係(relational))互相關聯。

外來鍵(foreign key):外來鍵為某個表中的一列,它包含另一個表的主鍵值,定義了兩個表之間的關係。

關係資料可以有效地儲存和方便地處理。因此,關係資料庫的可伸縮性遠比非關係資料庫要好。

可伸縮性(scale):能夠適應不斷增加的工作量而不失敗。設計良好的資料庫或應用程式稱之為可伸縮性好(scale well)。

為什麼要使用聯結

如果資料儲存在多個表中,怎樣用單條SELECT語句檢索出資料?

答案是使用聯結。簡單地說,聯結是一種機制,用來在一條SELECT語句中關聯表,因此稱之為聯結。使用特殊的語法,可以聯結多個表返回一組輸出,聯結在執行時關聯表中正確的行。

維護引用完整性,它是通過在表的定義中指定主鍵和外來鍵來實現的。這個後面會細講!

建立聯結

# 建立聯結
select vend_name, prod_name, prod_price
from vendors, products
where vendors.vend_id = products.vend_id
order by vend_name, prod_name;

# 輸出
+-------------+----------------+------------+
| vend_name   | prod_name      | prod_price |
+-------------+----------------+------------+
| ACME        | Bird seed      |      10.00 |
| ACME        | Carrots        |       2.50 |
| ACME        | Detonator      |      13.00 |
| ACME        | Safe           |      50.00 |
| ACME        | Sling          |       4.49 |
| ACME        | TNT (1 stick)  |       2.50 |
| ACME        | TNT (5 sticks) |      10.00 |
| Anvils R Us | .5 ton anvil   |       5.99 |
| Anvils R Us | 1 ton anvil    |       9.99 |
| Anvils R Us | 2 ton anvil    |      14.99 |
| Jet Set     | JetPack 1000   |      35.00 |
| Jet Set     | JetPack 2000   |      55.00 |
| LT Supplies | Fuses          |       3.42 |
| LT Supplies | Oil can        |       8.99 |
+-------------+----------------+------------+

注意:在引用的列可能出現二義性時,必須使用完全限定列名(用一個點分隔的表名和列名)。如果引用一個沒有用表名限制的具有二義性的列名,MySQL將返回錯誤。

WHERE子句的重要性

在一條SELECT語句中聯結幾個表時,相應的關係是在執行中構造的。在資料庫表的定義中不存在能指示MySQL如何對錶進行聯結的東西。你必須自己做這件事情。

沒有WHERE子句,第一個表中的每個行將與第二個表中的每個行配對,而不管它們邏輯上是否可以配在一起。

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

注意:應該保證所有聯結都有WHERE子句,否則MySQL將返回比想要的資料多得多的資料。同理,應該保證WHERE子句的正確性。不正確的過濾條件將導致MySQL返回不正確的資料。

內部聯結

目前為止所用的聯結稱為等值聯結(equijoin),它基於兩個表之間的相等測試。這種聯結也稱為內部聯結

# 同WHERE子句聯結
select vend_name, prod_name, prod_price
from vendors inner join products on vendors.vend_id = products.vend_id
order by vend_name, prod_name;

# 輸出
| vend_name   | prod_name      | prod_price |
+-------------+----------------+------------+
| ACME        | Bird seed      |      10.00 |
| ACME        | Carrots        |       2.50 |
| ACME        | Detonator      |      13.00 |
| ACME        | Safe           |      50.00 |
| ACME        | Sling          |       4.49 |
| ACME        | TNT (1 stick)  |       2.50 |
| ACME        | TNT (5 sticks) |      10.00 |
| Anvils R Us | .5 ton anvil   |       5.99 |
| Anvils R Us | 1 ton anvil    |       9.99 |
| Anvils R Us | 2 ton anvil    |      14.99 |
| Jet Set     | JetPack 1000   |      35.00 |
| Jet Set     | JetPack 2000   |      55.00 |
| LT Supplies | Fuses          |       3.42 |
| LT Supplies | Oil can        |       8.99 |
+-------------+----------------+------------+

ANSI SQL規範首選INNER JOIN語法。此外,儘管使用WHERE子句定義聯結的確比較簡單,但是使用明確的聯結語法能夠確保不會忘記聯結條件,有時候這樣做也能影響
效能。

聯結多個表

# 顯示編號為20005的訂單中的物品
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 = 20005;

# 輸出
+----------------+-------------+------------+----------+
| prod_name      | vend_name   | prod_price | quantity |
+----------------+-------------+------------+----------+
| .5 ton anvil   | Anvils R Us |       5.99 |       10 |
| 1 ton anvil    | Anvils R Us |       9.99 |        3 |
| TNT (5 sticks) | ACME        |      10.00 |        5 |
| Bird seed      | ACME        |      10.00 |        1 |
+----------------+-------------+------------+----------+

值得一提的是,MySQL在執行時關聯指定的每個表以處理聯結。這種處理可能是非常耗費資源的,因此應該仔細,不要聯結不必要的表。聯結的表越多,效能下降越厲害。

# 重寫子查詢例子:返回訂購產品TNT2的客戶列表
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 = 'TNT2'));

# 等價於
select cust_name, cust_contact
from customers, orders, orderitems
where customers.cust_id = orders.cust_id and orderitems.order_num = orders.order_num and orderitems.prod_id = 'TNT2';

# 輸出
+----------------+--------------+
| cust_name      | cust_contact |
+----------------+--------------+
| Coyote Inc.    | Y Lee        |
| Yosemite Place | Y Sam        |
+----------------+--------------+

建立高階聯結

使用表別名

別名除了用於列名和計算欄位外,SQL還允許給表名起別名。這樣做有兩個主要理由:

  • 縮短SQL語句。
  • 允許在單條SELECT語句中多次使用相同的表。
select cust_name, cust_contact
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 oi.prod_id = 'TNT2';

# 輸出
+----------------+--------------+
| cust_name      | cust_contact |
+----------------+--------------+
| Coyote Inc.    | Y Lee        |
| Yosemite Place | Y Sam        |
+----------------+--------------+

使用不同型別的聯結

自聯結

# 求首先找到生產ID為DTNTR的物品的供應商,然後找出這個供應商生產的其他物品
# 方法1:使用子查詢
select prod_id, prod_name
from products
where vend_id = (select vend_id
                 from products
                 where prod_id = 'DTNTR');

# 方法2:使用自聯結
select p1.prod_id, p1.prod_name
from products as p1, products as p2
where p1.vend_id = p2.vend_id and p2.prod_id = 'DTNTR';

# 輸出
+---------+----------------+
| DTNTR   | Detonator      |
| FB      | Bird seed      |
| FC      | Carrots        |
| SAFE    | Safe           |
| SLING   | Sling          |
| TNT1    | TNT (1 stick)  |
| TNT2    | TNT (5 sticks) |
+---------+----------------+

自聯結通常作為外部語句用來替代從相同表中檢索資料時使用的子查詢語句。雖然最終的結果是相同的,但有時候處理聯結遠比處理子查詢快得多。

自然聯結

自然聯結是這樣一種聯結,其中你只能選擇那些唯一的列。這一般是通過對錶使用萬用字元(SELECT *),對所有其他表的列使用明確的子集來完成的。

事實上,迄今為止我們建立的每個內部聯結都是自然聯結,很可能我們永遠都不會用到不是自然聯結的內部聯結。

外部聯結

聯結包含了那些在相關表中沒有關聯行的行。這種型別的聯結稱為外部聯結。

# 檢索所有客戶及其訂單
select customers.cust_id, orders.order_num
from customers inner join orders on customers.cust_id = orders.cust_id;

# 輸出
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
|   10001 |     20005 |
|   10001 |     20009 |
|   10003 |     20006 |
|   10004 |     20007 |
|   10005 |     20008 |
+---------+-----------+

# 為了檢索所有客戶,包括那些沒有訂單的客戶,使用外部聯結
select customers.cust_id, orders.order_num
from customers left outer join orders on customers.cust_id = orders.cust_id;

# 輸出
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
|   10001 |     20005 |
|   10001 |     20009 |
|   10002 |      NULL |
|   10003 |     20006 |
|   10004 |     20007 |
|   10005 |     20008 |
+---------+-----------+

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

注意:MySQL不支援簡化字元=和=的使用,這兩種操作符在其他DBMS中是很流行的。

存在兩種基本的外部聯結形式:左外部聯結和右外部聯結。它們之間的唯一差別是所關聯的表的順序不同。換句話說,左外部聯結可通過顛倒FROM或WHERE子句中表的順序轉換為右外部聯結。因此,兩種型別的外部聯結可互換使用,而究竟使用哪一種純粹是根據方便而定。

使用帶聚集函式的聯結

# 要檢索所有客戶及每個客戶所下的訂單數
select customers.cust_name,
       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;

# 輸出
+----------------+---------+---------+
| cust_name      | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc.    |   10001 |       2 |
| Wascals        |   10003 |       1 |
| Yosemite Place |   10004 |       1 |
| E Fudd         |   10005 |       1 |
+----------------+---------+---------+

# 包含那些沒有任何下訂單的客戶
select customers.cust_name,
       customers.cust_id,
       count(orders.order_num) as num_ord
from customers left outer join orders on customers.cust_id = orders.cust_id
group by customers.cust_id;

# 輸出
+----------------+---------+---------+
| cust_name      | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc.    |   10001 |       2 |
| Mouse House    |   10002 |       0 |
| Wascals        |   10003 |       1 |
| Yosemite Place |   10004 |       1 |
| E Fudd         |   10005 |       1 |
+----------------+---------+---------+

使用聯結和聯結條件

關於聯結及其使用的某些要點:

  • 注意所使用的聯結型別。一般我們使用內部聯結,但使用外部聯結也是有效的。
  • 保證使用正確的聯結條件,否則將返回不正確的資料。
  • 應該總是提供聯結條件,否則會得出笛卡兒積。
  • 在一個聯結中可以包含多個表,甚至對於每個聯結可以採用不同的聯結型別。雖然這樣做是合法的,一般也很有用,但應該在一起測試它們前,分別測試每個聯結。這將使故障排除更為簡單。

組合查詢

多數SQL查詢都只包含從一個或多個表中返回資料的單條SELECT語句。MySQL也允許執行多個查詢(多條SELECT語句),並將結果作為單個查詢結果集返回。這些組合查詢通常稱為並(union)或複合查詢(compound query)。

需要使用組合查詢的情況:

  • 在單個查詢中從不同的表返回類似結構的資料。
  • 對單個表執行多個查詢,按單個查詢返回資料。

任何具有多個WHERE子句的SELECT語句都可以作為一個組合查詢給出。

建立組合查詢

可用UNION操作符來組合數條SQL查詢。利用UNION,可給出多條SELECT語句,將它們的結果組合成單個結果集。

使用UNION

# 檢索價格不高於5的所有物品
select vend_id, prod_id, prod_price
from products
where prod_price <= 5;

# 輸出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1003 | FC      |       2.50 |
|    1002 | FU1     |       3.42 |
|    1003 | SLING   |       4.49 |
|    1003 | TNT1    |       2.50 |
+---------+---------+------------+

# 找出供應商1001和1002生產的所有物品
select vend_id, prod_id, prod_price
from products
where vend_id in (1001, 1002);

# 輸出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1001 | ANV01   |       5.99 |
|    1001 | ANV02   |       9.99 |
|    1001 | ANV03   |      14.99 |
|    1002 | FU1     |       3.42 |
|    1002 | OL1     |       8.99 |
+---------+---------+------------+

# 組合這兩條語句
select vend_id, prod_id, prod_price
from products
where prod_price <= 5
union
select vend_id, prod_id, prod_price
from products
where vend_id in (1001, 1002);

# 輸出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1003 | FC      |       2.50 |
|    1002 | FU1     |       3.42 |
|    1003 | SLING   |       4.49 |
|    1003 | TNT1    |       2.50 |
|    1001 | ANV01   |       5.99 |
|    1001 | ANV02   |       9.99 |
|    1001 | ANV03   |      14.99 |
|    1002 | OL1     |       8.99 |
+---------+---------+------------+

UNION規則

使用組合查詢則需要注意:

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

包含或取消重複的行

UNION從查詢結果集中自動去除了重複的行(換句話說,它的行為與單條SELECT語句中使用多個WHERE子句條件一樣)。這是UNION的預設行為,但是如果需要,可以改變它。事實上,如果想返回所有匹配行,可使用UNION ALL而不是UNION。

select vend_id, prod_id, prod_price
from products
where prod_price <= 5
union all
select vend_id, prod_id, prod_price
from products
where vend_id in (1001, 1002);

# 輸出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1003 | FC      |       2.50 |
|    1002 | FU1     |       3.42 |
|    1003 | SLING   |       4.49 |
|    1003 | TNT1    |       2.50 |
|    1001 | ANV01   |       5.99 |
|    1001 | ANV02   |       9.99 |
|    1001 | ANV03   |      14.99 |
|    1002 | FU1     |       3.42 |
|    1002 | OL1     |       8.99 |
+---------+---------+------------+

UNION幾乎總是完成與多個WHERE條件相同的工作。UNION ALL為UNION的一種形式,它完成WHERE子句完成不了的工作。如果確實需要每個條件的匹配行全部出現(包括重複行),則必須使用UNION ALL而不是WHERE。

對組合查詢結果排序

SELECT語句的輸出用ORDER BY子句排序。在用UNION組合查詢時,只能使用一條ORDER BY子句,它必須出現在最後一條SELECT語句之後。

select vend_id, prod_id, prod_price
from products
where prod_price <= 5
union all
select vend_id, prod_id, prod_price
from products
where vend_id in (1001, 1002)
order by vend_id, prod_price;

# 輸出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
|    1001 | ANV01   |       5.99 |
|    1001 | ANV02   |       9.99 |
|    1001 | ANV03   |      14.99 |
|    1002 | FU1     |       3.42 |
|    1002 | FU1     |       3.42 |
|    1002 | OL1     |       8.99 |
|    1003 | TNT1    |       2.50 |
|    1003 | FC      |       2.50 |
|    1003 | SLING   |       4.49 |
+---------+---------+------------+

全文字搜尋

理解全文字搜尋

MySQL支援幾種基本的資料庫引擎。但是並非所有的引擎都支援全文字搜尋。兩個最常使用的引擎為MyISAM和InnoDB,前者支援全文字搜尋,而後者不支援。

我們知道利用通配操作符和正則表示式等搜尋機制非常有用,但是它們存在幾個重要的限制:

  • 效能——萬用字元和正則表示式匹配通常要求MySQL嘗試匹配表中所有行(而且這些搜尋極少使用表索引)。因此,由於被搜尋行數不斷增加,這些搜尋可能非常耗時。
  • 明確控制——使用萬用字元和正則表示式匹配,很難(而且並不總是能)明確地控制匹配什麼和不匹配什麼。例如,指定一個詞必須匹配,一個詞必須不匹配,而一個詞僅在第一個詞確實匹配的情況下才可以匹配或者才可以不匹配。
  • 智慧化的結果——雖然基於萬用字元和正則表示式的搜尋提供了非常靈活的搜尋,但它們都不能提供一種智慧化的選擇結果的方法。例如,一個特殊詞的搜尋將會返回包含該詞的所有行,而不區分包含單個匹配的行和包含多個匹配的行(按照可能是更好的匹配來排列它們)。類似,一個特殊詞的搜尋將不會找出不包含該詞但包含其他相關詞的行。

所有這些限制以及更多的限制都可以用全文字搜尋來解決。在使用全文字搜尋時,MySQL不需要分別檢視每個行,不需要分別分析和處理每個詞。MySQL建立指定列中各詞的一個索引,搜尋可以針對這些詞進行。

使用全文字搜尋

為了進行全文字搜尋,必須索引被搜尋的列,而且要隨著資料的改變不斷地重新索引。

啟用全文字搜尋支援

一般在建立表時啟用全文字搜尋。CREATE TABLE語句接受FULLTEXT子句,它給出被索引列的一個逗號分隔的列表。

create table productnotes
(
    note_id int not null auto_increment,
    prod_id char(10) not null,
    note_date datetime not null,
    note_text text null,
    primary key(note_id),
    fulltext(note_text)
) engine=MyISAM;

在定義之後,MySQL自動維護該索引。在增加、更新或刪除行時,索引隨之自動更新。

不要在匯入資料時使用FULLTEXT:更新索引要花時間,雖然不是很多,但畢竟要花時間。如果正在匯入資料到一個新表,此時不應該啟用FULLTEXT索引。應該首先匯入所有資料,然後再修改表,定義FULLTEXT。這樣有助於更快地匯入資料(而且使索引資料的總時間小於在匯入每行時分別進行索引所需的總時間)。

進行全文字搜尋

在索引之後,使用兩個函式Match()和Against()執行全文字搜尋,其中Match()指定被搜尋的列,Against()指定要使用的搜尋表示式

# 檢索單個列note_text
select note_text
from productnotes
where match(note_text) against('rabbit');

# 輸出
+-----------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                             |
+-----------------------------------------------------------------------------------------------------------------------+
| Customer complaint: rabbit has been able to detect trap, food apparently less effective now.                          |
| Quantity varies, sold by the sack load.
All guaranteed to be bright and orange, and suitable for use as rabbit bait. |
+-----------------------------------------------------------------------------------------------------------------------+

此SELECT語句檢索單個列note_text。由於WHERE子句,一個全文字搜尋被執行。Match(note_text)指示MySQL針對指定的列進行搜尋,Against('rabbit')指定詞rabbit作為搜尋文字。由於有兩行包含詞rabbit,這兩個行被返回。

傳遞給 Match() 的值必須與FULLTEXT()定義中的相同。如果指定多個列,則必須列出它們(而且次序正確)。

除非使用BINARY方式,否則全文字搜尋不區分大小寫。

使用查詢擴充套件

查詢擴充套件用來設法放寬所返回的全文字搜尋結果的範圍。

在使用查詢擴充套件時,MySQL對資料和索引進行兩遍掃描來完成搜尋:

  • 首先,進行一個基本的全文字搜尋,找出與搜尋條件匹配的所有行;
  • 其次,MySQL檢查這些匹配行並選擇所有有用的詞。
  • 再其次,MySQL再次進行全文字搜尋,這次不僅使用原來的條件,而且還使用所有有用的詞。

利用查詢擴充套件,能找出可能相關的結果,即使它們並不精確包含所查詢的詞。

# 簡單的全文字搜尋,沒有查詢擴充套件
select note_text
from productnotes
where match(note_text) against('anvils');

# 輸出
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                                                                |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils. |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+

# 使用查詢擴充套件
select note_text
from productnotes
where match(note_text) against('anvils' with query expansion);

# 輸出
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                                                                |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils. |
| Customer complaint:
Sticks not individually wrapped, too easy to mistakenly detonate all at once.
Recommend individual wrapping.                       |
| Customer complaint:
Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead. |
| Please note that no returns will be accepted if safe opened using explosives.                                                                            |
| Customer complaint: rabbit has been able to detect trap, food apparently less effective now.                                                             |
| Customer complaint:
Circular hole in safe floor can apparently be easily cut with handsaw.                                                              |
| Matches not included, recommend purchase of matches or detonator (item DTNTR).                                                                           |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+

第一行包含詞anvils,因此等級最高。第二行與anvils無關,但因為它包含第一行中的兩個詞(customer和recommend),所以也被檢索出來。第3行也包含這兩個相同的詞,但它們在文字中的位置更靠後且分開得更遠,因此也包含這一行,但等級為第三。第三行確實也沒有涉及anvils(按它們的產品名)。

布林文字搜尋

MySQL支援全文字搜尋的另外一種形式,稱為布林方式(boolean mode)。

以布林方式,可以提供關於如下內容的細節:

  • 要匹配的詞。
  • 要排斥的詞(如果某行包含這個詞,則不返回該行,即使它包含其他指定的詞也是如此)。
  • 排列提示(指定某些詞比其他詞更重要,更重要的詞等級更高)。
  • 表示式分組。
  • 另外一些內容。

注意:布林方式不同於迄今為止使用的全文字搜尋語法的地方在於,即使沒有定義
FULLTEXT索引,也可以使用它。但這是一種非常緩慢的操作(其效能將隨著資料量的增加而降低)。

select note_text
from productnotes
where match(note_text) against('heavy' in boolean mode);

# 輸出+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                                                                |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes.                                      |
| Customer complaint: Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead. |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
# 了匹配包含heavy但不包含任意以rope開始的詞的行
select note_text
from productnotes
where match(note_text) against('heavy -rope*' in boolean mode);

# 輸出
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text                                                                                                                                                |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Customer complaint:Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead. |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+

這一次仍然匹配詞heavy,但-rope明確地指示MySQL排除包含rope(任何以rope開始的詞,包括ropes)的行。

兩個全文字搜尋布林操作符-*-排除一個詞,而*是截斷操作符(可想象為用於詞尾的一個萬用字元)。

全文字布林操作符:

布林操作符 解釋
+ 包含,詞必須存在
- 排除,詞必須不出現
> 包含,而且增加等級值
< 包含,且減少等級值
() 把片語成子表示式(允許這些子表示式作為一個組被包含、
排除、排列等)
~ 取消一個詞的排序值
* 詞尾的萬用字元
"" 定義一個短語(與單個詞的列表不一樣,它匹配整個短語以
便包含或排除這個短語)
# 搜尋匹配包含詞rabbit和bait的行
select note_text
from productnotes
where match(note_text) against('+rabbit +bait' in boolean mode);

# 搜尋匹配包含rabbit和bait中的至少一個詞的行。
select note_text
from productnotes
where match(note_text) against('rabbit bait' in boolean mode);

# 搜尋匹配短語rabbit bait而不是匹配兩個詞rabbit和bait。
select note_text
from productnotes
where match(note_text) against('"rabbit bait"' in boolean mode);

# 匹配rabbit和carrot,增加前者的等級,降低後者的等級
select note_text
from productnotes
where match(note_text) against('>rabbit <carrot' in boolean mode);

# 搜尋匹配詞safe和combination,降低後者的等級
select note_text
from productnotes
where match(note_text) against('+safe +(<combination)' in boolean mode);

全文字搜尋的使用說明

關於全文字搜尋的某些重要的說明:

  • 在索引全文字資料時,短詞被忽略且從索引中排除。短詞定義為那些具有3個或3個以下字元的詞(如果需要,這個數目可以更改)。
  • MySQL帶有一個內建的非用詞(stopword)列表,這些詞在索引全文字資料時總是被忽略。如果需要,可以覆蓋這個列表。
  • 許多詞出現的頻率很高,搜尋它們沒有用處(返回太多的結果)。因此,MySQL規定了一條50%規則,如果一個詞出現在50%以上的行中,則將它作為一個非用詞忽略。50%規則不用於IN BOOLEAN MODE。
  • 如果表中的行數少於3行,則全文字搜尋不返回結果(因為每個詞或者不出現,或者至少出現在50%的行中)。
  • 忽略詞中的單引號。例如,don't索引為dont。
  • 不具有詞分隔符(包括日語和漢語)的語言不能恰當地返回全文字搜尋結果。
  • 如前所述,僅在MyISAM資料庫引擎中支援全文字搜尋。

插入資料

資料插入

顧名思義,INSERT是用來插入(或新增)行到資料庫表的。插入可以用幾種方式使用:

  • 插入完整的行。
  • 插入行的一部分。
  • 插入多行。
  • 插入某些查詢的結果。

插入及系統安全:可針對每個表或每個使用者,利用MySQL的安全機制禁止使用INSERT語句。

插入完整的行

把資料插入表中的最簡單的方法是使用基本的INSERT語法,它要求指定表名和被插入到新行中的值。

insert into customers
value(
    null,
    'parzulpan',
    'Haidian',
    'Beijing',
    'BJ',
    '01000',
    'CN',
    null,
    null
);

雖然這種語法很簡單,但並不安全,應該儘量避免使用。上面的SQL語句高度依賴於表中列的定義次序,並且還依賴於其次序容易獲得的資訊。

# 更安全(不過更煩瑣)的方法
insert into customers(
    cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country,
    cust_contact,
    cust_email
)
value(
    'parzulpanA',
    'Haidian',
    'Beijing',
    'BJ',
    '01000',
    'CN',
    null,
    null
);

一般不要使用沒有明確給出列的列表的INSERT語句。使用列的列表能使SQL程式碼繼續發揮作用,即使表結構發生了變化。

注意:不管使用哪種INSERT語法,都必須給出VALUES的正確數目。如果不提供列名,則必須給每個表列提供一個值。如果提供列名,則必須對每個列出的列給出一個值。如果不這樣,將產生一條錯誤訊息,相應的行插入不成功。

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

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

提高整體效能:資料庫經常被多個客戶訪問,對處理什麼請求以及用什麼次序處理進行管理是MySQL的任務。INSERT操作可能很耗時(特別是有很多索引需要更新時),而且它可能降低等待處理的SELECT語句的效能。如果資料檢索是最重要的(通常是這樣),則你可以通過在INSERT和INTO之間新增關鍵字LOW_PRIORITY,指示MySQL
降低INSERT語句的優先順序。

insert low_priority into customers(
    cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country,
    cust_contact,
    cust_email
)
value(
    'parzulpanB',
    'Haidian',
    'Beijing',
    'BJ',
    '01000',
    'CN',
    null,
    null
);

插入多個行

可以使用多條INSERT語句,甚至一次提交它們,每條語句用一個分號結束。

insert into customers(
    cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country,
    cust_contact,
    cust_email
)
value(
    'parzulpanC',
    'Haidian',
    'Beijing',
    'BJ',
    '01000',
    'CN',
    null,
    null
);
insert into customers(
    cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country,
    cust_contact,
    cust_email
)
value(
    'parzulpanD',
    'Haidian',
    'Beijing',
    'BJ',
    '01000',
    'CN',
    null,
    null
);

實際上,MySQL用單條INSERT語句處理多個插入比使用多條INSERT語句快。

insert into customers(
    cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country,
    cust_contact,
    cust_email
)
value(
    'parzulpanE',
    'Haidian',
    'Beijing',
    'BJ',
    '01000',
    'CN',
    null,
    null
),
    (
    'parzulpanF',
    'Haidian',
    'Beijing',
    'BJ',
    '01000',
    'CN',
    null,
    null
);

插入檢索出的資料

可以將一條SELECT語句的結果插入表中。這就是所謂的INSERT SELECT,顧名思義,它是由一條INSERT語句和一條SELECT語句組成的。

insert into customers(
    cust_id,
    cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country,
    cust_contact,
    cust_email
)
select cust_id,
       cust_name,
       cust_address,
       cust_city,
       cust_state,
       cust_zip,
       cust_country,
       cust_contact,
       cust_email
from custonew;

# 使用INSERT SELECT從custnew中將所有資料匯入customers。
# SELECT語句從custnew檢索出要插入的值,而不是列出它們。

更新和刪除資料

更新資料

為了更新(修改)表中的資料,可使用UPDATE語句。可採用兩種方式使用UPDATE:

  • 更新表中特定行。
  • 更新表中所有行。

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

基本的UPDATE語句由3部分組成,分別是:

  • 要更新的表。
  • 列名和它們的新值。
  • 確定要更新行的過濾條件。
# 更新id為10006的電子郵件地址
update customers
set cust_email = '[email protected]'
where cust_id = 10006;
# 更新多個列
update customers
set cust_email = '[email protected]',
    cust_name = 'PARZULPAN'
where cust_id = 10006;

UPDATE語句中可以使用子查詢,使得能用SELECT語句檢索出的資料更新列資料。

注意:如果用UPDATE語句更新多行,並且在更新這些行中的一行或多行時出一個現錯誤,則整個UPDATE操作被取消(錯誤發生前更新的所有行被恢復到它們原來的值)。

為了即使是發生錯誤,也繼續進行更新,可使用IGNORE關鍵字。

update ignore customers
set cust_email = '[email protected]',
    cust_name = 'PARZULPANE',
    cust_state = 'ASAF'
where cust_id = 10006;

刪除資料

為了從一個表中刪除(去掉)資料,使用DELETE語句。可以兩種方式使用DELETE:

  • 從表中刪除特定的行。
  • 從表中刪除所有行。

注意:在使用DELETE時一定要注意細心。因為稍不注意,就會錯誤地刪除表中所有行。

# 刪除id為10006的行
delete from customers
where cust_id = 10006;

注意:如果想從表中刪除所有行,不要使用DELETE。可使用TRUNCATE TABLE語句,它完成相同的工作,但速度更快(TRUNCATE實際是刪除原來的表並重新建立一個表,而不是逐行刪除表中的資料)。

更新和刪除的指導原則

使用UPDATE或DELETE時所遵循的習慣:

  • 除非確實打算更新和刪除每一行,否則絕對不要使用不帶WHERE子句的UPDATE或DELETE語句。
  • 保證每個表都有主鍵,儘可能像WHERE子句那樣使用它(可以指定各主鍵、多個值或值的範圍)。
  • 在對UPDATE或DELETE語句使用WHERE子句前,應該先用SELECT進行測試,保證它過濾的是正確的記錄,以防編寫的WHERE子句不正確。
  • 使用強制實施引用完整性的資料庫,這樣MySQL將不允許刪除具有與其他表相關聯的資料的行。

建立和操縱表

建立表

MySQL不僅用於表資料操縱,而且還可以用來執行資料庫和表的所有操作,包括表本身的建立和處理。

兩種建立表的方法:

  • 使用具有互動式建立和管理表的工具。
  • 表也可以直接用MySQL語句操縱。

表建立基礎

為利用CREATE TABLE建立表,必須給出下列資訊

  • 新表的名字,在關鍵字CREATE TABLE之後給出;
  • 表列的名字和定義,用逗號分隔。
#
create table if not exists customers_p
(
    cust_id         int         not null auto_increment,
    cust_name       char(50)    not null,
    cust_address    char(50)    null,
    cust_city       char(50)    null,
    cust_state      char(5)     null,
    cust_zip        char(10)    null,
    cust_country    char(50)    null,
    cust_contact    char(50)    null,
    cust_email      char(255)   null,
    primary key (cust_id)
) ENGINE=InnoDB;

如果你僅想在一個表不存在時建立它,應該在表名前給出IF NOT EXISTS。這樣做不檢查已有表的模式是否與你打算建立的表模式相匹配。它只是查看錶名是否存在,並且僅在表名不存在時建立它。

使用NULL值

NULL值就是沒有值或缺值。允許NULL值的列也允許在插入行時不給出該列的值。不允許NULL值的列不接受該列沒有值的行,換句話說,在插入或更新行時,該列必須有值。

注意:不要把NULL值與空串相混淆。NULL值是沒有值,它不是空串。如果指定''(兩個單引號,其間沒有字元),這在NOT NULL列中是允許的。空串是一個有效的值,它不是無值。NULL值用關鍵字NULL而不是空串指定。

主鍵再介紹

主鍵值必須唯一,即表中的每個行必須具有唯一的主鍵值。如果主鍵使用單個列,則它的值必須唯一。如果使用多個列,則這些列的組合值必須唯一。

主鍵可以在建立表時定義,或者在建立表之後定義。主鍵為其值唯一標識表中每個行的列。主鍵中只能使用不允許NULL值的列。允許NULL值的列不能作為唯一標識。

使用AUTO_INCREMENT

AUTO_INCREMENT告訴MySQL,本列每當增加一行時自動增量。每次執行一個INSERT操作時,MySQL自動對該列增量(從而才有這個關鍵字AUTO_INCREMENT),給該列賦予下一個可用的值。

每個表只允許一個AUTO_INCREMENT列,而且它必須被索引(如,通過使它成為主鍵)。

在使用AUTO_INCREMENT列時獲得這個值呢?可使用last_insert_id()函式獲得這個值。


select last_insert_id() from customers;

指定預設值

如果在插入行時沒有給出值,MySQL允許指定此時使用的預設值。預設值用CREATE TABLE語句的列定義中的DEFAULT關鍵字指定。

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,
    primary key (order_num, order_item)
) ENGINE=InnoDB;

注意:與大多數DBMS不一樣,MySQL不允許使用函式作為預設值,它只支援常量。

一個建議是,使用預設值而不是NULL列,特別是對用於計算或資料分組的列更是如此。

引擎型別

幾個需要知道的引擎:

  • InnoDB是一個可靠的事務處理引擎,它不支援全文字搜尋;
  • Memory在功能等同於MyISAM,但由於資料儲存在記憶體(不是磁碟)中,速度很快(特別適合於臨時表);
  • MyISAM是一個性能極高的引擎,它支援全文字搜尋,但不支援事務處理。

值得一提的是,混用引擎型別有一個大缺陷。外來鍵(用於強制實施引用完整性)不能跨引擎,即使用一個引擎的表不能引用具有使用不同引擎的表的外來鍵。

更新表

為更新表定義,可使用ALTER TABLE語句。但是,理想狀態下,當表中儲存資料以後,該表就不應該再被更新。在表的設計過程中需要花費大量時間來考慮,以便後期不對該表進行大的改動。

為了使用ALTER TABLE更改表結構,必須給出下面的資訊:

  • 在ALTER TABLE之後給出要更改的表名(該表必須存在,否則將出錯);
  • 所做更改的列表。
# 句給vendors表增加一個名為vend_phone的列,必須明確其資料型別。
alter table vendors
add vend_phone char(20);

# 刪除剛剛新增的列
alter table vendors
drop column vend_phone;

ALTER TABLE的一種常見用途是定義外來鍵。

alter table orderitems
add constraint fk_orderitems_orders
foreign key (order_num) references orders (order_num);

複雜的表結構更改一般需要手動刪除過程,它涉及以下步驟:

  • 用新的列布局建立一個新表;
  • 使用INSERT SELECT語句從舊錶複製資料到新表。如果有必要,可使用轉換函式和計算欄位;
  • 檢驗包含所需資料的新表;
  • 重新命名舊錶(如果確定,可以刪除它);
  • 用舊錶原來的名字重新命名新表;
  • 根據需要,重新建立觸發器、儲存過程、索引和外來鍵。

注意:使用ALTER TABLE要極為小心,應該在進行改動前做一個完整的備份(模式和資料的備份)。資料庫表的更改不能撤銷,如果增加了不需要的列,可能不能刪除它們。類似地,如果刪除了不應該刪除的列,可能會丟失該列中的所有資料。

刪除表

刪除表(刪除整個表而不是其內容)非常簡單,使用DROP TABLE語句。

drop table customers_p_p;

重命名錶

使用RENAME TABLE語句可以重新命名一個表。

rename table customers_p to customers_rename;

使用檢視

試圖

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

為什麼使用檢視

檢視的常見應用:

  • 重用SQL語句。
  • 簡化複雜的SQL操作。在編寫查詢後,可以方便地重用它而不必知道它的基本查詢細節。
  • 使用表的組成部分而不是整個表。
  • 保護資料。可以給使用者授予表的特定部分的訪問許可權而不是整個表的訪問許可權。
  • 更改資料格式和表示。檢視可返回與底層表的表示和格式不同的資料。

在檢視建立之後,可以用與表基本相同的方式利用它們。可以對檢視執行SELECT操作,過濾和排序資料,將檢視聯結到其他檢視或表,甚至能新增和更新資料(不過新增和更新資料存在某些限制)。

重要的是知道檢視僅僅是用來檢視儲存在別處的資料的一種設施。檢視本身不包含資料

正是因為檢視不包含資料,所以每次使用檢視時,都必須處理查詢執行時所需的任一個檢索。如果你用多個聯結和過濾建立了複雜的檢視或者嵌套了檢視,可能會發現效能下降得很厲害。因此,在部署使用了大量檢視的應用前,應該進行測試。

檢視的規則和限制

關於檢視建立和使用的一些最常見的規則和限制:

  • 與表一樣,檢視必須唯一命名(不能給檢視取與別的檢視或表相同的名字)。
  • 對於可以建立的檢視數目沒有限制。
  • 為了建立檢視,必須具有足夠的訪問許可權。這些限制通常由資料庫管理人員授予。
  • 檢視可以巢狀,即可以利用從其他檢視中檢索資料的查詢來構造一個檢視。
  • ORDER BY可以用在檢視中,但如果從該檢視檢索資料SELECT中也含有ORDER BY,那麼該檢視中的ORDER BY將被覆蓋。
  • 檢視不能索引,也不能有關聯的觸發器或預設值。
  • 檢視可以和表一起使用。例如,編寫一條聯結表和檢視的SELECT語句。

建立檢視

檢視的建立:

  • 檢視用CREATE VIEW語句來建立。
  • 使用SHOW CREATE VIEW viewname; 來檢視建立檢視的語句。
  • 用DROP刪除檢視,其語法為DROP VIEW viewname;
  • 更新檢視時,可以先用DROP再用CREATE,也可以直接用CREATE OR REPLACE VIEW。如果要更新的檢視不存在,則第2條更新語句會建立一個檢視;如果要更新的檢視存在,則第2條更新語句會替換原有檢視。

利用試圖簡化複雜的聯結

檢視的最常見的應用之一是隱藏(簡化)複雜的SQL,這通常都會涉及聯結。

# 建立一個名為productcustomers的檢視,
# 它聯結三個表,以返回已訂購了任意產品的所有客戶的列表。
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;
# 檢索訂購了產品TNT2的客戶
select cust_name, cust_contact
from productcustomers
where prod_id = 'TNT2';

# 輸出
+----------------+--------------+
| cust_name      | cust_contact |
+----------------+--------------+
| Coyote Inc.    | Y Lee        |
| Yosemite Place | Y Sam        |
+----------------+--------------+

建立不受特定資料限制的檢視是一種好辦法。例如,上面建立的檢視返回生產所有產品的客戶而不僅僅是生產TNT2的客戶。擴充套件檢視的範圍不僅使得它能被重用,而且甚至更有用。這樣做不需要建立和維護多個類似檢視。

用檢視重新格式化檢索出的資料

檢視的另一常見用途是重新格式化檢索出的資料。

# 建立一個名為vendorlocations的檢視,
# 在單個組合計算列中返回供應商名和位置
create view vendorlocations as
select concat(rtrim(vend_name), '(', rtrim(vend_country), ')') as vend_title
from vendors
order by vend_name;
# 檢索出以建立所有供應商名和位置的資料
select *
from vendorlocations;

# 輸出
+------------------------+
| vend_title             |
+------------------------+
| ACME(USA)              |
| Anvils R Us(USA)       |
| Furball Inc.(USA)      |
| Jet Set(England)       |
| Jouets Et Ours(France) |
| LT Supplies(USA)       |
+------------------------+

用檢視過濾不想要的資料

# 過濾沒有電子郵件地址的客戶
create view customereamillist as
select cust_id, cust_name, cust_email
from customers
where cust_email is not null;

select *
from customereamillist;

# 輸出
+---------+----------------+---------------------+
| cust_id | cust_name      | cust_email          |
+---------+----------------+---------------------+
|   10001 | Coyote Inc.    | [email protected]     |
|   10003 | Wascals        | [email protected] |
|   10004 | Yosemite Place | [email protected]    |
+---------+----------------+---------------------+

如果從檢視檢索資料時使用了一條WHERE子句,則兩組子句(一組在檢視中,另一組是傳遞給檢視的)將自動組合。

使用檢視與計算欄位

# 檢索某個特定訂單中的物品,計算每種物品的總價格
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 = 20005;

# 輸出
+-----------+---------+----------+------------+----------------+
| order_num | prod_id | quantity | item_price | expanded_price |
+-----------+---------+----------+------------+----------------+
|     20005 | ANV01   |       10 |       5.99 |          59.90 |
|     20005 | ANV02   |        3 |       9.99 |          29.97 |
|     20005 | TNT2    |        5 |      10.00 |          50.00 |
|     20005 | FB      |        1 |      10.00 |          10.00 |
+-----------+---------+----------+------------+----------------+

正確使用檢視可極大地簡化複雜的資料處理。

更新檢視

通常,檢視是可更新的(即,可以對它們使用INSERT、UPDATE和DELETE)。更新一個檢視將更新其基表(因為檢視本身沒有資料)。如果你對檢視增加或刪除行,實際上是對其基表增加或刪除行。

但是,並非所有檢視都是可更新的。基本上可以說,如果MySQL不能正確地確定被更新的基資料,則不允許更新(包括插入和刪除)。這實際上意味著,如果檢視定義中有以下操作,則不能進行檢視的更新:

  • 分組(使用GROUP BY和HAVING);
  • 聯結;
  • 子查詢;
  • 並;
  • 聚集函式(Min()、Count()、Sum()等);
  • DISTINCT;
  • 匯出(計算)列。

所以,一般來說,應該將檢視用於檢索(SELECT語句)而不用於更新(INSERT、UPDATE和DELETE)。

使用儲存過程

儲存過程

儲存過程簡單來說,就是為以後的使用而儲存的一條或多條MySQL語句的集合。可將其視為批檔案,雖然它們的作用不僅限於批處理。

為什麼要使用儲存過程

一些主要的理由:

  • 簡化操作。通過把處理封裝在容易使用的單元中,可以簡化複雜的操作。
  • 保證資料的完整性。由於不要求反覆建立一系列處理步驟,這保證了資料的完整性。如果所有開發人員和應用程式都使用同一(試驗和測試)儲存過程,則所使用的程式碼都是相同的。
  • 簡化對變動的管理。如果表名、列名或業務邏輯(或別的內容)有變化,只需要更改儲存過程的程式碼。使用它的人員甚至不需要知道這些變化。
  • 提高效能。因為使用儲存過程比使用單獨的SQL語句要快。
  • 編寫功能更強更靈活的程式碼。存在一些只能用在單個請求中的MySQL元素和特性,儲存過程可以使用它們來編寫功能更強更靈活的程式碼。

總的來說,使用儲存過程有三個好處,即即簡單、安全、高效能。

儲存過程的一些缺點:

  • 一般來說,儲存過程的編寫比基本SQL語句複雜,編寫儲存過程需要更高的技能,更豐富的經驗。
  • 可能沒有建立儲存過程的安全訪問許可權。許多資料庫管理員限制儲存過程的建立許可權,允許使用者使用儲存過程,但不允許他們建立儲存過程。

使用儲存過程

執行儲存過程

MySQL稱儲存過程的執行為呼叫,因此MySQL執行儲存過程的語句為CALL。CALL接受儲存過程的名字以及需要傳遞給它的任意引數。

# 執行名為productpricing的儲存過程,
# 它計算並返回產品的最低、最高和平均價格。
call productpricing(@pricelow,
                    @pricehigh,
                    @priceavg);

建立儲存過程

# 一個返回產品平均價格的儲存過程。
create procedure productpricing()
begin
    select avg(prod_price) as priceavg
    from products;
end;

值得注意的是,如果使用的是mysql命令列實用程式,預設的MySQL語句分隔符為;。mysql命令列實用程式也使用;作為語句分隔符。如果命令列實用程式要解釋儲存過程自身內的;字元,則它們最終不會成為儲存過程的成分,這會使儲存過程中的SQL出現句法錯誤。

解決辦法是臨時更改命令列實用程式的語句分隔符。

DELIMITER //

create procedure productpricing()
begin
    select avg(prod_price) as priceavg
    from products;
end //

DELIMITER;

# 使用儲存過程
call productpricing();

# 輸出
+-----------+
| priceavg  |
+-----------+
| 16.133571 |
+-----------+

刪除儲存過程

drop procedure if exists productpricing;

使用引數

一般,儲存過程並不顯示結果,而是把結果返回給你指定的變數。

變數(variable):記憶體中一個特定的位置,用來臨時儲存資料。

DELIMITER //

create procedure productpricing(
    out p1 decimal(8,2),
    out p2 decimal(8,2),
    out p3 decimal(8,2)
)
begin
    select min(prod_price)
    into p1
    from products;
    select max(prod_price)
    into p2
    from products;
    select avg(prod_price)
    into p3
    from products;
end //

DELIMITER;

關鍵字OUT指出相應的引數用來從儲存過程傳出一個值(返回給呼叫者)。

MySQL支援IN(傳遞給儲存過程)、OUT(從儲存過程傳出,如這裡所用)和INOUT(對儲存過程傳入和傳出)型別的引數。

# 呼叫並顯示檢索
call productpricing(@pricelow, @pricehigh, @priceavg);
select @pricelow, @pricehigh, @priceavg; //

# 輸出
+-----------+------------+-----------+
| @pricelow | @pricehigh | @priceavg |
+-----------+------------+-----------+
|      2.50 |      55.00 |     16.13 |
+-----------+------------+-----------+
# ordertotal接受訂單號並返回該訂單的合計

delimiter //

create procedure ordertotal(
    in onumber int,
    out ototal decimal(8,3)
)
begin
    select sum(item_price*quantity)
    from orderitems
    where order_num = onumber
    into ototal;
end //

# 呼叫
call ordertotal(20005, @total);
select @total;  //

# 輸出
+---------+
| @total  |
+---------+
| 149.870 |
+---------+

# 呼叫
call ordertotal(20009, @total);
select @total;  //

# 輸出
+--------+
| @total |
+--------+
| 38.470 |
+--------+

建立智慧儲存過程

考慮這個場景。你需要獲得與以前一樣的訂單合計,但需要對合計增加營業稅,不過只針對某些顧客(或許是你所在市中那些顧客)。那麼,你需要做下面幾件事情:

  • 獲得合計;
  • 把營業稅有條件地新增到合計;
  • 返回合計(帶稅或者不帶稅)。
delimiter //
create procedure ordertotalA(
    in onumber int,
    in taxable boolean,
    out ototal decimal(8,2)
) comment 'obatain order total, optionally adding tax.'
begin
    # 定義區域性變數
    declare total decimal(8,2);
    #
    declare taxrate int default 6;

    select sum(item_price*quantity)
    from orderitems
    where order_num = onumber
    into total;

    # 是否加稅
    if taxable then
        # 是
        select total+(total/100*taxrate)
        into total;
    end if;

    select total
    into ototal;
end //

# 呼叫
call ordertotalA(20005, 0, @ototal);
select @ototal; //

# 輸出
+---------+
| @ototal |
+---------+
|  149.87 |
+---------+

# 呼叫
call ordertotalA(20005, 1, @total);
select @total; //

# 輸出
+--------+
| @total |
+--------+
| 158.86 |
+--------+

檢查儲存過程

為顯示用來建立一個儲存過程的CREATE語句,使用SHOW CREATE PROCEDURE語句:

show create procedure ordertotalA; //

SHOW PROCEDURE STATUS列出所有儲存過程。為限制其輸出,可使用LIKE指定一個過濾模式。

show procedure status like 'ordertotalA'; //

使用遊標

有時,需要在檢索出來的行中前進或後退一行或多行。這就是使用遊標的原因。

遊標(cursor)是一個儲存在MySQL伺服器上的資料庫查詢,它不是一條SELECT語句,而是被該語句檢索出來的結果集。在儲存了遊標之後,應用程式可以根據需要滾動或瀏覽其中的資料。

遊標主要用於互動式應用,其中使用者需要滾動螢幕上的資料,並對資料進行瀏覽或做出更改。

值得注意的是,不像多數DBMS,MySQL遊標只能用於儲存過程(和函式)

使用遊標

使用遊標涉及幾個明確的步驟:

  • 在能夠使用遊標前,必須宣告(定義)它。這個過程實際上沒有檢索資料,它只是定義要使用的SELECT語句。
  • 一旦聲明後,必須開啟遊標以供使用。這個過程用前面定義的SELECT語句把資料實際檢索出來。
  • 對於填有資料的遊標,根據需要取出(檢索)各行。
  • 在結束遊標使用時,必須關閉遊標。

建立遊標

遊標用DECLARE語句建立。DECLARE命名遊標,並定義相應的SELECT語句,根據需要帶WHERE和其他子句。

# 定義名為ordernumbers的遊標,使用可以檢索所有訂單的SELECT語句
delimiter //
create procedure processorders()
begin
    declare ordernumbers cursor
    for
    select order_num from orders;

    # 開啟
    open ordernumbers;

    # 關閉
    close ordernumbers;
end //

儲存過程處理完成後,遊標就消失(因為它侷限於儲存過程)。

開啟和關閉遊標

# 開啟
open ordernumbers;

# 關閉
close ordernumbers;

使用遊標資料

在一個遊標被開啟後,可以使用FETCH語句分別訪問它的每一行。FETCH指定檢索什麼資料(所需的列),檢索出來的資料儲存在什麼地方。它還向前移動遊標中的內部行指標,使下一條FETCH語句檢索下一行(不重複讀取同一行)。

# 從遊標中檢索單個行(第一行)
create procedure processordersB()
begin
    declare o int;

    declare ordernumbers cursor
    for
    select order_num from orders;

    open ordernumbers;
    fetch ordernumbers into o;
    close ordernumbers;
end;

# 呼叫
call processordersB();

# 迴圈檢索資料,從第一行到最後一行
create procedure processordersC()
begin
    declare done boolean default 0;
    declare o int;

    declare ordernumbers cursor
    for
    select order_num from orders;

    declare continue handler for sqlstate '02000' set done=1;

    open ordernumbers;

    repeat
        fetch ordernumbers into o;
    until done end repeat;

    close ordernumbers;
end;

# 呼叫
call processordersC();
# 建立和填充另一個表
create procedure processordersD()
begin
    declare done boolean default 0;
    declare o int;
    declare t decimal(8, 2);

    declare ordernumbers cursor
    for
    select order_num from orders;

    declare continue handler for sqlstate '02000' set done=1;

    create table if not exists ordertotals(
        order_num int,
        total decimal(8, 2)
    );

    open ordernumbers;

    repeat

        fetch ordernumbers into o;
        call ordertotal(o, t);

        insert into ordertotals(order_num, total)
        values(o, t);
    until done end repeat;

    close ordernumbers;
end;

# 呼叫
call processordersD();
select *
from ordertotals;

使用觸發器

觸發器 是MySQL響應以下任意語句而自動執行的一條MySQL語句(或位於BEGIN和END語句之間的一組語句)

  • DELETE
  • INSERT
  • UPDATE

其他MySQL語句不支援觸發器。

建立觸發器

在建立觸發器時,需要給出4條資訊:

  • 唯一的觸發器名;
  • 觸發器關聯的表;
  • 觸發器應該響應的活動(DELETE、INSERT或UPDATE);
  • 觸發器何時執行(處理之前或之後)。

值得一提的是,在MySQL 5中,觸發器名必須在每個表中唯一,但不是在每個資料庫中唯一。這表示同一資料庫中的兩個表可具有相同名字的觸發器。這在其他每個資料庫觸發器名必須唯一的DBMS中是不允許的,而且以後的MySQL版本很可能會使命名規則更為嚴格。因此,現在最好是在資料庫範圍內使用唯一的觸發器名。

觸發器用CREATE TRIGGER語句建立。

# 使用INSERT語句新增一行或多行到products中,
# 將看到對每個成功的插入,顯示Product added訊息。
create trigger newproduct after insert on products
for each row select 'Product added';

# 使用
insert into products(
    prod_id,
    vend_id,
    prod_name,
    prod_price,
    prod_desc
) value(
    'TNT3',
    1003,
    'TNT (5 sticks)',
    10.00,
    'TNT, red, pack of 10 sticks'
);

# 輸出

只有表才支援觸發器,檢視不支援(臨時表也不支援)。

觸發器按每個表每個事件每次地定義,每個表每個事件每次只允許一個觸發器。因此,每個表最多支援6個觸發器(每條INSERT、UPDATE和DELETE的之前和之後)。

刪除觸發器

drop trigger newproduct;

使用觸發器

INSERT觸發器

INSERT觸發器在INSERT語句執行之前或之後執行。需要知道以下幾點:

  • 在INSERT觸發器程式碼內,可引用一個名為NEW的虛擬表,訪問被插入的行;
  • 在BEFORE INSERT觸發器中,NEW中的值也可以被更新(允許更改被插入的值);
  • 對於AUTO_INCREMENT列,NEW在INSERT執行之前包含0,在INSERT執行之後包含新的自動生成值。
create trigger neworder after insert on orders
for each row select NEW.order_num;

# 使用
insert into orders(order_date, cust_id)
value(Now(), 10001);

DELETE觸發器

DELETE觸發器在DELETE語句執行之前或之後執行。需要知道以下兩點:

  • 在DELETE觸發器程式碼內,你可以引用一個名為OLD的虛擬表,訪問被刪除的行;
  • OLD中的值全都是隻讀的,不能更新。
# 使用OLD儲存將要被刪除的行到一個存檔表中
create table archive_orders
(
  order_num  int      NOT NULL AUTO_INCREMENT,
  order_date datetime NOT NULL ,
  cust_id    int      NOT NULL ,
  PRIMARY KEY (order_num)
) ENGINE=InnoDB;

create trigger deleteorder before delete on orders
for each row
insert into archive_orders(order_num, order_date, cust_id)
value(OLD.order_num, OLD.order_date, OLD.cust_id);

delete from orders where order_num = 20010;
select * from archive_orders;
# 輸出
+-----------+---------------------+---------+
| order_num | order_date          | cust_id |
+-----------+---------------------+---------+
|     20010 | 2020-09-01 15:00:16 |   10001 |
+-----------+---------------------+---------+

使用BEFORE DELETE觸發器的優點(相對於AFTER DELETE觸發器來說)為,如果由於某種原因,訂單不能存檔,DELETE本身將被放棄。

UPDATE觸發器

UPDATE觸發器在UPDATE語句執行之前或之後執行。需要知道以下幾點:

  • 在UPDATE觸發器程式碼中,你可以引用一個名為OLD的虛擬表訪問以前(UPDATE語句前)的值,引用一個名為NEW的虛擬表訪問新更新的值;
  • 在BEFORE UPDATE觸發器中,NEW中的值可能也被更新(允許更改將要用於UPDATE語句中的值);
  • OLD中的值全都是隻讀的,不能更新。
# 保證國家縮寫總是大寫
create trigger updatevendor before update on vendors
for each row set NEW.vend_state = Upper(NEW.vend_state);

關於觸發器的進一步介紹

一些使用觸發器時需要記住的重點:

  • 與其他DBMS相比,MySQL 5中支援的觸發器相當初級。未來的MySQL版本中有一些改進和增強觸發器支援的計劃。
  • 建立觸發器可能需要特殊的安全訪問許可權,但是,觸發器的執行是自動的。如果INSERT、UPDATE或DELETE語句能夠執行,則相關的觸發器也能執行。
  • 應該用觸發器來保證資料的一致性(大小寫、格式等)。在觸發器中執行這種型別的處理的優點是它總是進行這種處理,而且是透明地進行,與客戶機應用無關。
  • 觸發器的一種非常有意義的使用是建立審計跟蹤。使用觸發器,把更改(如果需要,甚至還有之前和之後的狀態)記錄到另一個表非常容易。
  • 遺憾的是,MySQL觸發器中不支援CALL語句。這表示不能從觸發器內呼叫儲存過程。所需的儲存過程程式碼需要複製到觸發器內。

管理事務處理

事務處理

MySQL支援幾種基本的資料庫引擎。但是並非所有引擎都支援明確的事務處理管理。MyISAM和InnoDB是兩種最常使用的引擎。前者不支援明確的事務處理管理,而後者支援。

事務處理(transaction processing)可以用來維護資料庫的完整性,它保證成批的MySQL操作要麼完全執行,要麼完全不執行。

事務處理是一種機制,用來管理必須成批執行的MySQL操作,以保證資料庫不包含不完
整的操作結果。利用事務處理,可以保證一組操作不會中途停止,它們或者作為整體執行,或者完全不執行(除非明確指示)。如果沒有錯誤發生,整組語句提交給(寫到)資料庫表。如果發生錯誤,則進行回退(撤銷)以恢復資料庫到某個已知且安全的狀態。

事務處理需要知道的幾個術語:

  • 事務(transaction) 指一組SQL語句;
  • 回退(rollback) 指撤銷指定SQL語句的過程;
  • 提交(commit) 指將未儲存的SQL語句結果寫入資料庫表;
  • 保留點(savepoint) 指事務處理中設定的臨時佔位符(placeholder),你可以對它釋出回退(與回退整個事務處理不同)。

控制事務處理

管理事務處理的關鍵在於將SQL語句組分解為邏輯塊,並明確規定資料何時應該回退,何時不應該回退。

# 來標識事務的開始
start transaction

使用ROLLBACK

MySQL的ROLLBACK命令用來回退(撤銷)MySQL語句。

select * from ordertotals;
start transaction;
delete from ordertotals;
select * from ordertotals;
rollback;
select * from ordertotals;

顯然,ROLLBACK只能在一個事務處理內使用(在執行一條STARTTRANSACTION命令之後)。

哪些語句可以回退?

事務處理用來管理INSERT、UPDATE和DELETE語句。你不能回退SELECT語句(這樣做也沒有什麼意義)。你不能回退CREATE或DROP操作。事務處理塊中可以使用這兩條語句,但如果你執行回退,它們不會被撤銷。

使用COMMIT

一般的MySQL語句都是直接針對資料庫表執行和編寫的。這就是所謂的隱含提交(implicit commit),即提交(寫或儲存)操作是自動進行的。

但是,在事務處理塊中,提交不會隱含地進行。為進行明確的提交,使用COMMIT語句。

start transaction;
delete from orderitems where order_num = 20010;
delete from orders where order_num = 20010;
commit;

如果第一條DELETE起作用,但第二條失敗,則DELETE不會提交(實際上,它是被自動撤銷的)。

值得一提的是,當COMMIT或ROLLBACK語句執行後,事務會自動關閉(將來的更改會隱含提交)。

使用保留點

簡單的ROLLBACK和COMMIT語句就可以寫入或撤銷整個事務處理。但是,只是對簡單的事務處理才能這樣做,更復雜的事務處理可能需要部分提交或回退。

為了支援回退部分事務處理,必須能在事務處理塊中合適的位置放置佔位符。這樣,如果需要回退,可以回退到某個佔位符。

這些佔位符稱為保留點。為了建立佔位符,可如下使用SAVEPOINT語句。

# 每個保留點都取標識它的唯一名字,以便在回退時,MySQL知道要回退到何處
savepoint delete1;

# 回退到本例給出的保留點
rollback to delete1;

可以在MySQL程式碼中設定任意多的保留點,越多越好。為什麼呢?因為保留點越多,你就越能按自己的意願靈活地進行回退。

保留點在事務處理完成(執行一條ROLLBACK或COMMIT)後自動釋放。自MySQL 5以來,也可以用RELEASE SAVEPOINT明確地釋放保留點。

更改預設的提交行為

預設的MySQL行為是自動提交所有更改。換句話說,任何時候你執行一條MySQL語句,該語句實際上都是針對表執行的,而且所做的更改立即生效。

# 指示MySQL不自動提交更改
set autocommit = 0;

autocommit標誌是針對每個連線而不是伺服器的。

全球化和本地化

字符集和校對順序

幾個重要術語:

  • 字符集為字母和符號的集合;
  • 編碼為某個字符集成員的內部表示;
  • 校對為規定字元如何比較的指令。

在MySQL的正常資料庫活動(SELECT、INSERT等)中,不需要操心太多的東西。使用何種字符集和校對的決定在伺服器、資料庫和表級進行。

使用字符集和校對順序

MySQL支援眾多的字符集。

# 檢視所支援的字符集完整列表
show character set;

# 輸出
+----------+-----------------------------+---------------------+--------+
| Charset  | Description                 | Default collation   | Maxlen |
+----------+-----------------------------+---------------------+--------+
| big5     | Big5 Traditional Chinese    | big5_chinese_ci     |      2 |
| dec8     | DEC West European           | dec8_swedish_ci     |      1 |
| cp850    | DOS West European           | cp850_general_ci    |      1 |
| hp8      | HP West European            | hp8_english_ci      |      1 |
| koi8r    | KOI8-R Relcom Russian       | koi8r_general_ci    |      1 |
| latin1   | cp1252 West European        | latin1_swedish_ci   |      1 |
| latin2   | ISO 8859-2 Central European | latin2_general_ci   |      1 |
| swe7     | 7bit Swedish                | swe7_swedish_ci     |      1 |
| ascii    | US ASCII                    | ascii_general_ci    |      1 |
| ujis     | EUC-JP Japanese             | ujis_japanese_ci    |      3 |
| sjis     | Shift-JIS Japanese          | sjis_japanese_ci    |      2 |
| hebrew   | ISO 8859-8 Hebrew           | hebrew_general_ci   |      1 |
| tis620   | TIS620 Thai                 | tis620_thai_ci      |      1 |
| euckr    | EUC-KR Korean               | euckr_korean_ci     |      2 |
| koi8u    | KOI8-U Ukrainian            | koi8u_general_ci    |      1 |
| gb2312   | GB2312 Simplified Chinese   | gb2312_chinese_ci   |      2 |
| greek    | ISO 8859-7 Greek            | greek_general_ci    |      1 |
| cp1250   | Windows Central European    | cp1250_general_ci   |      1 |
| gbk      | GBK Simplified Chinese      | gbk_chinese_ci      |      2 |
| latin5   | ISO 8859-9 Turkish          | latin5_turkish_ci   |      1 |
| armscii8 | ARMSCII-8 Armenian          | armscii8_general_ci |      1 |
| utf8     | UTF-8 Unicode               | utf8_general_ci     |      3 |
| ucs2     | UCS-2 Unicode               | ucs2_general_ci     |      2 |
| cp866    | DOS Russian                 | cp866_general_ci    |      1 |
| keybcs2  | DOS Kamenicky Czech-Slovak  | keybcs2_general_ci  |      1 |
| macce    | Mac Central European        | macce_general_ci    |      1 |
| macroman | Mac West European           | macroman_general_ci |      1 |
| cp852    | DOS Central European        | cp852_general_ci    |      1 |
| latin7   | ISO 8859-13 Baltic          | latin7_general_ci   |      1 |
| utf8mb4  | UTF-8 Unicode               | utf8mb4_general_ci  |      4 |
| cp1251   | Windows Cyrillic            | cp1251_general_ci   |      1 |
| utf16    | UTF-16 Unicode              | utf16_general_ci    |      4 |
| utf16le  | UTF-16LE Unicode            | utf16le_general_ci  |      4 |
| cp1256   | Windows Arabic              | cp1256_general_ci   |      1 |
| cp1257   | Windows Baltic              | cp1257_general_ci   |      1 |
| utf32    | UTF-32 Unicode              | utf32_general_ci    |      4 |
| binary   | Binary pseudo charset       | binary              |      1 |
| geostd8  | GEOSTD8 Georgian            | geostd8_general_ci  |      1 |
| cp932    | SJIS for Windows Japanese   | cp932_japanese_ci   |      2 |
| eucjpms  | UJIS for Windows Japanese   | eucjpms_japanese_ci |      3 |
+----------+-----------------------------+---------------------+--------+
# 檢視所支援校對的完整列表
show collation;

# 輸出

為了給表指定字符集和校對,可使用帶子句的CREATE TABLE。

create table mytable
(
    column1 int,
    column2 varchar(10)
) default character set hebrew collate hebrew_gengeral_ci;

除了能指定字符集和校對的表範圍外,MySQL還允許對每個列設定它們。

create table mytable
(
    column1 int,
    column2 varchar(10),
    column3 varchar(10) character set latin1 collate latin1_general_ci
) default character set hebrew collate hebrew_gengeral_ci;

值得注意的是,如果絕對需要,串可以在字符集之間進行轉換。為此,使用Cast()Convert()函式。

安全管理

訪問控制

MySQL伺服器的安全基礎是:使用者應該對他們需要的資料具有適當的訪問權,既不能多也不能少。換句話說,使用者不能對過多的資料具有過多的訪問權。

注意:應該嚴肅對待root登入的使用。僅在絕對需要時使用它(或許在你不能登入其他管理賬號時使用)。不應該在日常的MySQL操作中使用root。

管理使用者

MySQL使用者賬號和資訊儲存在名為mysql的MySQL資料庫中。

# 獲得所有使用者賬號列表
use mysql;
select user from user;

# 輸出
+---------------+
| user          |
+---------------+
| parzulpan     |
| mysql.session |
| mysql.sys     |
| root          |
+---------------+

建立使用者帳號

為了建立一個新使用者賬號,使用CREATE USER語句。

create user parzulpanT identified by 'xxxxxxx';

IDENTIFIED BY指定的口令為純文字,MySQL將在儲存到user表之前對其進行加密。為了作為雜湊值指定口令,使用IDENTIFIED BY PASSWORD。

重新命名一個使用者賬號,使用RENAME USER語句。

rename user parzulpanT to parzulpanRename;

刪除使用者帳號

刪除一個使用者賬號(以及相關的許可權),使用DROP USER語句。

drop user parzulpanRename;

設定訪問許可權

在建立使用者賬號後,必須接著分配訪問許可權。新建立的使用者賬號沒有訪問許可權。它們能登入MySQL,但不能看到資料,不能執行任何資料庫操作。

為看到賦予使用者賬號的許可權,使用SHOW GRANTS FOR。

show grants for parzulpanT;

# 輸出
+-------------------------------------------------------------------------+
| Grants for parzulpanT@%                                                  |
+-------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `parzulpanT`@`%` IDENTIFIED BY PASSWORD '*21AD'    |
+-------------------------------------------------------------------------+

顯示使用者parzulpanT有一個許可權USAGE ON .。USAGE表示根本沒有許可權。

為設定許可權,使用GRANT語句。GRANT要求你至少給出以下資訊:

  • 要授予的許可權;
  • 被授予訪問許可權的資料庫或表;
  • 使用者名稱。
# 允許使用者在learnDB.*(learnDB資料庫的所有表)上使用SELECT。
grant select on learnDB.* to parzulpanT;

# 輸出
+--------------------------------------------------+
| Grants for parzulpanT@%                         |
+--------------------------------------------------+
| GRANT SELECT ON `learnDB`.* TO `parzulpanT`@`%` |
+--------------------------------------------------

GRANT的反操作為REVOKE,用它來撤銷特定的許可權。

revoke select on learnDB.* from parzulpanT;

GRANT和REVOKE可在幾個層次上控制訪問許可權:

  • 整個伺服器,使用GRANT ALL和REVOKE ALL;
  • 整個資料庫,使用ON database.*;
  • 特定的表,使用ON database.table;
  • 特定的列;
  • 特定的儲存過程。

可以授予或撤銷的每個許可權:

許可權 說明
ALL 除GRANT OPTION外的所有許可權
ALTER 使用ALTER TABLE
ALTER ROUTINE 使用ALTER PROCEDURE和DROP PROCEDURE
CREATE 使用CREATE TABLE
CREATE ROUTINE 使用CREATE PROCEDURE
CREATE TEMPORARY TABLES 使用CREATE TEMPORARY TABLE
CREATE USER 使用CREATE USER、DROP USER、RENAME USER和REVOKE ALL PRIVILEGES
CREATE VIEW 使用CREATE VIEW
DELETE 使用DELETE
DROP 使用DROP TABLE
EXECUTE 使用CALL和儲存過程
FILE 使用SELECT INTO OUTFILE和LOAD DATA INFILE
GRANT OPTION 使用GRANT和REVOKE
INDEX 使用CREATE INDEX和DROP INDEX
INSERT 使用INSERT
LOCK TABLES 使用LOCK TABLES
PROCESS 使用SHOW FULL PROCESSLIST
RELOAD 使用FLUSH
REPLICATION CLIENT 伺服器位置的訪問
REPLICATION SLAVE 由複製從屬使用
SELECT 使用SELECT
SHOW DATABASES 使用SHOW DATABASES
SHOW VIEW 使用SHOW CREATE VIEW
SHUTDOWN 使用mysqladmin shutdown(用來關閉MySQL)
SUPER 使用CHANGE MASTER、KILL、LOGS、PURGE、MASTER和SET GLOBAL。還允許mysqladmin除錯登入
UPDATE 使用UPDATE
USAGE 無訪問許可權

更改口令

更改使用者口令,可使用SET PASSWORD語句。

# 新口令必須傳遞到Password()函式進行加密。
set password for parzulpanT = password('xxxxaaaa');

# 設定自己的口令
# 在不指定使用者名稱時,SET PASSWORD更新當前登入使用者的口令。
set password = password('ghdauighaw');

資料庫維護

資料備份

像所有資料一樣,MySQL的資料也必須經常備份。由於MySQL資料庫是基於磁碟的檔案,普通的備份系統和例程就能備份MySQL的資料。但是,由於這些檔案總是處於開啟和使用狀態,普通的檔案副本備份不一定總是有效。

幾種解決方案:

  • 使用命令列實用程式mysqldump轉儲所有資料庫內容到某個外部檔案。在進行常規備份前這個實用程式應該正常執行,以便能正確地備份轉儲檔案。
  • 可用命令列實用程式mysqlhotcopy從一個數據庫複製所有資料(並非所有資料庫引擎都支援這個實用程式)。
  • 可以使用MySQL的BACKUP TABLESELECT INTO OUTFILE轉儲所有資料到某個外部檔案。這兩條語句都接受將要建立的系統檔名,此係統檔案必須不存在,否則會出錯。資料可以用RESTORETABLE來複原。

為了保證所有資料被寫到磁碟(包括索引資料),可能需要在進行備份前使用FLUSH TABLES語句。

進行資料庫備份

應該知道的一些語句:

  • ANALYZE TABLE,用來檢查表鍵是否正確。
analyze table orders;

# 輸出
+----------------+---------+----------+----------+
| Table          | Op      | Msg_type | Msg_text |
+----------------+---------+----------+----------+
| learnDB.orders | analyze | status   | OK       |
+----------------+---------+----------+----------+
  • CHECK TABLE用來針對許多問題對錶進行檢查。在MyISAM表上還對索引進行檢查。CHECK TABLE支援一系列的用於MyISAM表的方式。CHANGED檢查自最後一次檢查以來改動過的表。EXTENDED執行最徹底的檢查,FAST只檢查未正常關閉的表,MEDIUM檢查所有被刪除的連結並進行鍵檢驗,QUICK只進行快速掃描。
check table orders, orderitems;
+--------------------+-------+----------+----------+
| Table              | Op    | Msg_type | Msg_text |
+--------------------+-------+----------+----------+
| learnDB.orders     | check | status   | OK       |
| learnDB.orderitems | check | status   | OK       |
+--------------------+-------+----------+----------+
  • 如果MyISAM表訪問產生不正確和不一致的結果,可能需要用REPAIR TABLE來修復相應的表。這條語句不應該經常使用,如果需要經常使用,可能會有更大的問題要解決。
  • 如果從一個表中刪除大量資料,應該使用OPTIMIZE TABLE來收回所用的空間,從而優化表的效能。

診斷啟動問題

在排除系統啟動問題時,首先應該儘量用手動啟動伺服器。MySQL伺服器自身通過在命令列上執行mysqld啟動。

幾個重要的mysqld命令列選項:

  • --help 顯示幫助——一個選項列表;
  • --safe-mode 裝載減去某些最佳配置的伺服器;
  • --verbose 顯示全文字訊息(為獲得更詳細的幫助訊息與--help聯合使用);
  • --version 顯示版本資訊然後退出。

檢視日誌檔案

MySQL維護管理員依賴的一系列日誌檔案。

主要的日誌檔案有以下幾種:

  • 錯誤日誌。它包含啟動和關閉問題以及任意關鍵錯誤的細節。此日誌通常名為hostname.err,位於data目錄中。此日誌名可用--log-error命令列選項更改。
  • 查詢日誌。它記錄所有MySQL活動,在診斷問題時非常有用。此日誌檔案可能會很快地變得非常大,因此不應該長期使用它。此日誌通常名為hostname.log,位於data目錄中。此名字可以用--log命令列選項更改。
  • 二進位制日誌。它記錄更新過資料(或者可能更新過資料)的所有語句。此日誌通常名為hostname-bin,位於data目錄內。此名字可以用--log-bin命令列選項更改。注意,這個日誌檔案是MySQL5中新增的,以前的MySQL版本中使用的是更新日誌。
  • 緩慢查詢日誌。顧名思義,此日誌記錄執行緩慢的任何查詢。這個日誌在確定資料庫何處需要優化很有用。此日誌通常名為hostname-slow.log ,位於 data 目錄中。此名字可以用--log-slow-queries命令列選項更改。

改善效能

提供進行效能優化探討和分析的一個出發點:

  • 首先,MySQL(與所有DBMS一樣)具有特定的硬體建議。在學習和研究MySQL時,使用任何舊的計算機作為伺服器都可以。但對用於生產的伺服器來說,應該堅持遵循這些硬體建議。
  • 一般來說,關鍵的生產DBMS應該執行在自己的專用伺服器上。
  • MySQL是用一系列的預設設定預先配置的,從這些設定開始通常是很好的。但過一段時間後你可能需要調整記憶體分配、緩衝區大小等。(為檢視當前設定,可使用SHOW VARIABLES;SHOW STATUS;。)
  • MySQL一個多使用者多執行緒的DBMS,換言之,它經常同時執行多個任務。如果這些任務中的某一個執行緩慢,則所有請求都會執行緩慢。如果你遇到顯著的效能不良,可使用SHOW PROCESSLIST顯示所有活動程序(以及它們的執行緒ID和執行時間)。你還可以用
    KILL命令終結某個特定的程序(使用這個命令需要作為管理員登入)。
  • 總是有不止一種方法編寫同一條SELECT語句。應該試驗聯結、並、
    子查詢等,找出最佳的方法。
  • 使用EXPLAIN語句讓MySQL解釋它將如何執行一條SELECT語句。
  • 一般來說,儲存過程執行得比一條一條地執行其中的各條MySQL語句快。
  • 應該總是使用正確的資料型別。
  • 決不要檢索比需求還要多的資料換言之,不要用SELECT *(除非你真正需要每個列)。
  • 有的操作(包括INSERT)支援一個可選的DELAYED關鍵字,如果使用它,將把控制立即返回給呼叫程式,並且一旦有可能就實際執行該操作。
  • 在匯入資料時,應該關閉自動提交。你可能還想刪除索引(包括FULLTEXT索引),然後在匯入完成後再重建它們。
  • 必須索引資料庫表以改善資料檢索的效能。確定索引什麼不是一件微不足道的任務,需要分析使用的SELECT語句以找出重複的WHERE和ORDER BY子句。如果一個簡單的WHERE子句返回結果所花的時間太長,則可以斷定其中使用的列(或幾個列)就是需要索引的物件。
  • 你的SELECT語句中有一系列複雜的OR條件嗎?通過使用多條SELECT語句和連線它們的UNION語句,你能看到極大的效能改進。
  • 索引改善資料檢索的效能,但損害資料插入、刪除和更新的效能。如果你有一些表,它們收集資料且不經常被搜尋,則在有必要之前不要索引它們。(索引可根據需要新增和刪除。)
  • LIKE很慢。一般來說,最好是使用FULLTEXT而不是LIKE。
  • 資料庫是不斷變化的實體。一組優化良好的表一會兒後可能就面目全非了。由於表的使用和內容的更改,理想的優化和配置也會改變。
  • 最重要的規則就是,每條規則在某些條件下都會被打破。