MySQL 基礎學習筆記

MySQL 基礎學習筆記

我的MySQL學習筆記,記錄一些MySQL的基礎知識。
源文檔GiHub地址:MySQL 基礎學習筆記 在CSDN留個備份

SQL 語句與種類

  • DDL(Data Definition Language)數據庫定義語言:如 create drop alter
  • DML(Data Manipulation Language)數據操縱語言:如 select insert update delete
  • DCL(Data Control Language)數據控制語言:如 commit rollback grant revoke

我們使用的 SQL 語句中,有 90% 屬於 DML

MySQL 數據類型

整數類型:

類型 佔用的存儲空間(單位:字節) 無符號數取值範圍 有符號數取值範圍 含義
TINYINT 1 0 ~ 2⁸-1 -2⁷ ~ 2⁷-1 非常小的整數
SMALLINT 2 0 ~ 2¹⁶-1 -2¹⁵ ~ 2¹⁵-1 小的整數
MEDIUMINT 3 0 ~ 2²⁴-1 -2²³ ~ 2²³-1 中等大小的整數
INT(別名:INTEGER 4 0 ~ 2³²-1 -2³¹ ~ 2³¹-1 標準的整數
BIGINT 8 0 ~ 2⁶⁴-1 -2⁶³ ~ 2⁶³-1 大整數

浮點類型:

類型 佔用的存儲空間(單位:字節) 絕對值最小非0值 絕對值最大非0值 含義
FLOAT 4 ±1.175494351E-38 ±3.402823466E+38 單精度浮點數
DOUBLE 8 ±2.2250738585072014E-308 ±1.7976931348623157E+308 雙精度浮點數

定點數類型:

類型 佔用的存儲空間(單位:字節) 取值範圍
DECIMAL(M, D) 取決於M和D 取決於M和D

日期和時間類型:

類型 存儲空間要求 取值範圍 含義
YEAR 1字節 1901~2155 年份值
DATE 3字節 ‘1000-01-01’ ~ ‘9999-12-31’ 日期值
TIME 3字節 ‘-838:59:59’ ~ ‘838:59:59’ 時間值
DATETIME 8字節 ‘1000-01-01 00:00:00’ ~ ‘9999-12-31 23:59:59’ 日期加時間值
TIMESTAMP 4字節 ‘1970-01-01 00:00:01’ ~ ‘2038-01-19 03:14:07’ 時間戳

字符串類型:

類型 最大長度 存儲空間要求 含義
CHAR(M) M個字符 M×W個字節 固定長度的字符串
VARCHAR(M) M個字符 L+1 或 L+2 個字節 可變長度的字符串
TINYTEXT 2⁸-1 個字節 L+1個字節 非常小型的字符串
TEXT 2¹⁶-1 個字節 L+2 個字節 小型的字符串
MEDIUMTEXT 2²⁴-1 個字節 L+3個字節 中等大小的字符串
LONGTEXT 2³²-1 個字節 L+4個字節 大型的字符串

枚舉類型ENUM:它表示在給定的字符串列表裏選擇一個

ENUM('str1', 'str2', 'str3' ⋯)

SET類型,表示在給定的字符串列表裏選擇多個:

SET('str1', 'str2', 'str3' ⋯)

二進制類型:

類型 字節數 含義
BIT(M) 近似爲(M+7)/8 存儲M個比特位的值

數據庫的基本操作

  • 展示數據庫:SHOW DATABASES;
  • 創建數據庫:CREATE DATABASE 數據庫名;,可以在創建前先判斷是否存在 CREATE DATABASE IF NOT EXISTS 數據庫名;
  • 切換數據庫:USE 數據庫名稱;
  • 刪除數據庫:DROP DATABASE 數據庫名;,可以先判斷是否存在:DROP DATABASE IF EXISTS 數據庫名;

表的基本操作

  • 展示數據庫中有哪些表:SHOW TABLES;

  • 創建表:

    CREATE TABLE 表名 (
        列名1    數據類型    [列的屬性],
        列名2    數據類型    [列的屬性],
        ...
        列名n    數據類型    [列的屬性]
    );
    
    CREATE TABLE 表名 (
        各個列的信息 ...
    ) COMMENT '表的註釋信息';
    
    CREATE TABLE IF NOT EXISTS 表名(
        各個列的信息 ...
    );
    
  • 刪除表:DROP TABLE 表1, 表2, ..., 表n;

  • 查看錶結構:

    DESCRIBE 表名;
    DESC 表名;
    EXPLAIN 表名;
    SHOW COLUMNS FROM 表名;
    SHOW FIELDS FROM 表名;
    

查看錶創建語句:

SHOW CREATE TABLE 表名; 
SHOW CREATE TABLE 表名\G; # 顯示效果好點

使用utf8字符集建庫

utf8字符集是殘缺的只有三個字節,utf8mb4 佔4個字節,裏面可以存儲所有的utf8字符。

create database xiaohaizi character set utf8mb4;
create database IF NOT EXISTS xiaohaizi character set utf8mb4; # 不存在才建立

修改表名

ALTER TABLE 舊錶名 RENAME TO 新表名;
RENAME TABLE 舊錶名1 TO 新表名1, 舊錶名2 TO 新表名2, ... 舊錶名n TO 新表名n;

還可以把一個數據庫中的錶轉移到另一個數據庫中:

RENAME TABLE dahaizi.first_table1 TO xiaohaizi.first_table;

增加表中的列

ALTER TABLE 表名 ADD COLUMN 列名 數據類型 [列的屬性];

例子:
ALTER TABLE first_table ADD COLUMN third_column CHAR(4) ;

默認mysql會添加在最後一列,如果想要添加到第一列,可以這樣:

ALTER TABLE 表名 ADD COLUMN 列名 列的類型 [列的屬性] FIRST;

添加在指定列名的後面:

ALTER TABLE 表名 ADD COLUMN 列名 列的類型 [列的屬性] AFTER 指定列名;

刪除列

ALTER TABLE 表名 DROP COLUMN 列名;

修改列的信息:

方式1:

ALTER TABLE 表名 MODIFY 列名 新數據類型 [新屬性];

注意:修改後的數據類型和屬性一定要兼容表中現有的數據

方式2:

這種修改方式需要我們填兩個列名,也就是說在修改數據類型和屬性的同時也可以修改列名

ALTER TABLE 表名 CHANGE 舊列名 新列名 新數據類型 [新屬性];

修改列排列位置

將列設爲表的第一列:

ALTER TABLE 表名 MODIFY 列名 列的類型 列的屬性 FIRST;

將列放到指定列的後邊:

ALTER TABLE 表名 MODIFY 列名 列的類型 列的屬性 AFTER 指定列名;

一條語句中包含多個修改操作

如果對同一個表有多個修改操作的話,我們可以把它們放到一條語句中執行,就像這樣:

ALTER TABLE 表名 操作1, 操作2, ..., 操作n;

列的屬性

簡單查詢語句:

SELECT * FROM 表名;

簡單插入語句:

INSERT INTO 表名(1,2, ...) VALUES(1的值,列2的值, ...);
# 批量插入
INSERT INTO 表名(1,2, ...) VAULES(1的值,列2的值, ...), (1的值,列2的值, ...), (1的值,列2的值, ...), ...;

默認值

在書寫INSERT語句插入記錄的時候可以只指定部分的列,那些沒有被顯式指定的列的值將被設置爲NULL,換一種說法就是列的默認值爲NULLNULL的含義是這個列的值還沒有被設置。如果我們不想讓默認值爲NULL,而是設置成某個有意義的值,可以在定義列的時候給該列增加一個DEFAULT屬性,就像這樣:

列名 列的類型 DEFAULT 默認值

NOT NULL屬性

有時候我們需要要求表中的某些列中必須有值,不能存放NULL,那麼可以用這樣的語法來定義這個列:

列名 列的類型 NOT NULL

主鍵

有時候在我們的表裏可以通過某個列或者某些列確定唯一的一條記錄,我們就可以把這個列或者這些列稱爲候選鍵。比如在學生信息表student_info中,只要我們知道某個學生的學號,就可以確定一個唯一的學生信息,也就是一條記錄。當然,我們也可以通過身份證號來確定唯一的一條學生信息記錄,所以學號身份證號都可以作爲學生信息表的候選鍵。在學生成績表student_score中,我們可以通過學號科目這兩個列的組合來確定唯一的一條成績記錄,所以學號、科目這兩個列的組合可以作爲學生成績表的候選鍵

一個表可能有多個候選鍵,我們可以選擇一個候選鍵作爲表的主鍵。一個表最多只能有一個主鍵,主鍵的值不能重複,通過主鍵可以找到唯一的一條記錄

主鍵可以通過兩種方式進行聲明:

  1. 如果主鍵只是單個列的話,可以直接在該列後聲明PRIMARY KEY

  2. 也可以把主鍵的聲明單獨提取出來,用這樣的形式聲明:

    PRIMARY KEY (列名1, 列名2, ...)
    

    對於多個列的組合作爲主鍵的情況,必須使用這種單獨聲明的形式

主鍵默認具有 NOT NULL 屬性。

UNIQUE屬性

對於不是主鍵的其他候選鍵,如果也想讓MySQL在我們向表中插入新記錄的時候幫助我們校驗一下某個列或者列組合的值是否重複,那麼我們可以把這個列或列組合添加一個UNIQUE屬性,表明該列或者列組合的值是不允許重複的。

聲明方式也是兩種:

  1. 如果我們想爲單個列聲明UNIQUE屬性,可以直接在該列後填寫UNIQUE或者UNIQUE KEY

  2. 也可以把UNIQUE屬性的聲明單獨提取出來

    UNIQUE [約束名稱] (列名1, 列名2, ...)
    或
    UNIQUE KEY [約束名稱] (列名1, 列名2, ...)
    

如果表中爲某個列或者列組合定義了UNIQUE屬性的話,MySQL會對我們插入的記錄做校驗,如果新插入記錄在該列或者列組合的值已經在表中存在了,那就會報錯!

主鍵和UNIQUE約束的區別

  1. 一張表中只能定義一個主鍵,卻可以定義多個UNIQUE約束!
  2. 規定:主鍵列不允許存放NULL,而聲明瞭UNIQUE屬性的列可以存放NULL,而且NULL可以重複地出現在多條記錄中!

外鍵

定義外鍵的語法:

CONSTRAINT [外鍵名稱] FOREIGN KEY(1,2, ...) REFERENCES 父表名(父列1, 父列2, ...);

其中的外鍵名稱也是可選的。如果A表中的某個列或者某些列依賴與B表中的某個列或者某些列,那麼就稱A表爲子表,B表爲父表。子表和父表可以使用外鍵來關聯起來。

父表中被子表依賴的列或者列組合必須建立索引,如果該列或者列組合已經是主鍵或者有UNIQUE屬性,那麼它們也就被默認建立了索引。示例中student_score表依賴於stuent_info表的number列,而number列又是stuent_info的主鍵(注意上一章定義的student_info結構中沒有把number列定義爲主鍵,本章纔將其定義爲主鍵,如果你的機器上還沒有將其定義爲主鍵的話,趕緊修改表結構唄~),所以在student_score表中創建外鍵是沒問題的。

AUTO_INCREMENT屬性

AUTO_INCREMENT翻譯成中文可以理解爲自動增長,簡稱自增。如果一個表中的某個列的數據類型是整數類型或者浮點數類型,那麼這個列可以設置AUTO_INCREMENT屬性。當我們把某個列設置了AUTO_INCREMENT屬性之後,如果我們在插入新記錄的時候不指定該列的值,或者將該列的值顯式地指定爲NULL或者0,那麼新插入的記錄在該列上的值就是當前該列的最大值加1後的值,定義語法爲:

列名 列的類型 AUTO_INCREMENT

id是從1開始遞增的。在爲列定義AUTO_INCREMENT屬性的時候需要注意這幾點:

  1. 一個表中最多有一個具有AUTO_INCREMENT屬性的列。
  2. 具有AUTO_INCREMENT屬性的列必須建立索引。主鍵和具有UNIQUE屬性的列會自動建立索引。不過至於什麼是索引,在學習MySQL進階的時候纔會介紹。
  3. 擁有AUTO_INCREMENT屬性的列就不能再通過指定DEFAULT屬性來指定默認值。
  4. 一般擁有AUTO_INCREMENT屬性的列都是作爲主鍵的屬性,來自動生成唯一標識一條記錄的主鍵值。

列的註釋

上一章中我們說了在建表語句的末尾可以添加COMMENT語句來給表添加註釋,其實我們也可以在每一個列末尾添加COMMENT語句來爲列來添加註釋,比方說:

CREATE TABLE first_table (
    id int UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '自增主鍵',
    first_column INT COMMENT '第一列',
    second_column VARCHAR(100) DEFAULT 'abc' COMMENT '第二列'
) COMMENT '第一個表';

影響展示外觀的ZEROFILL屬性

下邊是正整數3的三種寫法:

  • 寫法一:3
  • 寫法二:003
  • 寫法三:000003

對於無符號整數類型的列,我們可以在查詢數據的時候讓數字左邊補0,如果想實現這個效果需要給該列加一個ZEROFILL屬性(也可以理解爲這是一個屬於數據類型的屬性),就像這樣:

mysql> CREATE TABLE zerofill_table (
    ->     i1 INT UNSIGNED ZEROFILL,
    ->     i2 INT UNSIGNED
    -> );
Query OK, 0 rows affected (0.03 sec)

顯示寬度顯示寬度是在查詢語句顯示的結果中,如果聲明瞭 ZEROFILL 屬性的整數列的實際值的位數小於顯示寬度時,會在實際值的左側補0,使補0的位數和實際值的位數相加正好等於顯示寬度。我們也可以自己指定顯示寬度

  • 在展示查詢結果時,某列數據自動補0的條件有這幾個:

    • 該列必須是整數類型的
    • 該列必須有UNSIGNED ZEROFILL的屬性
    • 該列的實際值的位數必須小於顯示寬度
  • 在創建表的時候,如果聲明瞭ZEROFILL屬性的列沒有聲明UNSIGNED屬性,那MySQL會爲該列自動生成UNSIGNED屬性。

    也就是說如果我們創建表語句是這樣的:

    CREATE TABLE zerofill_table (
        i1 INT ZEROFILL,
        i2 INT UNSIGNED
    );
    

    MySQL會自動幫我們爲i1列加上UNSIGNED屬性,也就是這樣:

    CREATE TABLE zerofill_table (
        i1 INT UNSIGNED ZEROFILL,
        i2 INT UNSIGNED
    );
    

    也就是說MySQL現在只支持對無符號整數進行自動補0的操作。

  • 每個整數類型都會有默認的顯示寬度。

    比如TINYINT的默認顯示寬度是4INT的默認顯示寬度是(11)… 如果加了UNSIGNED屬性,則該類型的顯示寬度減1,比如TINYINT UNSIGNED的顯示寬度是3INT UNSIGNED的顯示寬度是10

  • 顯示寬度並不會影響實際類型的實際存儲空間。

    顯示寬度僅僅是在展示查詢結果時,如果整數的位數不夠顯示寬度的情況下起作用的,並不影響該數據類型要求的存儲空間以及該類型能存儲的數據範圍,也就是說INT(1)INT(10)僅僅在展示時可能有區別,在別的方面沒有任何區別。比方說zerofill_table表中i1列的顯示寬度是5,而數字12345678的位數是8,它照樣可以被填入i1列中:

    mysql> INSERT INTO zerofill_table(i1, i2) VALUES(12345678, 12345678);
    Query OK, 1 row affected (0.01 sec)
    
    mysql>
    
  • 只有列的實際值的位數小於顯示寬度時纔會補0,實際值的位數大於顯示寬度時照原樣輸出。

    比方說我們剛剛把12345678存到了i1列裏,在展示這個值時,並不會截短顯示的數據,而是照原樣輸出:

    mysql> SELECT * FROM zero_table;
    +----------+----------+
    | i1       | i2       |
    +----------+----------+
    |    00001 |        1 |
    | 12345678 | 12345678 |
    +----------+----------+
    2 rows in set (0.00 sec)
    
    mysql>
    
  • 對於沒有聲明ZEROFILL屬性的列,顯示寬度沒有一毛錢卵用。

    只有在查詢聲明瞭ZEROFILL屬性的列時,顯示寬度纔會起作用,否則忽略顯示寬度這個東西的存在。

一個列同時具有多個屬性

每個列可以同時具有多個屬性,屬性聲明的順序無所謂,各個屬性之間用空白隔開就好了~

查看錶結構時的列屬性

mysql> DESC student_info;
+-----------------+-------------------+------+-----+---------+-------+
| Field           | Type              | Null | Key | Default | Extra |
+-----------------+-------------------+------+-----+---------+-------+
| number          | int(11)           | NO   | PRI | NULL    |       |
| name            | varchar(5)        | YES  |     | NULL    |       |
| sex             | enum('男','女')   | YES  |     | NULL    |       |
| id_number       | char(18)          | YES  | UNI | NULL    |       |
| department      | varchar(30)       | YES  |     | NULL    |       |
| major           | varchar(30)       | YES  |     | NULL    |       |
| enrollment_time | date              | YES  |     | NULL    |       |
+-----------------+-------------------+------+-----+---------+-------+
7 rows in set (0.00 sec)

mysql>

可以看到:

  • NULL列代表該列是否可以存儲NULL,值爲NO時,表示不允許存儲NULL,值爲YES是表示可以存儲NULL
  • Key列存儲關於所謂的的信息,當值爲PRIPRIMARY KEY的縮寫,代表主鍵;UNIUNIQUE KEY的縮寫,代表UNIQUE屬性。
  • Default列代表該列的默認值。
  • Extra列展示一些額外的信息。比方說如果某個列具有AUTO_INCREMENT屬性就會被展示在這個列裏。

簡單查詢

查詢單個列

SELECT 列名 FROM 表名;

列的別名

我們也可以爲結果集中的列重新定義一個別名,命令格式如下:

SELECT 列名 [AS] 列的別名 FROM 表名;

查詢多個列

如果想查詢多個列的數據,可以在SELECT後邊寫多個列名,用逗號,分隔開就好:

SELECT 列名1, 列名2, ... 列名n FROM 表名;

我們把SELECT語句後邊跟隨的多個列統稱爲查詢列表,需要注意的是,查詢列表中的列名可以按任意順序擺放,結果集將按照我們指定的列名順序顯示

查詢所有列

SELECT * FROM 表名;

需要注意的是,除非你確實需要表中的每個列,否則一般最好別使用星號*來查詢所有列,雖然星號*看起來很方便,不用明確列出所需的列,但是查詢不需要的列通常會降低性能。

查詢結果去重

去除單列的重複結果

SELECT DISTINCT 列名 FROM 表名;

去除多列的重複結果

SELECT DISTINCT 列名1, 列名2, ... 列名n  FROM 表名;

限制查詢結果條數

有時候查詢結果的條數會很多,都顯示出來可能會撐爆屏幕~ 所以MySQL給我們提供了一種限制結果集中的記錄條數的方式,就是在查詢語句的末尾使用這樣的語法:

LIMIT 開始行, 限制條數;

開始行指的是我們想從第幾行數據開始查詢,限制條數是結果集中最多包含多少條記錄。

LIMIT 後面也可以直接跟數字,代表開始行爲 0

對查詢結果排序

按照單個列的值進行排序

我們可以用下邊的語法來指定返回結果的記錄按照某一列的值進行排序:

ORDER BY 列名 ASC|DESC
  • ASCDESC指的是排序方向。

  • ASC是指按照指定列的值進行由小到大進行排序,也叫做升序

  • DESC是指按照指定列的值進行由大到小進行排序,也叫做降序,中間的|表示這兩種方式只能選一個

按照多個列的值進行排序

ORDER BY1 ASC|DESC,2 ASC|DESC ...
  • 如果不指定排序方向,則默認使用的是ASC,也就是從小到大的升序規則。

帶搜索條件的查詢

使用 where 子句

形如:SELECT number, name, id_number, major FROM student_info WHERE name = '範劍';

MySQL還提供了很多別的比較操作符,比如:

操作符 示例 描述
= a = b a等於b
<>或者!= a <> b a不等於b
< a < b a小於b
<= a <= b a小於或等於b
> a > b a大於b
>= a >= b a大於或等於b
BETWEEN a BETWEEN b AND c 滿足 b <= a <= c
NOT BETWEEN a NOT BETWEEN b AND c 不滿足 b <= a <= c

匹配列表中的元素:

操作符 示例 描述
IN a IN (b1, b2, ...) a是b1, b2, … 中的某一個
NOT IN a NOT IN (b1, b2, ...) a不是b1, b2, … 中的任意一個

匹配NULL

操作符 示例 描述
IS NULL a IS NULL a的值是NULL
IS NOT NULL a IS NOT NULL a的值不是NULL

多個搜索條件

使用 AND 或者 OR 操作符嗎,其中 AND 的優先級比較大

通配符

通常用來進行模糊查詢

操作符 示例 描述
LIKE a LIKE b a匹配b
NOT LIKE a NOT LIKE b a不匹配b

MySQL支持兩種通配符:

  1. %:代表任意一個字符串。
  2. _:代表任意一個字符。

如果待匹配的字符包含 % 或者 _ ,則可以使用 \ 進行轉義

表達式和函數

操作數

MySQL操作數可以是下邊這幾種類型:

  1. 常數

    常數很好理解,我們平時用到的數字、字符串、時間值什麼的都可以被稱爲常數,它是一個確定的值,比如數字1,字符串'abc',時間值2019-08-16 17:10:43啥的。

  2. 列名

    針對某個具體的表,它的列名可以被當作表達式的一部分,比如對於student_info表來說,numbername都可以作爲操作數

  3. 函數調用

    MySQL中有函數的概念,比方說獲取當前時間的函數NOW,而在函數後邊加個小括號就算是一個函數調用,比如NOW()

  4. 標量子查詢或者行子查詢

  5. 其他表達式

    一個表達式也可以作爲一個操作數與另一個操作數來形成一個更復雜的表達式,比方說(假設col是一個列名):

    • (col - 5) / 3
    • (1 + 1) * 2 + col * 3

操作符

  1. 算術操作符

    操作符 示例 描述
    + a + b 加法
    - a - b 減法
    * a * b 乘法
    / a / b 除法
    DIV a DIV b 除法,取商的整數部分
    % a % b 取餘
    - -a 負號

    在使用MySQL中的算術操作符時需要注意,DIV/都表示除法操作符,但是DIV只會取商的整數部分,/會保留商的小數部分。比如表達式 2 DIV 3的結果是0,而2 / 3的結果是0.6667

  2. 比較操作符

    操作符 示例 描述
    = a = b a等於b
    <>或者!= a <> b a不等於b
    < a < b a小於b
    <= a <= b a小於或等於b
    > a > b a大於b
    >= a >= b a大於或等於b
    BETWEEN a BETWEEN b AND c 滿足 b <= a <= c
    NOT BETWEEN a NOT BETWEEN b AND c 不滿足 b <= a <= c
    IN a IN (b1, b2, ...) a是b1, b2, … 中的某一個
    NOT IN a NOT IN (b1, b2, ...) a不是b1, b2, … 中的任意一個
    IS NULL a IS NULL a的值是NULL
    IS NOT NULL a IS NOT NULL a的值不是NULL
    LIKE a LIKE b a匹配b
    NOT LIKE a NOT LIKE b a不匹配b

    比較操作符連接而成的表達式也稱爲布爾表達式,表示或者,也可以稱爲TRUE或者FALSE。比如1 > 3就代表FALSE3 != 2就代表TRUE

  3. 邏輯操作符

    操作符 示例 描述
    AND a AND b 只有a和b同時爲真,表達式才爲真
    OR a OR b 只要a或b有任意一個爲真,表達式就爲真
    XOR a XOR b a和b有且只有一個爲真,表達式爲真

表達式的使用

只要把這些操作數操作符相互組合起來就可以組成一個表達式表達式主要以下邊這兩種方式使用:

  1. 放在查詢列表中

    我們前邊都是將列名放在查詢列表中的(*號代表所有的列名~)。列名只是表達式中超級簡單的一種,我們可以將任意一個表達式作爲查詢列表的一部分來處理,比方說我們可以在查詢student_score表時把score字段的數據都加100,就像這樣:

    SELECT  number, subject, score + 100 FROM student_score;
    

    需要注意的是,放在查詢列表的表達式也可以不涉及列名,就像這樣:

    SELECT 1 FROM student_info;
    
  2. 作爲搜索條件

    搜索條件也可以不帶列名,比如這樣:

    SELECT number, name, id_number, major FROM student_info WHERE 2 > 1;
    

函數

在使用MySQL過程中經常會有一些需求,比方說將給定文本中的小寫字母轉換成大寫字母,把某個日期數據中的月份值提取出來等等。爲了解決這些常遇到的問題,MySQL提供了很多所謂的函數

  • UPPER函數是用來把給定的文本中的小寫字母轉換成大寫字母。
  • MONTH函數是用來把某個日期數據中的月份值提取出來。
  • NOW函數用來獲取當前的日期和時間。

文本處理函數:

名稱 調用示例 示例結果 描述
LEFT LEFT('abc123', 3) abc 給定字符串從左邊取指定長度的子串
RIGHT RIGHT('abc123', 3) 123 給定字符串從右邊取指定長度的子串
LENGTH LENGTH('abc') 3 給定字符串的長度
LOWER LOWER('ABC') abc 給定字符串的小寫格式
UPPER UPPER('abc') ABC 給定字符串的大寫格式
LTRIM LTRIM(' abc') abc 給定字符串左邊空格去除後的格式
RTRIM RTRIM('abc ') abc 給定字符串右邊空格去除後的格式
SUBSTRING SUBSTRING('abc123', 2, 3) bc1 給定字符串從指定位置截取指定長度的子串
CONCAT CONCAT('abc', '123', 'xyz') abc123xyz 將給定的各個字符串拼接成一個新字符串

日期和時間處理函數:

名稱 調用示例 示例結果 描述
NOW NOW() 2019-08-16 17:10:43 返回當前日期和時間
CURDATE CURDATE() 2019-08-16 返回當前日期
CURTIME CURTIME() 17:10:43 返回當前時間
DATE DATE('2019-08-16 17:10:43') 2019-08-16 將給定日期和時間值的日期提取出來
DATE_ADD DATE_ADD('2019-08-16 17:10:43', INTERVAL 2 DAY) 2019-08-18 17:10:43 將給定的日期和時間值添加指定的時間間隔
DATE_SUB DATE_SUB('2019-08-16 17:10:43', INTERVAL 2 DAY) 2019-08-14 17:10:43 將給定的日期和時間值減去指定的時間間隔
DATEDIFF DATEDIFF('2019-08-16', '2019-08-17'); -1 返回兩個日期之間的天數(負數代表前一個參數代表的日期比較小)
DATE_FORMAT DATE_FORMAT(NOW(),'%m-%d-%Y') 08-16-2019 用給定的格式顯示日期和時間

在使用DATE_ADDDATE_SUB這兩個函數時需要注意,增加或減去的時間間隔單位可以自己定義,下邊是MySQL支持的一些時間單位:

時間單位 描述
MICROSECOND 毫秒
SECOND
MINUTE 分鐘
HOUR 小時
DAY
WEEK 星期
MONTH
QUARTER 季度
YEAR

如果我們相讓2019-08-16 17:10:43這個時間值增加2分鐘,可以這麼寫:

SELECT DATE_ADD('2019-08-16 17:10:43', INTERVAL 2 MINUTE);

在使用DATE_FORMAT函數時需要注意,我們可以通過一些所謂的格式符來自定義日期和時間的顯示格式,下邊是MySQL中常用的一些日期和時間的格式符以及它們對應的含義:

格式符 描述
%b 簡寫的月份名稱(Jan、Feb、…、Dec)
%D 帶有英文後綴的月份中的日期(0th、1st、2nd、…、31st))
%d 數字格式的月份中的日期(00、01、02、…、31)
%f 微秒(000000-999999)
%H 二十四小時制的小時 (00-23)
%h 十二小時制的小時 (01-12)
%i 數值格式的分鐘(00-59)
%M 月份名(January、February、…、December)
%m 數值形式的月份(00-12)
%p 上午或下午(AM代表上午、PM代表下午)
%S 秒(00-59)
%s 秒(00-59)
%W 星期名(Sunday、Monday、…、Saturday)
%w 周內第幾天 (0=星期日、1=星期一、 6=星期六)
%Y 4位數字形式的年(例如2019)
%y 2位數字形式的年(例如19)

可以把我們想要的顯示格式用對應的格式符描述出來,就像這樣:

SELECT DATE_FORMAT(NOW(),'%b %d %Y %h:%i %p');

'%b %d %Y %h:%i %p'就是一個用格式符描述的顯示格式,意味着對應的日期和時間應該以下邊描述的方式展示:

  • 先輸出簡寫的月份名稱(格式符%b),也就是示例中的Aug,然後輸出一個空格。
  • 再輸出用數字格式表示的的月份中的日期(格式符%d),也就是示例中的16,然後輸出一個空格。
  • 再輸出4位數字形式的年(格式符%Y),也就是示例中的2019,然後輸出一個空格。
  • 再輸出十二小時制的小時(格式符%h),也就是示例中的05,然後輸出一個冒號:
  • 再輸出數值格式的分鐘(格式符%i),也就是示例中的10,然後輸出一個空格。
  • 最後輸出上午或者下午(格式符%p),也就是示例中的PM

數值處理函數

名稱 調用示例 示例結果 描述
ABS ABS(-1) 1 取絕對值
Pi PI() 3.141593 返回圓周率
COS COS(PI()) -1 返回一個角度的餘弦
EXP EXP(1) 2.718281828459045 返回e的指定次方
MOD MOD(5,2) 1 返回除法的餘數
RAND RAND() 0.7537623539136372 返回一個隨機數
SIN SIN(PI()/2) 1 返回一個角度的正弦
SQRT SQRT(9) 3 返回一個數的平方根
TAN TAN(0) 0 返回一個角度的正切

聚集函數

如果將上邊介紹的那些函數以函數調用的形式放在查詢列表中,那麼會爲表中符合WHERE條件的每一條記錄調用一次該函數。

有些函數是用來統計數據的,比方說統計一下表中的行數,某一列數據的最大值是什麼,我們把這種函數稱之爲聚集函數,下邊介紹MySQL中常用的幾種聚集函數

函數名 描述
COUNT 返回某列的行數
MAX 返回某列的最大值
MIN 返回某列的最小值
SUM 返回某列值之和
AVG 返回某列的平均值

COUNT函數使用來統計行數的,它有下邊兩種使用方式:

  1. COUNT(*):對錶中行的數目進行計數,不管列的值是不是NULL
  2. COUNT(列名):對特定的列進行計數,會忽略掉該列爲NULL的行。

兩者的區別是會不會忽略統計列的值爲NULL的行!

聚集函數中DISTINCT的使用

默認情況下,上邊介紹的聚集函數將計算指定列的所有非NULL數據,如果我們指定的列中有重複數據的話,可以選擇使用DISTINCT來過濾掉這些重複數據。比方說我們想查看一下student_info表中存儲了多少個專業的學生信息,就可以這麼寫:

SELECT COUNT(DISTINCT major) FROM student_info;

組合聚集函數

這些聚集函數也可以集中在一個查詢中使用,比如這樣:

SELECT COUNT(*) AS 成績記錄總數, MAX(score) AS 最高成績, MIN(score) AS 最低成績, AVG(score) AS 平均成績 FROM student_score;

隱式類型轉換

只要某個值的類型與上下文要求的類型不符,MySQL就會根據上下文環境中需要的類型對該值進行類型轉換,由於這些類型轉換都是MySQL自動完成的,所以也可以被稱爲隱式類型轉換。我們列舉幾種常見的隱式類型轉換的場景:

  1. 把操作數類型轉換爲適合操作符計算的相應類型。

    比方說對於加法操作符+來說,它要求兩個操作數都必須是數字才能進行計算,所以如果某個操作數不是數字的話,會將其隱式轉換爲數字,比方說下邊這幾個例子:

    1 + 2       →   3
    '1' + 2     →   3
    '1' + '2'   →   3
    

    雖然'1''2'都是字符串,但是如果它們作爲加法操作符+的操作數的話,都會被強制轉換爲數字,所以上邊幾個表達式其實都會被當作1 + 2去處理的

  2. 將函數參數轉換爲該函數期望的類型。

    我們拿用於拼接字符串的CONCAT函數舉例,這個函數以字符串類型的值作爲參數,如果我們在調用這個函數的時候,傳入了別的類型的值作爲參數,MySQL會自動把這些值的類型轉換爲字符串類型的:

    CONCAT('1', '2')    →   '12'
    CONCAT('1', 2)      →   '12'
    CONCAT(1, 2)        →   '12'
    

    雖然12都是數字,但是如果它們作爲CONCAT函數的參數的話,都會被強制轉換爲字符串,所以上邊幾個表達式其實都會被當作CONCAT('1', '2)去處理的

  3. 存儲數據時,把某個值轉換爲某個列需要的類型。

類型轉換的注意事項

  1. MySQL會盡量把值轉換爲表達式中需要的類型,而不是產生錯誤。

    按理說'23sfd'這個字符串無法轉換爲數字,但是MySQL規定只要字符串的開頭部分包含數字,那麼就把這個字符串轉換爲開頭的數字,如果開頭並沒有包含數字,那麼將被轉換成0,比方說這樣:

    '23sfd'         →   23
    '2019-08-28'    →   2019
    '11:30:32'      →   11
    'sfd'           →   0
    

    不過需要注意的是,這種強制轉換不能用於存儲數據中

  2. 在運算時會自動提升操作數的類型。比如TINYINT最大能表示的數字是127,如果兩個相加就會爆,所以MySQL會自動提升操作數的類型,讓結果不爆。MySQL自動將整數類型的操作數提升到了BIGINT,這樣就不會產生運算結果太大超過TINYINT能表示的數值範圍的尷尬情況了。類似的,有浮點數的運算過程會把操作數自動轉型爲DOUBLE類型。

分組查詢

分組的概念,就是:針對某個列,將該列的值相同的記錄分到一個組中。

創建分組

subject列中有多少不重複的課程,那就會有多少個分組。幸運的是,只要我們在GROUP BY子句中添加上分組列就好了,MySQL會幫助我們自動建立分組來方便我們統計信息,具體語句如下:

mysql> SELECT subject, AVG(score) FROM student_score GROUP BY subject;
+-----------------------------+------------+
| subject                     | AVG(score) |
+-----------------------------+------------+
| 母豬的產後護理              |    73.0000 |
| 論薩達姆的戰爭準備          |    73.2500 |
+-----------------------------+------------+
2 rows in set (0.01 sec)

這個查詢的執行過程就是按照subject中的值將所有的記錄分成兩組,然後分別對每個分組中記錄的score列調用AVG函數進行數據統計。

在使用分組的時候必須要意識到,分組的存在僅僅是爲了方便我們分別統計各個分組中的信息,所以我們只需要把分組列和聚集函數放到查詢列表處就好!當然,如果非分組列出現在查詢列表中會出現什麼情況呢?比如下邊這個查詢:

mysql> SELECT number, subject, AVG(score) FROM student_score GROUP BY subject;
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'xiaohaizi.student_score.number' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

可以看到出現了錯誤。爲啥會錯誤呢?

本例中的查詢列表處放置了既非分組列、又非聚集函數的number列。這樣會產生不確定性,所以MySQL中不允許。

帶有WHERE子句的分組查詢

這時就需要使用WHERE子句了。比如老師覺得各個科目的平均分太低了,所以想先把分數低於60分的記錄去掉之後再統計平均分,就可以這麼寫:

mysql> SELECT subject, AVG(score) FROM student_score WHERE score >= 60 GROUP BY subject;
+-----------------------------+------------+
| subject                     | AVG(score) |
+-----------------------------+------------+
| 母豬的產後護理              |    89.0000 |
| 論薩達姆的戰爭準備          |    82.3333 |
+-----------------------------+------------+
2 rows in set (0.00 sec)

整個過程是,首先過濾掉不符合where條件的記錄,然後再進行分組。

作用於分組的過濾條件

HAVING子句,當分組有很多不重複的值時,我們需要的結果中並不需要這麼多,我們只想把符合條件的分組加入到結果集。

mysql> SELECT subject, AVG(score) FROM student_score GROUP BY subject HAVING AVG(score) > 73;
+-----------------------------+------------+
| subject                     | AVG(score) |
+-----------------------------+------------+
| 論薩達姆的戰爭準備          |    73.2500 |
+-----------------------------+------------+
1 row in set (0.00 sec)

其實這裏所謂的針對分組的條件一般是指下邊這兩種:

  • 分組列

    也就是說我們可以把用於分組的列放到HAVING子句的條件中,比如這樣:

    SELECT subject, AVG(score) FROM student_score GROUP BY subject having subject = '母豬的產後護理';
    
  • 作用於分組的聚集函數

    當然,並不是HAVING子句中只能放置在查詢列表出現的那些聚集函數,只要是針對這個分組進行統計的聚集函數都可以,比方說老師想查詢最高分大於98分的課程的平均分,可以這麼寫:

    mysql> SELECT subject, AVG(score) FROM student_score GROUP BY subject HAVING MAX(score) > 98;
    +-----------------------+------------+
    | subject               | AVG(score) |
    +-----------------------+------------+
    | 母豬的產後護理        |    73.0000 |
    +-----------------------+------------+
    1 row in set (0.00 sec)
    
    mysql>
    

    其中的MAX(score)這個聚集函數並沒有出現在查詢列表中,但仍然可以作爲HAVING子句中表達式的一部分。

分組和排序

如果我們想對各個分組查詢出來的統計數據進行排序,需要爲查詢列表中有聚集函數的表達式添加別名,比如想按照各個學科的平均分從大到小降序排序,可以這麼寫:

mysql> SELECT subject, AVG(score) AS avg_score FROM student_score GROUP BY subject ORDER BY avg_score DESC;
+-----------------------------+-----------+
| subject                     | avg_score |
+-----------------------------+-----------+
| 論薩達姆的戰爭準備          |   73.2500 |
| 母豬的產後護理              |   73.0000 |
+-----------------------------+-----------+
2 rows in set (0.01 sec)

mysql>

嵌套分組

有時候按照某個列進行分組太籠統,一個分組內可以被繼續劃分成更小的分組。比方說對於student_info表來說

表結構如下:

+----------+--------+-----+--------------------+------------+------------------+-----------------+
| number   | name   | sex | id_number          | department | major            | enrollment_time |
+----------+--------+-----+--------------------+------------+------------------+-----------------+
| 20180101 | 杜子騰 | 男  | 158177199901044792 | 計算機學院 | 計算機科學與工程 | 2018-09-01      |
| 20180102 | 杜琦燕 | 女  | 151008199801178529 | 計算機學院 | 計算機科學與工程 | 2018-09-01      |
| 20180103 | 範統   | 男  | 17156319980116959X | 計算機學院 | 軟件工程         | 2018-09-01      |
| 20180104 | 史珍香 | 女  | 141992199701078600 | 計算機學院 | 軟件工程         | 2018-09-01      |
| 20180105 | 範劍   | 男  | 181048199308156368 | 航天學院   | 飛行器設計       | 2018-09-01      |
| 20180106 | 朱逸羣 | 男  | 197995199501078445 | 航天學院   | 電子信息         | 2018-09-01      |
+----------+--------+-----+--------------------+------------+------------------+-----------------+

如果先按照 department 分組,再按照 major 分組,則爲:

SELECT department, major, COUNT(*) FROM student_info GROUP BY department, major;
結果爲:
+------------+------------------+----------+
| department | major            | COUNT(*) |
+------------+------------------+----------+
| 航天學院   | 電子信息         | 1        |
| 航天學院   | 飛行器設計       | 1        |
| 計算機學院 | 計算機科學與工程 | 2        |
| 計算機學院 | 軟件工程         | 2        |
+------------+------------------+----------+

使用分組注意事項

使用分組來統計數據給我們帶來了非常大的便利,但是要隨時提防有坑的地方:

  1. 如果分組列中含有NULL值,那麼NULL也會作爲一個獨立的分組存在。

  2. 如果存在多個分組列,也就是嵌套分組,聚集函數將作用在最後的那個分組列上。

  3. 如果查詢語句中存在WHERE子句和ORDER BY子句,那麼GROUP BY子句必須出現在WHERE子句之後,ORDER BY子句之前。

  4. 非分組列不能單獨出現在檢索列表中(可以被放到聚集函數中)。

  5. GROUP BY子句後也可以跟隨表達式(但不能是聚集函數)。

    上邊介紹的GROUP BY後跟隨的都是表中的某個列或者某些列,其實一個表達式也可以,比如這樣:

    mysql> SELECT concat('專業:', major), COUNT(*) FROM student_info GROUP BY concat('專業:', major);
    +-----------------------------------+----------+
    | concat('專業:', major)           | COUNT(*) |
    +-----------------------------------+----------+
    | 專業:電子信息                    |        1 |
    | 專業:計算機科學與工程            |        2 |
    | 專業:軟件工程                    |        2 |
    | 專業:飛行器設計                  |        1 |
    +-----------------------------------+----------+
    4 rows in set (0.00 sec)
    
    mysql>
    

    MySQL會根據這個表達式的值來對記錄進行分組,使用表達式進行分組的時候需要特別注意,查詢列表中的表達式和GROUP BY子句中的表達式必須完全一樣。不過一般情況下我們也不會用表達式進行分組,所以目前基本沒啥用~

  6. WHERE子句和HAVING子句的區別。

    WHERE子句在分組前進行過濾,作用於每一條記錄,WHERE子句過濾掉的記錄將不包括在分組中。而HAVING子句在數據分組後進行過濾,作用於整個分組。

簡單查詢語句中各子句的順序

我們上邊介紹了查詢語句的各個子句,但是除了SELECT之外,其他的子句全都是可以省略的。如果在一個查詢語句中出現了多個子句,那麼它們之間的順序是不能亂放的,順序如下所示:

SELECT [DISTINCT] 查詢列表
[FROM 表名]
[WHERE 布爾表達式]
[GROUP BY 分組列表 ]
[HAVING 分組過濾條件]
[ORDER BY 排序列表]
[LIMIT 開始行, 限制條數]

其中中括號[]中的內容表示可以省略,我們在書寫查詢語句的時候各個子句必須嚴格遵守這個順序,不然會報錯的!

子查詢

標量子查詢

主要用於合併多個SQL語句,必須要查一個人的各科成績,需要學號,而學號在另一個表

mysql root@(none):xiaohaizi> select * from student_score where number = (select number from student_info where name = '杜琦燕') ; 
+----------+--------------------+-------+
| number   | subject            | score |
+----------+--------------------+-------+
| 20180102 | 母豬的產後護理     | 100   |
| 20180102 | 論薩達姆的戰爭準備 | 98    |
+----------+--------------------+-------+
2 rows in set
Time: 0.010s

子查詢必須放在小括號()中,小括號中的查詢語句也被稱爲子查詢或者內層查詢,使用內層查詢的結果作爲搜索條件的操作數的查詢稱爲外層查詢。如果你在一個查詢語句中需要用到更多的表的話,那麼在一個子查詢中可以繼續嵌套另一個子查詢,在執行查詢語句時,將按照從內到外的順序依次執行這些查詢。

標量子查詢單純的代表一個值,由標量子查詢作爲的操作數組成的搜索條件只要符合表達語法就可以。

列子查詢

如果我們想查詢'計算機科學與工程'專業的學生的成績,我們最後要得到的是成績,所以成績先寫前面,成績需要用到學號查,那麼我們的子查詢只需要得到計算機科學與工程專業的學號,最後用 in 語句連接:

mysql root@(none):xiaohaizi> select * from student_score where number in (select number from student_info where major='計算機科學與工程' );                                                    
+----------+--------------------+-------+
| number   | subject            | score |
+----------+--------------------+-------+
| 20180101 | 母豬的產後護理     | 78    |
| 20180101 | 論薩達姆的戰爭準備 | 88    |
| 20180102 | 母豬的產後護理     | 100   |
| 20180102 | 論薩達姆的戰爭準備 | 98    |
+----------+--------------------+-------+
4 rows in set
Time: 0.015s

行子查詢

列子查詢,大家肯定就好奇有沒有行子查詢。哈哈,當然有了,只要子查詢的結果集中最多隻包含一條記錄,而且這條記錄中有超過一個列的數據(如果該條記錄只包含一個列的話,該子查詢就成了標量子查詢),那麼這個子查詢就可以被稱之爲行子查詢,比如這樣:

mysql> SELECT * FROM student_score WHERE (number, subject) = (SELECT number, '母豬的產後護理' FROM student_info LIMIT 1);
+----------+-----------------------+-------+
| number   | subject               | score |
+----------+-----------------------+-------+
| 20180104 | 母豬的產後護理        |    55 |
+----------+-----------------------+-------+
1 row in set (0.01 sec)

mysql>

在想要得到標量子查詢或者行子查詢,但又不能保證子查詢的結果集只有一條記錄時,應該使用LIMIT 1子句來限制記錄數量。

表子查詢

如果子查詢結果集中包含多行多列,那麼這個子查詢也可以被稱之爲表子查詢,比如這樣:

mysql> SELECT * FROM student_score WHERE (number, subject) IN (SELECT number, '母豬的產後護理' FROM student_info WHERE major = '計算機科學與工程');
+----------+-----------------------+-------+
| number   | subject               | score |
+----------+-----------------------+-------+
| 20180101 | 母豬的產後護理        |    78 |
| 20180102 | 母豬的產後護理        |   100 |
+----------+-----------------------+-------+
2 rows in set (0.00 sec)

mysql>

在這個例子中的子查詢執行之後的結果集中包含多行多列,所以可以被看作是一個表子查詢

EXISTS和NOT EXISTS子查詢

有時候外層查詢並不關心子查詢中的結果是什麼,而只關心子查詢的結果集是不是爲空集,這時可以用到下邊這兩個操作符:

操作符 示例 描述
EXISTS EXISTS (SELECT ...) 當子查詢結果集不是空集時表達式爲真
NOT EXISTS NOT EXISTS (SELECT ...) 當子查詢結果集是空集時表達式爲真

我們來舉個例子:

mysql> SELECT * FROM student_score WHERE EXISTS (SELECT * FROM student_info WHERE number = 20180108);
Empty set (0.00 sec)

mysql>

其中子查詢的意思是在student_info表中查找學號爲20180108的學生信息,很顯然並沒有學號爲20180108的學生,所以子查詢的結果集是一個空集,於是EXISTS表達式的結果爲FALSE,所以外層查詢也就不查了,直接返回了一個Empty set,表示沒有結果。你可以自己試一下NOT EXISTS的使用。

不相關子查詢和相關子查詢

前邊介紹的子查詢和外層查詢都沒有依賴關係,也就是說子查詢可以獨立運行併產生結果之後,再拿結果作爲外層查詢的條件去執行外層查詢,這種子查詢稱爲不相關子查詢

而有時候我們需要在子查詢的語句中引用到外層查詢的值,這樣的話子查詢就不能當作一個獨立的語句去執行,這種子查詢被稱爲相關子查詢。比方說我們想查看一些學生的基本信息,但是前提是這些學生在student_score表中有成績記錄,那可以這麼寫:

mysql> SELECT number, name, id_number, major FROM student_info WHERE EXISTS (SELECT * FROM student_score WHERE student_score.number = student_info.number);
+----------+-----------+--------------------+--------------------------+
| number   | name      | id_number          | major                    |
+----------+-----------+--------------------+--------------------------+
| 20180101 | 杜子騰    | 158177199901044792 | 計算機科學與工程         |
| 20180102 | 杜琦燕    | 151008199801178529 | 計算機科學與工程         |
| 20180103 | 範統      | 17156319980116959X | 軟件工程                 |
| 20180104 | 史珍香    | 141992199701078600 | 軟件工程                 |
+----------+-----------+--------------------+--------------------------+
4 rows in set (0.00 sec)

mysql>

student_info和student_score表裏都有number列,所以在子查詢的WHERE語句中書寫number = number會造成二義性,也就是讓服務器懵逼,不知道這個number列到底是哪個表的,所以爲了區分,在列名前邊加上了表名,並用點.連接起來,這種顯式的將列所屬的表名書寫出來的名稱稱爲該列的全限定名。所以上邊子查詢的WHERE語句中用了列的全限定名:student_score.number = student_info.number。

這條查詢語句可以分成這麼兩部分來理解

對同一個表的子查詢

比方說我們想看看在student_score表的'母豬的產後護理'這門課的成績中,有哪些超過了平均分的記錄

聚集函數不能放到WHERE子句中

mysql>  SELECT * FROM student_score WHERE subject = '母豬的產後護理' AND score > (SELECT AVG(score) FROM student_score WHERE subject = '母豬的產後護理');
+----------+-----------------------+-------+
| number   | subject               | score |
+----------+-----------------------+-------+
| 20180101 | 母豬的產後護理        |    78 |
| 20180102 | 母豬的產後護理        |   100 |
+----------+-----------------------+-------+
2 rows in set (0.01 sec)

mysql>

我們使用子查詢先統計出了'母豬的產後護理'這門課的平均分,然後再到外層查詢中使用這個平均分組成的表達式來作爲搜索條件去查找大於平均分的記錄。

連接查詢

連接本質上就是把多張表合成一張大表。設計的時候爲了節省存儲空間,故而分成多張表,本質上多個表組合後結果集就是多個表的笛卡爾積

最簡單的表的連接方式爲:

SELECT t1.m1, t1.n1, t2.m2, t2.n2 FROM t1, t2;
SELECT m1, n1, m2, n2 FROM t1, t2;
SELECT t1.*, t2.* FROM t1, t2;

連接過程簡介

笛卡爾積一般來說是非常大的,所以我們一般不把整張表全部連接,只需要在連接過程中篩選出我們需要的。其中,過濾條件可以分成兩種:

  • 涉及單張表的條件
  • 設計多張表的條件

比如下面的查詢語句:

SELECT * FROM t1, t2 WHERE t1.m1 > 1 AND t1.m1 = t2.m2 AND t2.n2 < 'd';

在這個查詢中我們指明瞭這三個過濾條件:

  • t1.m1 > 1
  • t1.m1 = t2.m2
  • t2.n2 < 'd'

內連接和外連接

假設我們要查詢出學生的基本信息,兩個表由學號相連,所以可以這麼寫:

mysql root@(none):xiaohaizi> SELECT student_info.number, name, major, subject, score FROM student_info, student_score WHERE student_info.number = student_score.number;    
+----------+--------+------------------+--------------------+-------+
| number   | name   | major            | subject            | score |
+----------+--------+------------------+--------------------+-------+
| 20180101 | 杜子騰 | 計算機科學與工程 | 母豬的產後護理     | 78    |
| 20180101 | 杜子騰 | 計算機科學與工程 | 論薩達姆的戰爭準備 | 88    |
| 20180102 | 杜琦燕 | 計算機科學與工程 | 母豬的產後護理     | 100   |
| 20180102 | 杜琦燕 | 計算機科學與工程 | 論薩達姆的戰爭準備 | 98    |
| 20180103 | 範統   | 軟件工程         | 母豬的產後護理     | 59    |
| 20180103 | 範統   | 軟件工程         | 論薩達姆的戰爭準備 | 61    |
| 20180104 | 史珍香 | 軟件工程         | 母豬的產後護理     | 55    |
| 20180104 | 史珍香 | 軟件工程         | 論薩達姆的戰爭準備 | 46    |
+----------+--------+------------------+--------------------+-------+
8 rows in set
Time: 0.012s

但是比如有些同學缺考,卻並沒有被查詢出來,所以在score表中沒有記錄,這個需求的本質是:驅動表中的記錄即使在被驅動表中沒有出現過,也需要加入結果集

爲了解決這個問題,就有了內連接外連接的概念:

  • 對於內連接的兩個表,驅動表中的記錄在被驅動表中找不到匹配的記錄,該記錄不會加入到最後的結果集,我們上邊提到的連接都是所謂的內連接

  • 對於外連接的兩個表,驅動表中的記錄即使在被驅動表中沒有匹配的記錄,也仍然需要加入到結果集。

    MySQL中,根據選取驅動表的不同,外連接仍然可以細分爲2種:

    • 左外連接

      選取左側的表爲驅動表。

    • 右外連接

      選取右側的表爲驅動表。

可是這樣仍然存在問題,即使對於外連接來說,有時候我們也並不想把驅動表的全部記錄都加入到最後的結果集。這就犯難了,有時候匹配失敗要加入結果集,有時候又不要加入結果集,這咋辦,有點兒愁啊。。。噫,把過濾條件分爲兩種不就解決了這個問題了麼,所以放在不同地方的過濾條件是有不同語義的:

  • WHERE子句中的過濾條件

    WHERE子句中的過濾條件就是我們平時見的那種,不論是內連接還是外連接,凡是不符合WHERE子句中的過濾條件的記錄都不會被加入最後的結果集。

  • ON子句中的過濾條件

    對於外連接的驅動表的記錄來說,如果無法在被驅動表中找到匹配ON子句中的過濾條件的記錄,那麼該記錄仍然會被加入到結果集中,對應的被驅動表記錄的各個字段使用NULL值填充。

    需要注意的是,這個**ON子句是專門爲外連接驅動表中的記錄在被驅動表找不到匹配記錄時應不應該把該記錄加入結果集這個場景下提出的**,所以如果把ON子句放到內連接中,MySQL會把它和WHERE子句一樣對待,也就是說:內連接中的WHERE子句和ON子句是等價的。

一般情況下,我們都把只涉及單表的過濾條件放到WHERE子句中,把涉及兩表的過濾條件都放到ON子句中,我們也一般把放到ON子句中的過濾條件也稱之爲連接條件

左(外)連接的語法

比如我們要把t1表和t2表進行左外連接查詢可以這麼寫:

SELECT * FROM t1 LEFT [OUTER] JOIN t2 ON 連接條件 [WHERE 普通過濾條件];

其中中括號裏的OUTER單詞是可以省略的。對於LEFT JOIN類型的連接來說,我們把放在左邊的表稱之爲外表或者驅動表,右邊的表稱之爲內表或者被驅動表。所以上述例子中t1就是外表或者驅動表,t2就是內表或者被驅動表。需要注意的是,對於左(外)連接和右(外)連接來說,必須使用ON子句來指出連接條件。瞭解了左(外)連接的基本語法之後,再次回到我們上邊那個現實問題中來,看看怎樣寫查詢語句才能把所有的學生的成績信息都查詢出來,即使是缺考的考生也應該被放到結果集中:

mysql> SELECT student_info.number, name, major, subject, score FROM student_info LEFT JOIN student_score ON student_info.number = student_score.number;
+----------+-----------+--------------------------+-----------------------------+-------+
| number   | name      | major                    | subject                     | score |
+----------+-----------+--------------------------+-----------------------------+-------+
| 20180101 | 杜子騰    | 計算機科學與工程         | 母豬的產後護理              |    78 |
| 20180101 | 杜子騰    | 計算機科學與工程         | 論薩達姆的戰爭準備          |    88 |
| 20180102 | 杜琦燕    | 計算機科學與工程         | 母豬的產後護理              |   100 |
| 20180102 | 杜琦燕    | 計算機科學與工程         | 論薩達姆的戰爭準備          |    98 |
| 20180103 | 範統      | 軟件工程                 | 母豬的產後護理              |    59 |
| 20180103 | 範統      | 軟件工程                 | 論薩達姆的戰爭準備          |    61 |
| 20180104 | 史珍香    | 軟件工程                 | 母豬的產後護理              |    55 |
| 20180104 | 史珍香    | 軟件工程                 | 論薩達姆的戰爭準備          |    46 |
| 20180105 | 範劍      | 飛行器設計               | NULL                        |  NULL |
| 20180106 | 朱逸羣    | 電子信息                 | NULL                        |  NULL |
+----------+-----------+--------------------------+-----------------------------+-------+
10 rows in set (0.00 sec)

mysql>

從結果集中可以看出來,雖然範劍朱逸羣並沒有對應的成績記錄,但是由於採用的是連接類型爲左(外)連接,所以仍然把它放到了結果集中,只不過在對應的成績記錄的各列使用NULL值填充而已。

右(外)連接的語法

右(外)連接和左(外)連接的原理是一樣一樣的,語法也只是把LEFT換成RIGHT而已:

SELECT * FROM t1 RIGHT [OUTER] JOIN t2 ON 連接條件 [WHERE 普通過濾條件];

內連接的語法

內連接和外連接的根本區別就是在驅動表中的記錄不符合ON子句中的連接條件時不會把該記錄加入到最後的結果集,我們最開始說的那些連接查詢的類型都是內連接。不過之前僅僅提到了一種最簡單的內連接語法,就是直接把需要連接的多個表都放到FROM子句後邊。其實針對內連接,MySQL提供了好多不同的語法,我們以t1t2表爲例瞅瞅:

SELECT * FROM t1 [INNER | CROSS] JOIN t2 [ON 連接條件] [WHERE 普通過濾條件];

也就是說在MySQL中,下邊這幾種內連接的寫法都是等價的:

  • SELECT * FROM t1 JOIN t2;
  • SELECT * FROM t1 INNER JOIN t2;
  • SELECT * FROM t1 CROSS JOIN t2;

上邊的這些寫法和直接把需要連接的表名放到FROM語句之後,用逗號,分隔開的寫法是等價的:

 SELECT * FROM t1, t2;

現在我們雖然介紹了很多種內連接的書寫方式,不過熟悉一種就好了,這裏我們推薦INNER JOIN的形式書寫內連接(因爲INNER JOIN語義很明確嘛,可以和LEFT JOINRIGHT JOIN很輕鬆的區分開)。這裏需要注意的是,由於在內連接中ON子句和WHERE子句是等價的,所以內連接中不要求強制寫明ON子句。

我們前邊說過,連接的本質就是把各個連接表中的記錄都取出來依次匹配的組合加入結果集並返回給用戶。不論哪個表作爲驅動表,兩表連接產生的笛卡爾積肯定是一樣的。而對於內連接來說,由於凡是不符合ON子句或WHERE子句中的條件的記錄都會被過濾掉,其實也就相當於從兩表連接的笛卡爾積中把不符合過濾條件的記錄給踢出去,所以對於內連接來說,驅動表和被驅動表是可以互換的,並不會影響最後的查詢結果。但是對於外連接來說,由於驅動表中的記錄即使在被驅動表中找不到符合ON子句連接條件的記錄也會被加入結果集,所以此時驅動表和被驅動表的關係就很重要了,也就是說左外連接和右外連接的驅動表和被驅動表不能輕易互換。

舉個例子,現有t1表和t2表,如下:

mysql> SELECT * FROM t1;
+------+------+
| m1   | n1   |
+------+------+
|    1 | a    |
|    2 | b    |
|    3 | c    |
+------+------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM t2;
+------+------+
| m2   | n2   |
+------+------+
|    2 | b    |
|    3 | c    |
|    4 | d    |
+------+------+
3 rows in set (0.00 sec)

執行各種語句的結果爲:

mysql> SELECT * FROM t1 INNER JOIN t2 ON t1.m1 = t2.m2;
+------+------+------+------+
| m1   | n1   | m2   | n2   |
+------+------+------+------+
|    2 | b    |    2 | b    |
|    3 | c    |    3 | c    |
+------+------+------+------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM t1 LEFT JOIN t2 ON t1.m1 = t2.m2;
+------+------+------+------+
| m1   | n1   | m2   | n2   |
+------+------+------+------+
|    2 | b    |    2 | b    |
|    3 | c    |    3 | c    |
|    1 | a    | NULL | NULL |
+------+------+------+------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM t1 RIGHT JOIN t2 ON t1.m1 = t2.m2;
+------+------+------+------+
| m1   | n1   | m2   | n2   |
+------+------+------+------+
|    2 | b    |    2 | b    |
|    3 | c    |    3 | c    |
| NULL | NULL |    4 | d    |
+------+------+------+------+
3 rows in set (0.00 sec)

多表連接

不管是多少個表的連接,本質上就是各個表的記錄在符合過濾條件下的自由組合。

表的別名

我們也可以爲表來定義別名,格式與定義列的別名一致,都是用空白字符或者AS隔開,這個在表名特別長的情況下可以讓語句表達更清晰一些,比如這樣:

mysql> SELECT s1.number, s1.name, s1.major, s2.subject, s2.score FROM student_info AS s1 INNER JOIN student_score AS s2 WHERE s1.number = s2.number;
+----------+-----------+--------------------------+-----------------------------+-------+
| number   | name      | major                    | subject                     | score |
+----------+-----------+--------------------------+-----------------------------+-------+
| 20180101 | 杜子騰    | 計算機科學與工程         | 母豬的產後護理              |    78 |
| 20180101 | 杜子騰    | 計算機科學與工程         | 論薩達姆的戰爭準備          |    88 |
| 20180102 | 杜琦燕    | 計算機科學與工程         | 母豬的產後護理              |   100 |
| 20180102 | 杜琦燕    | 計算機科學與工程         | 論薩達姆的戰爭準備          |    98 |
| 20180103 | 範統      | 軟件工程                 | 母豬的產後護理              |    59 |
| 20180103 | 範統      | 軟件工程                 | 論薩達姆的戰爭準備          |    61 |
| 20180104 | 史珍香    | 軟件工程                 | 母豬的產後護理              |    55 |
| 20180104 | 史珍香    | 軟件工程                 | 論薩達姆的戰爭準備          |    46 |
+----------+-----------+--------------------------+-----------------------------+-------+
8 rows in set (0.00 sec)

mysql>

這個例子中,我們在FROM子句中給student_info定義了一個別名s1student_score定義了一個別名s2,那麼在整個查詢語句的其他地方就可以引用這個別名來替代該表本身的名字了。

自連接

我們這裏需要的是兩張一模一樣的t1表進行連接,爲了把兩個一樣的表區分一下,需要爲表定義別名。比如這樣:

mysql> SELECT * FROM t1 AS table1, t1 AS table2;

連接查詢與子查詢的轉換

有的查詢需求既可以使用連接查詢解決,也可以使用子查詢解決,比如

SELECT * FROM student_score WHERE number IN (SELECT number FROM student_info WHERE major = '計算機科學與工程');

這個子查詢就可以被替換:

SELECT s2.* FROM student_info AS s1 INNER JOIN student_score AS s2 WHERE s1.number = s2.number AND s1.major = '計算機科學與工程';

組合查詢

多條查詢語句產生的結果集查也可以被合併成一個大的結果集,這種將多條查詢語句產生的結果集合並起來的查詢方式稱爲合併查詢,或者組合查詢

涉及單表的組合查詢

使用 UNION 可以將兩個查詢語句連接到一起。

SELECT m1 FROM t1 WHERE m1 < 2 UNION SELECT m1 FROM t1 WHERE m1 > 2;
# 也可以連接多個
SELECT m1 FROM t1 WHERE m1 < 2 UNION SELECT m1 FROM t1 WHERE m1 > 2 UNION SELECT m1 FROM t1 WHERE m1 = 2;

多個表達式也可以,只要數量相同比如下邊這個使用UNION連接起來的各個查詢語句的查詢列表處都有2個表達式:

SELECT m1, n1 FROM t1 WHERE m1 < 2 UNION SELECT m1, n1 FROM t1 WHERE m1 > 2;

使用UNION連接起來的各個查詢語句的查詢列表中位置相同的表達式的類型應該是相同的。當然這不是硬性要求,如果不匹配的話,MySQL將會自動的進行類型轉換,比方說下邊這個組合查詢語句:

SELECT m1, n1 FROM t1 WHERE m1 < 2 UNION SELECT n1, m1 FROM t1 WHERE m1 > 2;

使用UNION連接起來的兩個查詢中,第一個語句的查詢列表是m1, n1,第二個查詢語句的查詢列表是n1, m1,我們應該注意兩點:

  • 第一個查詢的查詢列表處的m1和第二個查詢的查詢列表的n1對應,第一個查詢的查詢列表處的n1和第二個查詢的查詢列表的m1對應,m1n1雖然類型不同,但MySQL會幫助我們自動進行必要的類型轉換。
  • 這幾個查詢語句的結果集都可以被合併到一個大的結果集中,但是這個大的結果集總是要有展示一下列名的吧,所以就規定組合查詢的結果集中顯示的列名將以第一個查詢中的列名爲準,上邊的例子就採用了第一個查詢中的m1, n1作爲結果集的列名。

涉及不同表的組合查詢

我們可以使用UNION直接將這兩個查詢語句拼接到一起:

SELECT m1, n1 FROM t1 WHERE m1 < 2 UNION SELECT m2, n2 FROM t2 WHERE m2 > 2;

包含或去除重複的行

默認情況下,使用UNION來合併多個查詢的記錄會默認過濾掉重複的記錄。

如果我們想要保留重複記錄,可以使用UNION ALL來連接多個查詢:

SELECT m1, n1 FROM t1 UNION ALL SELECT m2, n2 FROM t2;

組合查詢中的ORDER BYLIMIT子句

組合查詢會把各個查詢的結果彙總到一塊,如果我們相對最終的結果集進行排序或者只保留幾行的話,可以在組合查詢的語句末尾加上ORDER BYLIMIT子句,就像這樣:

SELECT m1, n1 FROM t1 UNION SELECT m2, n2 FROM t2 ORDER BY m1 DESC LIMIT 2;

這裏需要注意的一點是,由於最後的結果集展示的列名是第一個查詢中給定的列名,所以ORDER BY子句中指定的排序列也必須是第一個查詢中給定的列名(別名也可以)。

如果我們只想單獨爲各個小的查詢排序,而不爲最終的彙總的結果集排序行不行呢?先試試:

mysql> (SELECT m1, n1 FROM t1 ORDER BY m1 DESC) UNION (SELECT m2, n2 FROM t2 ORDER BY m2 DESC);
+------+------+
| m1   | n1   |
+------+------+
|    1 | a    |
|    2 | b    |
|    3 | c    |
|    4 | d    |
+------+------+
4 rows in set (0.00 sec)

mysql>

從結果來看,我們爲各個小查詢加入的ORDER BY子句好像並沒有起作用,這是因爲MySQL規定組合查詢並不保證最後彙總起來的大結果集中的順序是按照各個小查詢的結果集中的順序排序的,也就是說我們在各個小查詢中加入ORDER BY子句的作用和沒加一樣~ 不過如果我們只是單純的想從各個小的查詢中獲取有限條排序好的記錄加入最終的彙總,那是可以滴,比如這樣:

mysql> (SELECT m1, n1 FROM t1 ORDER BY m1 DESC LIMIT 1) UNION (SELECT m2, n2 FROM t2 ORDER BY m2 DESC LIMIT 1);
+------+------+
| m1   | n1   |
+------+------+
|    3 | c    |
|    4 | d    |
+------+------+
2 rows in set (0.00 sec)

mysql>

如圖所示,最終結果集中的(3, 'c')其實就是查詢(SELECT m1, n1 FROM t1 ORDER BY m1 DESC LIMIT 1)的結果,(4, 'd')其實就是查詢(SELECT m2, n2 FROM t2 ORDER BY m2 DESC LIMIT 1)的結果。

數據的插入、刪除和更新

插入數據

插入完整的數據

INSERT INTO 表名 VALUES(1的值,列2的值, ..., 列n的值);
比如:
INSERT INTO first_table VALUES(2, NULL);
也可以自定義順序:
INSERT INTO first_table(first_column, second_column) VALUES (3, 'ccc');

插入記錄的一部分

在插入記錄的時候,某些列的值可以被省略,但是這個列必須滿足下邊列出的某個條件之一:

  • 該列允許存儲NULL值
  • 該列有DEFAULT屬性,給出了默認值

我們定義的first_table表中的兩個字段都允許存放NULL值,所以在插入數據的時候可以省略部分列的值。在INSERT語句中沒有顯式指定的列的值將被設置爲NULL,比如這樣寫:

mysql> INSERT INTO first_table(first_column) VALUES(5);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO first_table(second_column) VALUES('fff');
Query OK, 1 row affected (0.00 sec)

mysql>

第一條插入語句我們只指定了first_column列的值是5,而沒有指定second_column的值,所以second_column的值就是NULL;第二條插入語句我們只指定了second_column的值是'ddd',而沒有指定first_column的值,所以first_column的值就是NULL,也表示沒有數據

批量插入記錄

MySQL爲我們提供了批量插入的語句,就是直接在VALUES後多加幾組值,每組值用小括號()擴起來,各個組之間用逗號分隔就好了,就像這樣:

mysql> INSERT INTO first_table(first_column, second_column) VALUES(7, 'ggg'), (8, 'hhh');

將某個查詢的結果集插入表中

我們想把first_column表中的一些數據插入到second_table表的話可以這麼寫:

mysql> INSERT INTO second_table(s, i) SELECT second_column, first_column FROM first_table WHERE first_column < 5;

相當於先執行查詢語句,再把查詢得到的結果插入到指定的表中。

在將某個查詢的結果集插入到表中時需要注意,INSERT語句指定的列要和查詢列表中的表達式一一對應

INSERT IGNORE

對於那些是主鍵或者具有UNIQUE約束的列或者列組合來說,如果表中已存在的記錄中沒有與待插入記錄在這些列或者列組合上重複的值,那麼就把待插入記錄插到表中,否則忽略此次插入操作MySQL給我們提供了INSERT IGNORE的語法來實現這個功能:

mysql> INSERT IGNORE INTO first_table(first_column, second_column) VALUES(1, '哇哈哈') ;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql>

INSERT ON DUPLICATE KEY UPDATE

對於主鍵或者有唯一性約束的列或列組合來說,新插入的記錄如果和表中已存在的記錄重複的話,我們可以選擇的策略不僅僅是忽略該條記錄的插入,也可以選擇更新這條重複的舊記錄。

對於那些是主鍵或者具有UNIQUE約束的列或者列組合來說,如果表中已存在的記錄中沒有與待插入記錄在這些列或者列組合上重複的值,那麼就把待插入記錄插到表中,否則按照規定去更新那條重複的記錄中某些列的值MySQL給我們提供了INSERT ... ON DUPLICATE KEY UPDATE ...的語法來實現這個功能: 這個語句的意思就是,對於要插入的數據(1, '哇哈哈')來說,如果first_table表中已經存在first_column的列值爲1的記錄(因爲first_column列具有UNIQUE約束),那麼就把該記錄的second_column列更新爲'雪碧'

mysql> INSERT INTO first_table (first_column, second_column) VALUES(1, '哇哈哈') ON DUPLICATE KEY UPDATE second_column = '雪碧';
Query OK, 2 rows affected (0.00 sec)

mysql>
mysql> SELECT * FROM first_table;
+--------------+---------------+
| first_column | second_column |
+--------------+---------------+
|            1 | 雪碧          |
|            2 | NULL          |
|            3 | ccc           |
|            4 | ddd           |
|            5 | NULL          |
|         NULL | fff           |
|            7 | ggg           |
|            8 | hhh           |
|            9 | iii           |
+--------------+---------------+
9 rows in set (0.00 sec)

mysql>

對於那些是主鍵或者具有UNIQUE約束的列或者列組合來說,如果表中已存在的記錄中有與待插入記錄在這些列或者列組合上重複的值,我們可以使用VALUES(列名)的形式來引用待插入記錄中對應列的值,比方說下邊這個INSERT語句:

mysql> INSERT INTO first_table (first_column, second_column) VALUES(1, '哇哈哈') ON DUPLICATE KEY UPDATE second_column = VALUES(second_column);
Query OK, 2 rows affected (0.00 sec)

mysql>

刪除數據

如果某些記錄我們不想要了,那可以使用下邊的語句把它們給刪除掉:

DELETE FROM 表名 [WHERE 表達式];
DELETE FROM second_table; # 刪除所有數據

更新數據

我們有時候對於某些記錄的某些列的值不滿意,需要去修改它們,修改記錄的語法就是這樣:

UPDATE 表名 SET1=1,2=2, ...,  列n=值n [WHERE 布爾表達式];

我們在UPDATE單詞後邊指定要更新的表,然後把你想更新的列的名稱和該列更新後的值寫到SET單詞後邊,如果想更新多個列的話,它們之間用逗號,分隔開。如果我們不指定WHERE子句,那麼表中所有的記錄都會被更新,否則的話只有符合WHERE子句中的條件的記錄纔可以被更新。

視圖

創建視圖

我們可以把視圖理解爲一個查詢語句的別名,創建視圖的語句如下:

CREATE VIEW 視圖名 AS 查詢語句

比如我們想來創建一個視圖可以這麼寫:

mysql> CREATE VIEW male_student_view AS SELECT s1.number, s1.name, s1.major, s2.subject, s2.score FROM student_info AS s1 INNER JOIN student_score AS s2 WHERE s1.number = s2.number AND s1.sex = '男';
Query OK, 0 rows affected (0.02 sec)

這樣,這個名稱爲male_student_view的視圖就代表了那一串查詢語句了。

使用視圖

視圖也可以被稱爲虛擬表,因爲我們可以對視圖進行一些類似表的增刪改查操作,對視圖的一切操作,都會被映射到底層的表上。查詢語句的查詢列表可以被當作視圖虛擬列,比方說male_student_view這個視圖對應的查詢語句中的查詢列表是numbernamemajorsubjectscore,它們就可以被當作male_student_view視圖的虛擬列

視圖其實就相當於是某個查詢語句的別名!創建視圖的時候並不會把那個又臭又長的查詢語句的結果集維護在硬盤或者內存裏!在對視圖進行查詢時,MySQL服務器將會幫助我們把對視圖的查詢語句轉換爲對底層表的查詢語句然後再執行

所以在使用層面,我們完全可以把視圖當作一個表去使用,但是它的實現原理卻是在執行語句時轉換爲對底層表的操作。使用視圖的好處也是顯而易見的,視圖可以簡化語句的書寫,避免了每次都要寫一遍又臭又長的語句,而且對視圖的操作更加直觀,使用者也不用去考慮它的底層實現細節。

利用視圖來創建新視圖

視圖是某個查詢語句的別名,其實這個查詢語句不僅可以從真實表中查詢數據,也可以從另一個視圖中查詢數據,只要是個合法的查詢語句就好了。比方說我們利用male_student_view視圖來創建另一個新視圖可以這麼寫:

mysql> CREATE VIEW by_view AS SELECT number, name, score FROM male_student_view;
Query OK, 0 rows affected (0.02 sec)

在對這種依賴其他的視圖而生成的新視圖進行查詢時,查詢語句會先被轉換成對它依賴的視圖的查詢,再轉換成對底層表的查詢。

創建視圖時指定自定義列名

視圖虛擬列其實是這個視圖對應的查詢語句的查詢列表,我們也可以在創建視圖的時候爲它的虛擬列自定義列名,這些自定義列名寫到視圖名後邊,用逗號,分隔就好了,不過需要注意的是,自定義列名一定要和查詢列表中的表達式一一對應。比如我們新創建一個自定義列名的視圖:

mysql> CREATE VIEW student_info_view(no, n, m) AS SELECT number, name, major FROM student_info;

我們的自定義列名列表是no, n, m,分別對應查詢列表中的number, name, major。有了自定義列名之後,我們之後對視圖的查詢語句都要基於這些自定義列名

查看和刪除視圖

我們創建視圖時默認是將其放在當前數據庫下的,如果我們想查看當前數據庫中有哪些視圖的話,其實和查看有哪些表的命令是一樣的:

mysql> SHOW TABLES;

需要注意的是,因爲視圖是一張虛擬表,所以新創建的視圖的名稱不能和當前數據庫中的其他視圖或者表的名稱衝突!

查看視圖定義:

視圖是一張虛擬表,用來查看視圖結構的語句和用來查看錶結構的語句比較類似,是這樣的:

SHOW CREATE VIEW 視圖名;

可更新的視圖

有些視圖是可更新的,也就是在視圖上執行INSERTDELETEUPDATE語句。對視圖執行INSERT、DELETE、UPDATE語句的本質上是對該視圖對應的底層表中的數據進行增、刪、改操作。

不過並不是可以在所有的視圖上執行更新語句的,在生成視圖的時候使用了下邊這些語句的都不能進行更新:

  • 聚集函數(比如SUM(), MIN(), MAX(), COUNT()等等)
  • DISTINCT
  • GROUP BY
  • HAVING
  • UNION 或者 UNION ALL
  • 某些子查詢
  • 某些連接查詢
  • 等等等等

一般我們只在查詢語句裏使用視圖,而不在INSERT、DELETE、UPDATE語句裏使用視圖

刪除視圖

如果某個視圖我們不想要了,可以使用這個語句來刪除掉它:

DROP VIEW 視圖名

自定義變量和語句結束分隔符

存儲程序

存儲程序可以封裝一些語句,然後給用戶提供一種簡單的方式來調用這個存儲程序,從而間接地執行這些語句。根據調用方式的不同,我們可以把存儲程序分爲存儲例程觸發器事件這幾種類型。其中,存儲例程又可以被細分爲存儲函數存儲過程。如圖:

在這裏插入圖片描述

自定義變量簡介

MySQL中支持自定義變量,比如:

SET @a = 1;

需要注意的是,需要在變量前寫上@符號。

可以把常量賦值給變量,也可以把變量賦值給變量。

還可以將某個查詢的結果賦值給一個變量,前提是這個查詢的結果只有一個值:

mysql> SET @a = (SELECT m1 FROM t1 LIMIT 1);

還可以用另一種形式的語句來將查詢的結果賦值給一個變量:

SELECT n1 FROM t1 LIMIT 1 INTO @b;

語句結束分割符

MySQL客戶端的交互界面處,當我們完成鍵盤輸入並按下回車鍵時,MySQL客戶端會檢測我們輸入的內容中是否包含;\g或者\G這三個符號之一,如果有的話,會把我們輸入的內容發送到服務器。這樣一來,如果我們想一次性給服務器發送多條的話,就需要把這些語句寫到一行中

我們也可以用delimiter命令來自定義MySQL的檢測語句輸入結束的符號,也就是所謂的語句結束分隔符,比如這樣:

mysql> delimiter $
mysql> SELECT * FROM t1 LIMIT 1;
    -> SELECT * FROM t2 LIMIT 1;
    -> SELECT * FROM t3 LIMIT 1;
    -> $
+------+------+
| m1   | n1   |
+------+------+
|    1 | a    |
+------+------+
1 row in set (0.00 sec)

delimiter $命令意味着修改語句結束分隔符爲$,也就是說之後MySQL客戶端檢測用戶語句輸入結束的符號爲$。上邊例子中我們雖然連續輸入了3個以分號;結尾的查詢語句並且按了回車鍵,但是輸入的內容並沒有被提交,直到敲下$符號並回車,MySQL客戶端纔會將我們輸入的內容提交到服務器,此時我們輸入的內容裏已經包含了3個獨立的查詢語句了,所以返回了3個結果集。

存儲函數和存儲過程

存儲函數

創建存儲函數

存儲函數其實就是一種函數,只不過在這個函數裏可以執行MySQL的語句而已。函數的概念大家都應該不陌生,它可以把處理某個問題的過程封裝起來,之後我們直接調用函數就可以去解決這個問題了,簡單方便又環保。MySQL中定義存儲函數的語句如下:

CREATE FUNCTION 存儲函數名稱([參數列表])
RETURNS 返回值類型
BEGIN
    函數體內容
END

從這裏我們可以看出,定義一個存儲函數需要指定函數名稱、參數列表、返回值類型以及函數體內容。如果該函數不需要參數,那參數列表可以被省略,函數體內容可以包括一條或多條語句,每條語句都要以分號;結尾。上邊語句中的製表符和換行僅僅是爲了好看,如果你覺得煩,完全可以把存儲函數的定義都寫在一行裏,用一個或多個空格把上述幾個部分分隔開就好! 光看定義理解的不深刻,我們先寫一個存儲函數開開眼:

mysql> delimiter $
mysql> CREATE FUNCTION avg_score(s VARCHAR(100))
    -> RETURNS DOUBLE
    -> BEGIN
    ->     RETURN (SELECT AVG(score) FROM student_score WHERE subject = s);
    -> END $
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;

我們定義了一個名叫avg_score的函數,它接收一個VARCHAR(100)類型的參數,聲明的返回值類型是DOUBLE,需要注意的是,我們在RETURN語句後邊寫了一個SELECT語句,表明這個函數的返回結果就是根據這個查詢語句產生的,也就是返回了指定科目的平均成績。

存儲函數的調用

我們自定義的函數和系統內置函數的使用方式是一樣的,都是在函數名後加小括號()表示函數調用,調用有參數的函數時可以把參數寫到小括號裏邊。函數調用可以放到查詢列表或者作爲搜索條件,或者和別的操作數一起組成更復雜的表達式,我們現在來調用一下剛剛寫好的這個名爲avg_score的函數吧:

mysql> SELECT avg_score('母豬的產後護理');
+------------------------------------+
| avg_score('母豬的產後護理')        |
+------------------------------------+
|                                 73 |
+------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT avg_score('論薩達姆的戰爭準備');
+------------------------------------------+
| avg_score('論薩達姆的戰爭準備')          |
+------------------------------------------+
|                                    73.25 |
+------------------------------------------+
1 row in set (0.00 sec)

mysql>

通過調用函數的方式而不是直接寫查詢語句的方式來獲取某門科目的平均成績看起來就簡介多了。

查看和刪除存儲函數

如果我們想查看我們已經定義了多少個存儲函數,可以使用下邊這個語句:

SHOW FUNCTION STATUS [LIKE 需要匹配的函數名]

由於這個命令得到的結果太多,我們就不演示了哈,大家可以自己試試。如果我們想查看某個函數的具體是怎麼定義的,可以使用這個語句:

SHOW CREATE FUNCTION 函數名

比如:

mysql> SHOW CREATE FUNCTION avg_score\G
*************************** 1. row ***************************
            Function: avg_score
            sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
     Create Function: CREATE DEFINER=`root`@`localhost` FUNCTION `avg_score`(s VARCHAR(100)) RETURNS double
BEGIN
RETURN (SELECT AVG(score) FROM student_score WHERE subject = s);
END
character_set_client: utf8
collation_connection: utf8_general_ci
  Database Collation: utf8mb4_general_ci
1 row in set (0.00 sec)

如果需要刪除函數,可以使用:

DROP FUNCTION 函數名

比如我們來刪掉avg_score這個函數:

mysql> DROP FUNCTION avg_score;
Query OK, 0 rows affected (0.00 sec)

mysql>

函數體定義

上邊定義的avg_score的函數體裏邊只包含一條語句,如果只爲了節省書寫一條語句的時間而定義一個存儲函數,其實也不是很值~ 其實存儲函數的函數體中可以包含多條語句,並且支持一些特殊的語法來供我們使用

在函數內部定義局部變量

在函數體內可以定義局部變量,作用域是這個函數,但是定義變量前首先需要聲明,聲明方式如下:

DECLARE 變量名1, 變量名2, ... 數據類型 [DEFAULT 默認值];

例子:

mysql> delimiter $;
mysql> CREATE FUNCTION var_demo()
-> RETURNS INT
-> BEGIN
->     DECLARE c INT;
->     SET c = 5;
->     RETURN c;
-> END $
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;

我們定義了一個名叫var_demo而且不需要參數的函數,然後在函數體中聲明瞭一個名稱爲cINT類型的局部變量,之後我們調用SET語句爲這個局部變量賦值了整數5,並且把局部變量c當作函數結果返回

如果我們不對聲明的局部變量賦值的話,它的默認值就是NULL,當然我們也可以通過DEFAULT子句來顯式的指定局部變量的默認值,比如這樣:

mysql> delimiter $
mysql> CREATE FUNCTION var_default_demo()
-> RETURNS INT
-> BEGIN
->     DECLARE c INT DEFAULT 1;
->     RETURN c;
-> END $
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;
mysql>

在函數體中使用自定義變量

在函數體內,如果通過 SET @ 的方式定義變量,那麼在函數體外,這個變量的值仍然可以訪問到。

存儲函數的參數

在定義存儲函數的時候,可以指定多個參數,每個參數都要指定對應的數據類型,就像這樣:

參數名 數據類型

比如我們上邊編寫的這個avg_score函數:

CREATE FUNCTION avg_score(s VARCHAR(100))
RETURNS DOUBLE
BEGIN
    RETURN (SELECT AVG(score) FROM student_score WHERE subject = s);
END

這個函數只需要一個類型爲VARCHAR(100)參數,我們這裏給這個參數起的名稱是s,不過這個參數名不要和函數體語句中的其他變量名、列名啥的衝突,比如上邊的例子中如果把變量名s改爲爲subject,它就與下邊用到WHERE子句中的列名衝突了。

另外,函數參數不可以指定默認值,我們在調用函數的時候,必須顯式的指定所有的參數,並且參數類型也一定要匹配,比方說我們在調用函數avg_score時,必須指定我們要查詢的課程名,不然會報錯的:

mysql> select avg_score();
ERROR 1318 (42000): Incorrect number of arguments for FUNCTION xiaohaizi.avg_score; expected 1, got 0
mysql>

判斷語句的編寫

在存儲函數的函數體裏也可以使用判斷的語句,語法格式如下:

IF 表達式 THEN
    處理語句列表
[ELSEIF 表達式 THEN
    處理語句列表]
... # 這裏可以有多個ELSEIF語句
[ELSE
    處理語句列表]
END IF;

其中處理語句列表中可以包含多條語句,每條語句以分號;結尾就好。

舉一個包含IF語句的存儲函數的例子:

mysql> delimiter $
mysql> CREATE FUNCTION condition_demo(i INT)
-> RETURNS VARCHAR(10)
-> BEGIN
->     DECLARE result VARCHAR(10);
->     IF i = 1 THEN
->         SET result = '結果是1';
->     ELSEIF i = 2 THEN
->         SET result = '結果是2';
->     ELSEIF i = 3 THEN
->         SET result = '結果是3';
->     ELSE
->         SET result = '非法參數';
->     END IF;
->     RETURN result;
-> END $
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;
mysql>

循環語句

  • WHILE循環語句
WHILE 表達式 DO
    處理語句列表
END WHILE;

這個語句的意思是:如果滿足給定的表達式,則執行處理語句,否則退出循環。比如我們想定義一個計算從1nn個數的和(假設n大於0)的存儲函數,可以這麼寫:

mysql> delimiter $
mysql> CREATE FUNCTION sum_all(n INT UNSIGNED)
-> RETURNS INT
-> BEGIN
->     DECLARE result INT DEFAULT 0;
->     DECLARE i INT DEFAULT 1;
->     WHILE i <= n DO
->         SET result = result + i;
->         SET i = i + 1;
->     END WHILE;
->     RETURN result;
-> END $
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;
mysql>
  • REPEAT循環語句

    REPEAT循環語句和WHILE循環語句類似,只是形式上變了一下:

    REPEAT
        處理語句列表
    UNTIL 表達式 END REPEAT;
    

    先執行處理語句,再判斷表達式是否成立,如果成立則退出循環,否則繼續執行處理語句。與WHILE循環語句不同的一點是:WHILE循環語句先判斷表達式的值,再執行處理語句,REPEAT循環語句先執行處理語句,再判斷表達式的值,所以至少執行一次處理語句,所以如果sum_all函數用REPEAT循環改寫,可以寫成這樣:

    CREATE FUNCTION sum_all(n INT UNSIGNED)
    RETURNS INT
    BEGIN
        DECLARE result INT DEFAULT 0;
        DECLARE i INT DEFAULT 1;
        REPEAT
            SET result = result + i;
            SET i = i + 1;
        UNTIL i > n END REPEAT;
        RETURN result;
    END
    
  • LOOP循環語句

    這只是另一種形式的循環語句:

    LOOP
        處理語句列表
    END LOOP;
    

    不過這種循環語句有一點比較奇特,它沒有判斷循環終止的條件?那這個循環語句怎麼停止下來呢?其實可以把循環終止的條件寫到處理語句列表中然後使用RETURN語句直接讓函數結束就可以達到停止循環的效果,比方說我們可以這樣改寫sum_all函數:

    CREATE FUNCTION sum_all(n INT UNSIGNED)
    RETURNS INT
    BEGIN
        DECLARE result INT DEFAULT 0;
        DECLARE i INT DEFAULT 1;
        LOOP
            IF i > n THEN
                RETURN result;
            END IF;
            SET result = result + i;
            SET i = i + 1;
        END LOOP;
    END
    

如果我們僅僅想結束循環,而不是使用RETURN語句直接將函數返回,那麼可以使用LEAVE語句。不過使用LEAVE時,需要先在LOOP語句前邊放置一個所謂的標記,比方說我們使用LEAVE語句再改寫sum_all函數:

CREATE FUNCTION sum_all(n INT UNSIGNED)
RETURNS INT
BEGIN
    DECLARE result INT DEFAULT 0;
    DECLARE i INT DEFAULT 1;
    flag:LOOP
        IF i > n THEN
            LEAVE flag;
        END IF;
        SET result = result + i;
        SET i = i + 1;
    END LOOP flag;
    RETURN result;
END

可以看到,我們在LOOP語句前加了一個flag:這樣的東東,相當於爲這個循環打了一個名叫flag的標記,然後在對應的END LOOP語句後邊也把這個標記名flag給寫上了。在存儲函數的函數體中使用LEAVE flag語句來結束flag這個標記所代表的循環。

存儲過程

存儲過程定義

存儲函數存儲過程都屬於存儲例程,都是對某些語句的一個封裝。存儲函數側重於執行這些語句並返回一個值,而存儲過程更側重於單純的去執行這些語句。先看一下存儲過程的定義語句:

CREATE PROCEDURE 存儲過程名稱([參數列表])
BEGIN
    需要執行的語句
END

存儲函數最直觀的不同點就是,存儲過程的定義不需要聲明返回值類型

比如:

mysql> delimiter $
mysql> CREATE PROCEDURE t1_operation(
    ->     m1_value INT,
    ->     n1_value CHAR(1)
    -> )
    -> BEGIN
    ->     SELECT * FROM t1;
    ->     INSERT INTO t1(m1, n1) VALUES(m1_value, n1_value);
    ->     SELECT * FROM t1;
    -> END $
Query OK, 0 rows affected (0.00 sec)

mysql> delimiter ;
mysql>

我們建立了一個名叫t1_operation的存儲過程,它接收兩個參數,一個是INT類型的,一個是CHAR(1)類型的。這個存儲過程做了3件事兒,一件是查詢一下t1表中的數據,第二件是根據接收的參數來向t1表中插入一條語句,第三件是再次查詢一下t1表中的數據。

存儲過程調用

存儲函數執行語句並返回一個值,所以常用在表達式中。存儲過程偏向於執行某些語句,並不能用在表達式中,我們需要顯式的使用CALL語句來調用一個存儲過程

CALL 存儲過程([參數列表]);

比方說我們調用一下t1_operation存儲過程可以這麼寫:

mysql> CALL t1_operation(4, 'd');

查看或刪除存儲過程

存儲函數類似,存儲過程也有相似的查看和刪除語句,我們下邊只列舉一下相關語句,就不舉例子了。

查看當前數據庫中創建的存儲過程都有哪些的語句:

SHOW PROCEDURE STATUS [LIKE 需要匹配的存儲過程名稱]

查看某個存儲過程具體是怎麼定義的語句:

SHOW CREATE PROCEDURE 存儲過程名稱

刪除存儲過程的語句:

DROP PROCEDURE 存儲過程名稱

存儲過程中的語句

與存儲函數保持一致

存儲過程的參數前綴

存儲函數強大的一點是,存儲過程在定義參數的時候可以選擇添加一些前綴,就像是這個樣子:

參數類型 [IN | OUT | INOUT] 參數名 數據類型

可以看到可選的前綴有下邊3種:

前綴 實際參數是否必須是變量 描述
IN 用於調用者向存儲過程傳遞數據,如果IN參數在過程中被修改,調用者不可見。
OUT 用於把存儲過程運行過程中產生的數據賦值給OUT參數,存儲過程執行結束後,調用者可以訪問到OUT參數。
INOUT 綜合INOUT的特點,既可以用於調用者向存儲過程傳遞數據,也可以用於存放存儲過程中產生的數據以供調用者使用。
  • IN參數

    先定義一個參數前綴是IN的存儲過程p_in

    mysql> delimiter $
    mysql> CREATE PROCEDURE p_in (
    ->     IN arg INT
    -> )
    -> BEGIN
    ->     SELECT arg;
    ->     SET arg = 123;
    -> END $
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> delimiter ;
    mysql>
    

    這個p_in存儲過程只有一個參數arg,它的前綴是IN。這個存儲過程實際執行兩個語句,第一個語句是用來讀取參數arg的值,第二個語句是給參數arg賦值。我們調用一下p_in

    mysql> SET @a = 1;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> CALL p_in(@a);
    +------+
    | arg  |
    +------+
    |    1 |
    +------+
    1 row in set (0.00 sec)
    
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SELECT @a;
    +------+
    | @a   |
    +------+
    |    1 |
    +------+
    1 row in set (0.00 sec)
    
    mysql>
    

    我們定義了一個變量a並把整數1賦值賦值給它,因爲它是在客戶端定義的,所以需要加@前綴,然後把它當作參數傳給p_in存儲過程。從結果中可以看出,第一個讀取語句被成功執行,雖然第二個語句沒有報錯,但是在存儲過程執行完畢後,再次查看變量a的值卻並沒有改變,這也就是說:IN參數只能被用於讀取,對它賦值是不會被調用者看到的。

    另外,因爲我們只是想在存儲過程執行中使用IN參數,並不需要把執行過程中產生的數據存儲到它裏邊,所以其實在調用存儲過程時,將常量作爲參數也是可以的,比如這樣:

    mysql> CALL p_in(1);
    +------+
    | arg  |
    +------+
    |    1 |
    +------+
    1 row in set (0.00 sec)
    
    Query OK, 0 rows affected (0.00 sec)
    
    mysql>
    
  • OUT參數

    先定義一個前綴是OUT的存儲過程p_out

    mysql> delimiter $
    mysql> CREATE PROCEDURE p_out (
    ->     OUT arg INT
    -> )
    -> BEGIN
    ->     SELECT arg;
    ->     SET arg = 123;
    -> END $
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> delimiter ;
    mysql>
    

    這個p_out存儲過程只有一個參數arg,它的前綴是OUTp_out存儲過程也有兩個語句,一個用於讀取參數arg的值,另一個用於爲參數arg賦值,我們調用一下p_out

    mysql> SET @b = 2;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> CALL p_out(@b);
    +------+
    | arg  |
    +------+
    | NULL |
    +------+
    1 row in set (0.00 sec)
    
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SELECT @b;
    +------+
    | @b   |
    +------+
    |  123 |
    +------+
    1 row in set (0.00 sec)
    
    mysql>
    

    我們定義了一個變量b並把整數2賦值賦值給它,然後把它當作參數傳給p_out存儲過程。從結果中可以看出,第一個讀取語句並沒有獲取到參數的值,也就是說OUT參數的值默認爲NULL。在存儲過程執行完畢之後,再次讀取變量b的值,發現它的值已經被設置成123,說明在過程中對該變量的賦值對調用者是可見的!這也就是說:OUT參數只能用於賦值,對它賦值是可以被調用者看到的。

    另外,由於OUT參數只是爲了用於將存儲過程執行過程中產生的數據賦值給它後交給調用者查看,那麼在調用存儲過程時,實際的參數就不允許是常量!

  • INOUT參數

    知道了IN參數和OUT參數的意思,INOUT參數也就明白了,這種參數既可以在存儲過程中被讀取,也可以被賦值後被調用者看到,所以要求在調用存儲過程時實際的參數必須是一個變量

需要注意的是,如果我們不寫明參數前綴的話,默認的前綴是IN!

由於存儲過程可以傳入多個OUT或者INOUT類型的參數,所以我們可以在一個存儲過程中獲得多個結果,比如這樣:

mysql> delimiter $
mysql> CREATE PROCEDURE get_score_data(
    ->     OUT max_score DOUBLE,
    ->     OUT min_score DOUBLE,
    ->     OUT avg_score DOUBLE,
    ->     s VARCHAR(100)
    -> )
    -> BEGIN
    ->     SELECT MAX(score), MIN(score), AVG(score) FROM student_score WHERE subject = s INTO max_score, min_score, avg_score;
    -> END $
Query OK, 0 rows affected (0.02 sec)

mysql> delimiter ;
mysql>

我們定義的這個get_score_data存儲過程接受4個參數,前三個參數都是OUT參數,第四個參數沒寫前綴,默認就是IN參數。存儲過程的內容是將指定學科的最高分、最低分、平均分分別賦值給三個OUT參數。在這個存儲過程執行完之後,我們可以通過訪問這幾個OUT參數來獲得相應的最高分、最低分以及平均分:

mysql> CALL get_score_data(@a, @b, @c, '母豬的產後護理');
Query OK, 1 row affected (0.01 sec)

mysql> SELECT @a, @b, @c;
+------+------+------+
| @a   | @b   | @c   |
+------+------+------+
|  100 |   55 |   73 |
+------+------+------+
1 row in set (0.00 sec)

mysql>

存儲過程和存儲函數的不同點

存儲過程存儲函數非常類似,我們列舉幾個它們的不同點以加深大家的對這兩者區別的印象:

  • 存儲函數在定義時需要顯式用RETURNS語句標明返回的數據類型,而且在函數體中必須使用RETURN語句來顯式指定返回的值,存儲過程不需要。
  • 存儲函數只支持IN參數,而存儲過程支持IN參數、OUT參數、和INOUT參數。
  • 存儲函數只能返回一個值,而存儲過程可以通過設置多個OUT參數或者INOUT參數來返回多個結果。
  • 存儲函數執行過程中產生的結果集並不會被顯示到客戶端,而存儲過程執行過程中產生的結果集會被顯示到客戶端。
  • 存儲函數直接在表達式中調用,而存儲過程只能通過CALL語句來顯式調用。

遊標的使用

遊標的簡介

截止到現在爲止,我們只能使用SELECT ... INTO ...語句將一條記錄的各個列值賦值到多個變量裏,比如在前邊的get_score_data存儲過程裏有這樣的語句:

SELECT MAX(score), MIN(score), AVG(score) FROM student_score WHERE subject = s INTO max_score, min_score, avg_score;

但是如果某個查詢語句的結果集中有多條記錄的話,我們就無法把它們賦值給某些變量了~ 所以爲了方便我們去訪問這些有多條記錄的結果集,MySQL中引入了遊標的概念。

遊標既可以用在存儲函數中,也可以用在存儲過程中,我們下邊以存儲過程爲例來說明遊標的使用方式,它的使用大致分成這麼四個步驟:

  1. 創建遊標
  2. 打開遊標
  3. 通過遊標訪問記錄
  4. 關閉遊標

創建遊標

DECLARE 遊標名稱 CURSOR FOR 查詢語句;

我們定義一個存儲過程試一試:

CREATE PROCEDURE cursor_demo()
BEGIN
    DECLARE t1_record_cursor CURSOR FOR SELECT m1, n1 FROM t1;
END

這樣名叫t1_record_cursor的遊標就創建成功了。

如果存儲程序中也有聲明局部變量的語句,創建遊標的語句一定要放在局部變量聲明後頭。

打開和關閉遊標

在創建完遊標之後,我們需要手動打開和關閉遊標,語法也簡單:

OPEN 遊標名稱;

CLOSE 遊標名稱;

打開遊標意味着執行查詢語句,創建一個該查詢語句得到的結果集關聯起來的遊標關閉遊標意味着會釋放該遊標相關的資源,所以一旦我們使用完了遊標,就要把它關閉掉。當然如果我們不顯式的使用CLOSE語句關閉遊標的話,在該存儲過程的END語句執行完之後會自動關閉的。

CREATE PROCEDURE cursor_demo()
BEGIN
    DECLARE t1_record_cursor CURSOR FOR SELECT m1, n1 FROM t1;

    OPEN t1_record_cursor;

    CLOSE t1_record_cursor;
END

使用遊標獲取記錄

獲取記錄的語句長這樣:

FETCH 遊標名 INTO 變量1, 變量2, ... 變量n

這個語句的意思就是把指定遊標對應記錄的各列的值依次賦值給INTO後邊的各個變量。

遊標可以理解爲一個指針或者迭代器,如果你只訪問當前,會只提示當前的。放在循環裏面可以把所有的值都遍歷出來。

我們來繼續改寫一下cursor_demo存儲過程:

CREATE PROCEDURE cursor_demo()
BEGIN
    DECLARE m_value INT;
    DECLARE n_value CHAR(1);

    DECLARE t1_record_cursor CURSOR FOR SELECT m1, n1 FROM t1;

    OPEN t1_record_cursor;

    FETCH t1_record_cursor INTO m_value, n_value;
    SELECT m_value, n_value;

    CLOSE t1_record_cursor;
END $

我們來調用一下這個存儲過程:

mysql> CALL cursor_demo();
+---------+---------+
| m_value | n_value |
+---------+---------+
|       1 | a       |
+---------+---------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql>

額,奇怪,t1表裏有4條記錄,我們這裏只取出了第一條?是的,如果想獲取多條記錄,那需要把 FETCH 語句放到循環語句中,我們再來修改一下cursor_demo存儲過程:

CREATE PROCEDURE cursor_demo()
BEGIN
    DECLARE m_value INT;
    DECLARE n_value CHAR(1);
    DECLARE record_count INT;
    DECLARE i INT DEFAULT 0;

    DECLARE t1_record_cursor CURSOR FOR SELECT m1, n1 FROM t1;

    SELECT COUNT(*) FROM t1 INTO record_count;

    OPEN t1_record_cursor;

    WHILE i < record_count DO
        FETCH t1_record_cursor INTO m_value, n_value;
        SELECT m_value, n_value;
        SET i = i + 1;
    END WHILE;

    CLOSE t1_record_cursor;
END

這次我們又多使用了兩個變量,record_count表示t1表中的記錄行數,i表示當前遊標對應的記錄位置。每調用一次 FETCH 語句,遊標就移動到下一條記錄的位置。看一下調用效果:

mysql> CALL cursor_demo();
+---------+---------+
| m_value | n_value |
+---------+---------+
|       1 | a       |
+---------+---------+
1 row in set (0.00 sec)

+---------+---------+
| m_value | n_value |
+---------+---------+
|       2 | b       |
+---------+---------+
1 row in set (0.00 sec)

+---------+---------+
| m_value | n_value |
+---------+---------+
|       3 | c       |
+---------+---------+
1 row in set (0.00 sec)

+---------+---------+
| m_value | n_value |
+---------+---------+
|       4 | d       |
+---------+---------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql>

這回就把t1表中全部的記錄就都遍歷完了。

遍歷結束時的執行策略

上邊介紹的遍歷方式需要我們首先獲得查詢語句結構集中記錄的條數,也就是需要先執行下邊這條語句:

SELECT COUNT(*) FROM t1 INTO record_count;

我們之所以要獲取結果集中記錄的條數,是因爲我們需要一個結束循環的條件,當調用FETCH語句的次數與結果集中記錄條數相等時就結束循環。

其實在FETCH語句獲取不到記錄的時候會觸發一個事件,從而我們可以得知所有的記錄都被獲取過了,然後我們就可以去主動的停止循環。MySQL中響應這個事件的語句如下:

DECLARE CONTINUE HANDLER FOR NOT FOUND 處理語句;

只要我們在存儲過程中寫了這個語句,那麼在FETCH語句獲取不到記錄的時候,服務器就會執行我們填寫的處理語句。

處理語句可以是簡單的一條語句,也可以是由BEGIN ... END 包裹的多條語句。

我們接下來再來改寫一下cursor_demo存儲過程:

CREATE PROCEDURE cursor_demo()
BEGIN
    DECLARE m_value INT;
    DECLARE n_value CHAR(1);
    DECLARE not_done INT DEFAULT 1;

    DECLARE t1_record_cursor CURSOR FOR SELECT m1, n1 FROM t1;

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET not_done = 0;

    OPEN t1_record_cursor;

    flag: LOOP
        FETCH t1_record_cursor INTO m_value, n_value;
        IF not_done = 0 THEN
            LEAVE flag;
        END IF;
        SELECT m_value, n_value, not_done;
    END LOOP flag;

    CLOSE t1_record_cursor;
END

我們聲明瞭一個默認值爲1not_done變量和一個這樣的語句:

DECLARE CONTINUE HANDLER FOR NOT FOUND SET not_done = 0;

not_done變量的值爲1時表明遍歷結果集的過程還沒有結束,當FETCH語句無法獲取更多記錄時,就會觸發一個事件,從而導致MySQL服務器主動調用上邊的這個語句將not_done變量的值改爲0。另外,我們把原先的WHILE語句替換成了LOOP語句,直接在LOOP語句的循環體中判斷not_done變量的值,當它的值爲0時就主動跳出循環。

讓我們調用一下這個存儲過程看一下效果:

mysql> call cursor_demo;
+---------+---------+----------+
| m_value | n_value | not_done |
+---------+---------+----------+
|       1 | a       |        1 |
+---------+---------+----------+
1 row in set (0.05 sec)

+---------+---------+----------+
| m_value | n_value | not_done |
+---------+---------+----------+
|       2 | b       |        1 |
+---------+---------+----------+
1 row in set (0.05 sec)

+---------+---------+----------+
| m_value | n_value | not_done |
+---------+---------+----------+
|       3 | c       |        1 |
+---------+---------+----------+
1 row in set (0.06 sec)

+---------+---------+----------+
| m_value | n_value | not_done |
+---------+---------+----------+
|       4 | d       |        1 |
+---------+---------+----------+
1 row in set (0.06 sec)

Query OK, 0 rows affected (0.07 sec)

觸發器和事件

我們前邊說過存儲程序包括存儲例程存儲函數存儲過程)、觸發器事件,其中存儲例程是需要我們手動調用的,而觸發器事件MySQL服務器在特定情況下自動調用的,接下來我們分別看一下觸發器事件

觸發器

我們在對錶中的記錄做增、刪、改操作前和後都可能需要讓MySQL服務器自動執行一些額外的語句,這個就是所謂的觸發器的應用場景。

創建觸發器

定義觸發器的語句:

CREATE TRIGGER 觸發器名
{BEFORE|AFTER}
{INSERT|DELETE|UPDATE}
ON 表名
FOR EACH ROW
BEGIN
    觸發器內容
END

由大括號{}包裹並且內部用豎線|分隔的語句表示必須在給定的選項中選取一個值,比如{BEFORE|AFTER}表示必須在BEFOREAFTER這兩個之間選取一個。

其中{BEFORE|AFTER}表示觸發器內容執行的時機,它們的含義如下:

名稱 描述
BEFORE 表示在具體的語句執行之前就開始執行觸發器的內容
AFTER 表示在具體的語句執行之後纔開始執行觸發器的內容

{INSERT|DELETE|UPDATE}表示具體的語句,MySQL中目前只支持對INSERTDELETEUPDATE這三種類型的語句設置觸發器。

FOR EACH ROW BEGIN ... END表示對具體語句影響的每一條記錄都執行我們自定義的觸發器內容:

  • 對於INSERT語句來說,FOR EACH ROW影響的記錄就是我們準備插入的那些新記錄。
  • 對於DELETE語句和UPDATE語句來說,FOR EACH ROW影響的記錄就是符合WHERE條件的那些記錄(如果語句中沒有WHERE條件,那就是代表全部的記錄)。

如果觸發器內容只包含一條語句,那也可以省略BEGN、END這兩個詞兒。

因爲MySQL服務器會對某條語句影響的所有記錄依次調用我們自定義的觸發器內容,所以針對每一條受影響的記錄,我們需要一種訪問該記錄中的內容的方式,MySQL提供了NEWOLD兩個單詞來分別代表新記錄和舊記錄,它們在不同語句中的含義不同:

  • 對於INSERT語句設置的觸發器來說,NEW代表準備插入的記錄,OLD無效。
  • 對於DELETE語句設置的觸發器來說,OLD代表刪除前的記錄,NEW無效。
  • 對於UPDATE語句設置的觸發器來說,NEW代表修改後的記錄,OLD代表修改前的記錄。

舉個例子:

mysql> delimiter $
mysql> CREATE TRIGGER bi_t1
    -> BEFORE INSERT ON t1
    -> FOR EACH ROW
    -> BEGIN
    ->     IF NEW.m1 < 1 THEN
    ->         SET NEW.m1 = 1;
    ->     ELSEIF NEW.m1 > 10 THEN
    ->         SET NEW.m1 = 10;
    ->     END IF;
    -> END $
Query OK, 0 rows affected (0.02 sec)

mysql> delimiter ;
mysql>

我們對t1表定義了一個名叫bi_t1觸發器,它的意思就是在對t1表插入新記錄之前,對準備插入的每一條記錄都會執行BEGIN ... END之間的語句,NEW.列名表示當前待插入記錄指定列的值。現在t1表中一共有4條記錄:

mysql> SELECT * FROM t1;
+------+------+
| m1   | n1   |
+------+------+
|    1 | a    |
|    2 | b    |
|    3 | c    |
|    4 | d    |
+------+------+
4 rows in set (0.00 sec)

mysql>

我們現在執行一下插入語句並再次查看一下t1表的內容:

mysql> INSERT INTO t1(m1, n1) VALUES(5, 'e'), (100, 'z');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM t1;
+------+------+
| m1   | n1   |
+------+------+
|    1 | a    |
|    2 | b    |
|    3 | c    |
|    4 | d    |
|    5 | e    |
|   10 | z    |
+------+------+
6 rows in set (0.00 sec)

mysql>

這個INSERT語句影響的記錄有兩條,分別是(5, 'e')(100, 'z'),這兩條記錄將分別執行我們自定義的觸發器內容。很顯然(5, 'e')被成功的插入到了t1表中,而(100, 'z')插入到表中後卻變成了(10, 'z'),這個就說明我們的bi_t1觸發器生效了!

小貼士: 我們上邊定義的觸發器名bi_t1bibefore insert的首字母縮寫,t1是表名。雖然對於觸發器的命名並沒有什麼特殊的要求,但是習慣上還是建議大家把它定義我上邊例子中的形式,也就是bi_表名bd_表名bu_表名ai_表名ad_表名au_表名的形式。

上邊只是舉了一個對INSERT語句設置BEFORE觸發器的例子,對DELETEUPDATE操作設置BEFORE或者AFTER觸發器的過程是類似的,就不贅述了。

查看和刪除觸發器

查看當前數據庫中定義的所有觸發器的語句:

SHOW TRIGGERS;

查看某個具體的觸發器的定義:

SHOW CREATE TRIGGER 觸發器名;

刪除觸發器:

DROP TRIGGER 觸發器名;

觸發器使用注意事項

  • 觸發器內容中不能有輸出結果集的語句。
  • 觸發器內容中NEW代表記錄的列的值可以被更改,OLD代表記錄的列的值無法更改。
  • 在BEFORE觸發器中,我們可以使用SET NEW.列名 = 某個值的形式來更改待插入記錄或者待更新記錄的某個列的值,但是這種操作不能在AFTER觸發器中使用,因爲在執行AFTER觸發器的內容時記錄已經被插入完成或者更新完成了。
  • 如果我們的BEFORE觸發器內容執行過程中遇到了錯誤,那這個觸發器對應的具體語句將無法執行;如果具體的操作語句執行過程中遇到了錯誤,那與它對應的AFTER觸發器的內容將無法執行。

事件

有時候我們想讓MySQL服務器在某個時間點或者每隔一段時間自動地執行一些語句,這時候就需要去創建一個事件

創建事件

創建事件的語法如下:

CREATE EVENT 事件名
ON SCHEDULE
{
    AT 某個確定的時間點| 
    EVERY 期望的時間間隔 [STARTS datetime][END datetime]
}
DO
BEGIN
    具體的語句
END

事件支持兩種類型的自動執行方式:

  1. 在某個確定的時間點執行。

    比方說:

    CREATE EVENT insert_t1_event
    ON SCHEDULE
    AT '2019-09-04 15:48:54'
    DO
    BEGIN
        INSERT INTO t1(m1, n1) VALUES(6, 'f');
    END
    

    我們在這個事件中指定了執行時間是'2019-09-04 15:48:54',除了直接填某個時間常量,我們也可以填寫一些表達式:

    CREATE EVENT insert_t1
    ON SCHEDULE
    AT DATE_ADD(NOW(), INTERVAL 2 DAY)
    DO
    BEGIN
        INSERT INTO t1(m1, n1) VALUES(6, 'f');
    END
    

    其中的DATE_ADD(NOW(), INTERVAL 2 DAY)表示該事件將在當前時間的兩天後執行。

  2. 每隔一段時間執行一次。

    比方說:

    CREATE EVENT insert_t1
    ON SCHEDULE
    EVERY 1 HOUR
    DO
    BEGIN
        INSERT INTO t1(m1, n1) VALUES(6, 'f');
    END
    

    其中的EVERY 1 HOUR表示該事件將每隔1個小時執行一次。默認情況下,採用這種每隔一段時間執行一次的方式將從創建事件的事件開始,無限制的執行下去。我們也可以指定該事件開始執行時間和截止時間:

    CREATE EVENT insert_t1
    ON SCHEDULE
    EVERY 1 HOUR STARTS '2019-09-04 15:48:54' ENDS '2019-09-16 15:48:54'
    DO
    BEGIN
        INSERT INTO t1(m1, n1) VALUES(6, 'f');
    END
    

    如上所示,該事件將從’2019-09-04 15:48:54’開始直到’2019-09-16 15:48:54’爲止,中間每隔1個小時執行一次。

    表示事件間隔的單位除了HOUR,還可以用YEAR、QUARTER、MONTH、DAY、HOUR、 MINUTE、WEEK、SECOND、YEAR_MONTH、DAY_HOUR、DAY_MINUTE、DAY_SECOND、HOUR_MINUTE、HOUR_SECOND、MINUTE_SECOND這些單位,根據具體需求選用我們需要的時間間隔單位。

在創建好事件之後我們就不用管了,到了指定時間,MySQL服務器會幫我們自動執行的。

查看和刪除事件

查看當前數據庫中定義的所有事件的語句:

SHOW EVENTS;

查看某個具體的事件的定義:

SHOW CREATE EVENT 事件名;

刪除事件:

DROP EVENT 事件名;

事件使用注意事項

默認情況下,MySQL服務器並不會幫助我們執行事件,除非我們使用下邊的語句手動開啓該功能:

mysql> SET GLOBAL event_scheduler = ON;
Query OK, 0 rows affected (0.00 sec)

mysql>

event_scheduler其實是一個系統變量,它的值也可以在MySQL服務器啓動的時候通過啓動參數或者通過配置文件來設置event_scheduler的值。


END. By riba2534.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章