Skip to main content
Jkyo Chen Blog

MySQL 必知必会

了解 SQL #

使用 MySQL #

USE crashcourse;
SHOW DATABASES;
SHOW TABLES;
SHOW COLUMNS
FROM customers; // 显示表列
    DESCRIBE customers; // 上面这句的快捷方式
SHOW STATUS; // 用于显示广泛的服务器状态信息
SHOW CREATE DATABASE/ SHOW CREATE TABLE 分别用来创建特定数据库或表的 MySQL 语句
SHOW GRANTS; // 用来显示授予用户(所有用户或特定用户)的安全权限
SHOW ERRORS / SHOW WARNINGS // 用来显示服务器错误或警告消息

HELP SHOW // 显示允许的 SHOW 语句

检索数据 #

DISTINCT #

SQL SELECT DISTINCT vend_id FROM products; //指示 MySQL 只返回不同的值。 // 不能部分使用 DISTINCT 。DISTINCT 关键字应用于所有的列而不仅是前置它的列。 SELECT DISTINCT vend_id, prod_price; // 除非指定的两个列都不同,否则所有行都将被检索出来。

LIMIT #

```SQL SELECT pro_name FROM products LIMIT 5; // 指示 MySQL 返回不多于 5 行。

SELECT pro_name FROM products LIMIT 5,5; // 指示 MySQL 返回从行 5 开始的 5 行。第一个数为开始位置,第二个数为要检索的行数。

// 检索出来的第一行为行 0 而不是行 1 。 LIMIT 1,1; // 将检索出第二行而不是第一行。 // 如果没有足够的行,MySQL 将只返回它能返回的那么多行。

// MySQL 5 的 LIMIT 语法 LIMIT 4 OFFSET 3 // 从行 3 开始取 4 行,就像 LIMIT 3,4 一样 ```

完全限定的表名 #

SELECT products.pro_name
FROM crashcourse.products;

排序检索数据 #

SELECT prod_name
FROM products;

// 通过非选择列进行排序。

SELECT prod_id, prod_price, prod_name
FROM products
ORDER BY prod_price, prod_name; // 首先按照价格,然后再按名称排序

SELECT prod_id, prod_price, prod_name
FROM products
ORDER BY prod_price DESC; // 降序

SELECT prod_id, prod_price, prod_name
FROM products
ORDER BY prod_price DESC, prod_name; // DESC 关键字只应用到直接位于其前面的列名。

// 如果在多个列降序排序。必须对每个列指定 DESC 关键字

ASC 升序(默认)

// 在字典(dirtionary)排序顺序中,A 被视为与 a 相同,这是 MySQL 的默认行为。

SELECT prod_price
FROM products
ORDER BY prod_price DESC
LIMIT 1; // 找出列中最高的值

过滤数据 #

SELECT prod_name, prod_price
FROM products
WHERE prod_price = 2.50;

// SQL过滤,应用过滤

// 操作符
=, <>, !=, <, <=, >, >=, BETWEEN

// MySQL 在执行匹配时默认不区分大小写

// 将值与串类型的列进行比较,则需要限定引号。

SELECT prod_name, prod_price
FROM products
WHERE prod_price BETWEEN 5 AND 10;

// 在创建表时,表设计人员可以指定其中的列是否可以不包括值。在一个列不包含值时,称其为包含空值 NULL
// NULL 无值,它与字段包含 0 ,空字符或仅仅包含空格不同。
SELECT prod_name
FROM products
WHERE prod_price IS NULL;

数据过滤 #

AND 操作符 #

OR 操作符 #

SQL SELECT prod_name, prod_price FROM products WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >= 10;

IN 操作符 #

SELECT prod_name, prod_price
FROM products
WHERE vend_id
IN (1002, 1003)
ORDER BY prod_name;

// IN 操作符后跟由逗号分隔的合法值清单,整个清单必须括在圆括号中。

NOT 操作符 #

// NOT 用来否定后跟条件的关键字
SELECT prod_name, prod_price
FROM products
WHERE vend_id NOT IN (1002, 1003)
ORDER BY prod_name;

用通配符进行过滤 #

LIKE 操作符 #

百分号(%)通配符 #

// % 表示任何字符出现任意次数
SELECT prod_id, prod_name
FROM products
WHERE prod_name
LIKE 'jet%'; // 不区分大小写

// 根据 MySQL 的配置方式,搜索可以是区分大小写的。
// 通配符可在搜索模式中任意位置使用,并且可以使用多个通配符。
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE '%anvil%';

SELECT prod_name
FROM products
WHERE prod_name LIKE 's%e';

//% 代表搜索模式中给定位置的 0 个,1 个或多个字符。

WHERE prod_name LIKE '%'; // 不能匹配用值 NULL 作为产品名的行

下划线(_)通配符 #

// _ 总是匹配一个字符
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE '_ ton anvil';

使用通配符的技巧 #

用正则表达式进行搜索 #

基本字符匹配 #

SELECT prod_name
FROM products
WHERE prod_name REGEXP '10000'
ORDER BY prod_name; // 检索列 prod_name 包含文本 1000 的所有行。

SELECT prod_name
FROM products
WHERE prod_name REGEXP '.000'
ORDER BY prod_name; // . 表示匹配任意一个字符。

// MySQL中的正则表达式匹配(自版本 3.23.4 后),不区分大小写。
// 为区分大小写,可使用 BINARY 关键字。
WHERE prod_name REGEXP BINARY 'JetPack .000';

进行 OR 匹配 #

// 为搜索两个串之一,使用 |
SELECT prod_name
FROM products
WHERE prod_name REGEXP '1000|2000'
ORDER BY prod_name;

匹配几个字符之一 #

// 匹配任何单一字符,指定一组用[和]括起来的字符完成
SELECT prod_name
FROM products
WHERE prod_name REGEXP '[123] Ton'
ORDER BY prod_name;

// [] 是另一种形式的 OR 语句。
[123]Ton 是 [1|2|3]Ton 缩写。
// 但是需要 [] 来定义 OR 语句查找什么。
// 反例:
SELECT prod_name
FROM products
WHERE prod_name REGEXP '1|2|3 Ton'
ORDER BY prod_name; // '1' 或 '2' 或 '3 ton'

// 字符集合也可以被否定。在集合的开始处放置一个 ^ 即可。
[^123]

匹配范围 #

// 集合可用来定义要匹配的一个或多个字符。
// 匹配数字 0 到 9
[0123456789] 等价于 [0-9]
[a-z] // 匹配任意字母字符
SELECT prod_name
FROM products
WHERE prod_name REGEXP '[1-5] Ton'
ORDER BY prod_name;

匹配特殊字符 #

// 为了匹配特殊字符,必须用 \\ 为前导。
SELECT vend_name
FROM vendors
WHERE vend_name REGEXP '\\.'
ORDER BY vend_name; // 转译(escaping)

// '\\' 也用来引用元字符(具有特殊含义的字符)
'\\f' // 换页
'\\n' // 换行
'\\r' // 回车
'\\t' // 制表
'\\v' // 纵向制表

'\\\' // 匹配反斜杠(\)字符本身

匹配字符类 #

[: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] stick?\\)'
ORDER BY prod_name; // ? 匹配它前面的任何字符的 0 次或 1 次出现

SELECT prod_name
FROM products
WHERE prod_name REGEXP '[[:digit:]]{4}'

定位符 #

// 为了匹配特定位置的文本
^ // 文本的开始
$ // 文本的结尾
[[:<:]] // 词的开始
[[:>:]] // 词的结尾

SELECT prod_name
FROM products
WHERE prod_name REGEXP '^[0-9\\.]'
// 可以在不使用数据库表的情况下用 SELECT 来测试正则表达式
// REGEXP 检查总是返回 0 (没有匹配) 或 1 (匹配)
SELECT 'hello' REGEXP '[0-9]';

创建计算字段 #

拼接字段 #

// 多数 DBMS 使用 + 或 || 来实现拼接,MySQL 则使用 Concat() 函数来实现。

SELECT Concat(vend_name, ' (', vend_country, ')')
FROM vendors
ORDER BY vend_name;

SELECT Concat(RTrim(vend_name), ' (', RTrim(vend_country), ')'); // RTrim() 函数去掉值右边的所有空格
// LTrim() 去掉串左边的空格 Trim() 去掉串左右两边的空格

// 别名( alias )
SELECT Concat(vend_name, ' (', vend_country, ')') AS vend_title
FROM vendors
ORDER BY vend_name;

// 别名有时也称为 导出列 (derived column)

执行算术计算 #

SELECT prod_id,
       quantity,
       item_price,
       quantity * item_price AS expanded_price
FROM orderitems
WHERE order_num = 20005;

// 测试 省略 FROM 子句
SELECT 3 * 2
SELECT Trim('abc')
SELECT Now() // Now 函数返回当前日期和时间

使用数据处理函数 #

使用函数 #

文本处理函数 #

SELECT vend_name, Upper(vend_name) AS vend_name_upcase
FROM vendors
ORDER BY vend_name; // Upper() 将文本转换为大写

Left() // 返回串左边的字符
Length() // 返回串的长度
Locate() // 找出串的一个子串
Lower() // 将串转换为小写
LTrim() // 去掉串左边的空格
Right() // 返回串右边的字符
RTrim() // 去掉串右边的空格
Soundex() // 返回串的 SOUNDEX 值
SubString() // 返回子串的字符
Upper() // 将串转换为大写
SELECT cust_name, cust_contact
FROM customers
WHERE Soundex(cust_contact) = Soundex('Y Lie');
// 匹配所有发音类似于 Y. Lie 的联系名

日期和时间处理函数 #

addDate() // 增加一个日期(天,周等)
addTime() // 增加一个时间(时,分等)
CurDate() // 返回当前日期
CurTime() // 返回当前时间
Date() // 返回日期时间的日期部分
DateDiff() // 计算两个日期之差
Date_Add() // 高度灵活的日期运算函数
Date_Format() // 返回一个格式化的日期或时间串
Day() // 返回一个日期的天数部分
DayOfWeek() // 对于一个日期,返回对应的星期几
Hour() // 返回一个时间的小时部分
Minute() // 返回一个时间的分钟部分
Month() // 返回一个日期的月份部分
Now() // 返回当前日期和时间
Second() // 返回一个时间的秒部分
Time() // 返回一个日期时间的时间部分
Year() // 返回一个日期的年份部分
SELECT cust_id, order_num
FROM orders
WHERE order_date = '2005-09-01';

// 指示 MySQL 仅给出的日期与列中的日期部分进行比较,而不是将给出的日期与整个列值进行比较。
SELECT cust_id, order_num
FROM orders
WHERE Date(order_date) = '2005-09-01';

// 如果要的是日期,请使用 Date() ,即使你知道相应的列只包含日期也是如此。
// Date() 和 Time() 都是在 MySQL 4.1.1 中第一次引入的。

// 匹配月份
SELECT cust_id, order_num
FROM orders
WHERE Date(order_date) BETWEEN '2005-09-01' AND '2005-09-30';

// 或者
SELECT cust_id, order_num
FROM orders
WHERE Year(order_date) = 2005 AND Month(Order_date) = 9;

数值处理函数 #

Abs() // 返回一个数的绝对值
Cos() // 返回一个角度的余弦
Exp() // 返回一个数的指数值
Mod() // 返回除操作的余数
Pi() // 返回圆周率
Rand() // 返回一个随机数
Sin() // 返回一个角度的正弦
Sqrt() // 返回一个数的平方根
Tan() // 返回一个角度的正切

汇总数据 #

聚集函数 #

// 聚集函数 运行在行组上,计算和返回单个值的函数。

AVG() // 返回某列的平均值
COUNT() // 返回某列的行数
MAX() // 返回某列的最大值
MIN() // 返回某列的最小值
SUM() // 返回某列之和

// MySQL 还支持一系列的标准偏差聚集函数,但本书并未涉及这些内容。

AVG() 函数 #

SELECT AVG(products) AS avg_price
FROM products;

// AVG() 只用于单个列
// 忽略列值为 NULL 的行

COUNT() 函数 #

SELECT COUNT(*) AS num_cust
FROM customers;

// 只对具有电子邮件地址的客户计数,忽略指定列为空的行
SELECT COUNT(cust_email) AS num_cust
FROM customers;

MAX() MIN() 函数 #

// 返回指定列中的最大值。要求指定列名。
SELECT MAX(prod_price) AS max_price
FROM products;

SELECT MIN(prod_price) AS min_price
FROM products;

// MySQL 允许将它用来返回任意列中的最大值,包括返回文本列中的最大值。
// MAX(),MIN() 函数忽略列值为 NULL 的行

SUM() 函数 #

// 返回指定列值的和
SELECT SUM(quantity) AS items_ordered
FROM orderitems
WHERE order_num = 20005;

SELECT SUM(item_price * quantity) AS total_price
FROM orderitems
WHERE order_num = 20005;

// 利用标准的算术操作符,所有聚集函数都可用来执行多个列的计算
// SUM() 函数忽略列值为 NULL 的行

聚集不同值 #

SELECT AVG(DISTINCT prod_price) AS avg_price
FROM products
WHERE vend_id = 1003;

组合聚合函数 #

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;

// 在指定别名以包含某个聚集函数的结果时,不应该使用表中实际的列名。

分组数据 #

GROUP BY #

SELECT vend_id, COUNT(*) AS num_prods
FROM products
GROUP BY vend_id;
// WITH ROLLUP 关键字,可以得到每个分组以及每个分组汇总级别(针对每个分组)的值
SELECT vend_id, COUNT(*) AS num_prods
FROM products
GROUP BY vend_id WITH ROLLUP;

过滤分组 #

SELECT cust_id, COUNT(*) AS orders
FROM orders
GROUP BY cust_id
HAVING COUNT(*) >= 2;
SELECT vend_id, COUNT(*) AS num_prods
FROM products
WHERE prod_price >= 10
GROUP BY vend_id
HAVING COUNT(*) >= 2;

分组和排序 #

SELECT order_num, SUM(quantity * item_price) AS ordertotal
FROM orderitems
GROUP BY order_num
HAVING SUM(quantity * item_price) >= 50
ORDER BY ordertotal;

SELECT 子句顺序 #

SELECT // 要返回的列或表达式 // 是
FROM // 从中检索数据的表 // 仅在从表选择数据时使用
WHERE // 行级过滤 // 否
GROUP BY // 分组说明 // 仅在按组计算聚集时使用
HAVING // 组级过滤 // 否
ORDER BY // 输出排序顺序 // 否
LIMIT // 要检索的行数 // 否

使用子查询 #

利用子查询进行过滤 #

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_state,
       (SELECT COUNT(*)
        FROM orders
        WHERE orders.cust_id = customers.cust_id) AS orders
FROM customers
ORDER BY cust_name;

联结表 #

关系表 #

创建联结 #

SELECT vend_name, prod_name, prod_price
FROM vendors, products
WHERE vendors.vend_id = products.vend_id
ORDER BY vend_name, prod_name;

内部联结 #

// 等值联结( equijoin ),它基于两个表之间的相等测试。这种表称为内部联结
SELECT vend_name, prod_name, prod_price
FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id; // 和前面 SELECT 语句相同

// ANSI SQL 规范首选 INNER JOIN 语法。

联结多个表 #

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;

// MySQL 在运行时关联指定的每个表以处理联结。这种处理可能是非常耗费资源的。不要联结不必要的表。
// 联结的表越多,性能下降的越厉害。

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 操作的最有效的方法。
SELECT cust_name, cust_contact
FROM customers, orders, orderitems
ON customers.cust_id = orders.cust_id
  AND orders.order_num = orderitems.order_num
  AND prod_id = 'TNT2';

创建高级联结 #

使用表别名 #

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 prod_id = 'TNT2';

// 表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户机

使用不同类型的联结 #

自联结 #

SELECT prod_id, prod_name
FROM products
WHERE vend_id = (SELECT vend_id
                 FROM products
                 WHERE prod_id = 'DTNTR');
// 联结查询
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';

// 自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。
// 有时候处理联结远比处理子查询快得多,应该尝试两种方法,确定哪一种性能更好。

自然联结 #

// 自然联结 排除多次出现,使每个列只返回一次。
// 一般通过对表中使用通配符(SELECT * ),对所有其他表的列使用明确的子集完成的。
SELECT c.*, o.order_num, o.order_date, oi.prod_id, oi.quantity, oi.item_price
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 prod_id = 'FB';

外部联结 #

// 联结包含了那些在相关表中没有关联的行。

// 内联结
SELECT customers.cust_id, orders.order_num
FROM customers INNER JOIN orders
  ON customer.cust_id = orders.cust_id;

// 外部联结
SELECT customers.cust_id, orders.order_num
FROM customers LEFT OUTER JOIN orders
  ON customer.cust_id = orders.cust_id;

使用带聚集函数的联结 #

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;

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;

使用联结和联结条件 #

组合查询 #

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);

UNION 规则 #

包含或取消重复的行 #

对组合查询结果排序 #

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)
ORDER BY vend_id, prod_price;

// 不允许使用多条 ORDER BY 子句
// 上面这个 SQL ,MySQL 将用它来排序所有 SELECT 语句返回的所有结果。

全文本搜索 #

使用全文本搜索 #

启用全文本搜索支持 #

// 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=MyISM;

进行全文本搜索 #

// 在索引之后,使用两个函数 Match() 和 Against() 执行全文本搜索
// Match() 指定被搜索的列,Against() 指定要使用的搜索表达式。
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('rabbit');
SELECT note_text,
       Match(note_text) Against('rabbit') AS rank
FROM productnotes;
// 返回所有行,rank 包含全文本搜索计算出的等级值。
// 等级由 MySQL 根据行中词的数目,唯一词的数目,整个索引中词的总数以及包含该词的行的数目计算出来。
// 文本中词靠前的行的等级值比词靠后的行的等级值高。

使用查询扩展 #

// 没有查询扩展
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('anvils');

// 查询扩展
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('anvils', WITH QUERY EXPANSION);
// 查询扩展极大地增加了返回的行数,但这样做也增加了你实际上并不想要的行的数目

布尔文本搜索 #

// 使用 IN BOOLEAN MODE 但实际上没有指定布尔操作符
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('heavy' IN BOOLEAN MODE);

SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('heaby -rope*' IN BOOLEAN MODE);
// -rope* 明确地指示 MySQL 排除任何以 rope 开始的词,包括 ropes 的行

// MySQL 4.x 中使用上面的例子不返回任何行,这是 * 操作符处理中的一个错误。修改 -rope* 为 -ropes。
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('+rabbit +bait' IN BOOLEAN MODE);

SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('rabbit bait' IN BOOLEAN MODE);
// 搜索匹配至少包含一个词的行

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 

全文本搜索的使用说明 #

插入数据 #

数据插入 #

插入完整的行 #

INSERT INTO Customers
VALUES(NULL,
       'Pep E. LaPew',
       'Los Angeles',
       'CA',
       '90046',
       'USA',
       NULL,
       NULL);

// 必须给出每个列,各个列必须以它们在表定义中出现的次序填充。
// 某个列没有值,应该使用 NULL 值。
// 主键也为 NULL ,由 MySQL 自动增量。不能省略此列
// 不安全,尽量避免使用,上面的 SQL 高度依赖于表中列的定义次序,并且还依赖于其次序容易获得的消息。
INSERT INTO customers(cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country,
    cust_contact,
    cust_email)
VALUES('Pep E, LaPew',
    '100 Main Street',
    'Los Angeles',
    'CA',
    '90046',
    'USA',
    NULL,
    NULL);

// VALUES 必须以指定次序匹配指定的列名。
// 不需要设置主键的 NULL 值。

插入多个行 #

// 使用多条 INSERT 语句
// 或者
INSERT INTO customers(cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country)
VALUES(
    'Pep E. LaPew',
    '100 Main Street',
    'Los Angeles',
    'CA',
    '90046',
    'USA'
), (
    'M. Martian',
    '42 Galaxy Way',
    'New Yorw',
    'NY',
    '11213',
    'USA'
);
// 单条 INSERT 语句有多组值,每组值用一对圆括号括起来,用逗号分隔。
// 次技术可以提高数据库处理的性能,因为 MySQL 用单条 INSERT 语句处理多个插入比使用多条 INSERT 语句快。

插入检索出的数据 #

INSERT INTO customers(cust_id,
    cust_contact,
    cust_email,
    cust_name,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country)
SELECT cust_id,
    cust_contact,
    cust_email,
    cust_name,
    cust_address,
    cust_city,
    cuts_state,
    cust_zip,
    cust_country)
FROM custnew;
// 这里假设 cust_id 的值不重复,也可以省略
// INSERT 和 SELECT 语句中使用了相同的列名,但是不一定要求列名匹配
// 事实上,MySQL 甚至不关心 SELECT 返回的列名,它使用的是列的位置,SELECT 中的第一列(不管其列名)将用来填充表列中指定的第一个列
// 这对于从使用不同列名的表中导入数据是非常有用的。
// INSERT SELECT 中的 SELECT 语句可包含 WHERE 子句以过滤插入的数据。

更新和删除数据 #

更新数据 #

UPDATE customers
SET cust_email = 'elmer@fudd.com'
WHERE cust_id = 10005;

UPDATE customers
SET cust_name = 'THE Fudds'
    cust_email = 'elmer@fudd.com'
WHERE cust_id = 10005;
// 为了删除某个列的值,可设置它为 NULL(假定表定义允许 NULL 值)
UPDATE customers
SET cust_email = NULL,
WHERE cust_id = 10005;

删除数据 #

DELETE FROM customers
WHERE cust_id = 10006;
//  DELETE 删除整行而不是删除列,为了删除指定的列,使用 UPDATE 语句

更新和删除的指导原则 #

创建和操纵表 #

创建表 #

表创建基础 #

CREATE TABLE customers
(
    cust_id      int        NOT NULL AUTO_INCRMENT,
    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;
// 语句格式化  MySQL 语句中忽略空格
// 创建表时,指定的表名必须不存在。在表名后给出 `IF NOT EXISTS`

使用 NULL 值 #

CREATE TABLE orders
(
    order_num   int         NOT NULL AUTO_INCREMENT,
    order_date  datetime    NOT NULL,
    cuts_id     int         NOT NULL,
    PRIMARY KEY(order_num)
) ENGINE=InnoDB;
// NULL 为默认设置,如果不指定 NOT NULL,则认为指定的是 NULL

主键再介绍 #

CREATE TABLE orderitems
(
    order_num   int         NOT NULL,
    order_item  int         NOT NULL,
    prod_id     char(10)    NOT NULL,
    quantity    int         NOT NULL,
    item_price  decimal(8,2)NOT NULL,
    PRIMARY KEY(order_num, order_item)
) ENGINE=InnoDB;

使用 AUTO_INCREMENT #

// 使用
last_insert_id() // 函数获得这个值
SELECT last_insert_id()
// 此语句返回最后一个 AUTO_INCREMENT 值,然后可以将它用于后续的 MySQL 语句

指定默认值 #

// 插入行没有给出值,MySQL 允许指定此时使用的默认值
CREATE TABLE orderitems
(
    order_num   int     NOT NULL,
    order_item  int     NOT NULL,
    quantity    char(10)NOT NULL DEFAULT 1,
    item_price  decimal(8, 2)NOT NULL,
    PRIMARY KEY (order_num, order_item)
) ENGINE=InnDB;

引擎类型 #

更新表 #

// 增加一个列
ALTER TABLE vendors
ADD vend_phone CHAR(20);

// 删除添加的列
ALTER TABLE vendors
DROP COLUMN vend_phone;
ALTER TABLE orderitems
ADD CONSTRAINT fk_orderitems_orders
FOREIGN KEY (order_num)
REFERENCES orders (order_num);

ALTER TABLE orderitems
ADD CONSTRAINT fk_orderitems_products
FOREIGN KEY (prod_id)
REFERENCES products(prod_id);

ALTER TABLE orders
ADD CONSTRAINT fk_orders_customers
FOREIGN KEY (cust_id)
REFERENCES customers (cust_id);

ALTER TABLE products
ADD CONSTRAINT fk_products_vendors
FOREIGN KEY (vend_id)
REFERENCES vendors (vend_id);

删除表 #

DROP TABLE customers2;
// 删除表没有确认,也不能撤销,执行这条语句将永久删除该表

重命名表 #

// 重命名一个表
RENAME TABLE customers2 TO customers;

// 重命名多个表
RENAME TABLE backup_customer TO customers,
             backup_vendors TO vendors,
             backup_products TO products;

使用视图 #

视图 #

SELECT cust_name, cust_contact
FROM cusstomers, orders, orderitems
WHERE customers.cust_id = orders.cust_id
  AND orderitems.order_num = orders.order_num
  AND prod_id = 'TNT2';

SELECT cust_name, cust_contact
FROM productcustomers
WHERE prod_id = 'TNT2';

为什么使用视图 #

视图的规则和限制 #

视图 #

利用视图简化复杂的联结 #

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;

SELECT cust_name, cust_contact
FROM productcustomers
WHERE prod_id = 'TNT2';

// 它将指定的 WHERE 子句添加到视图查询中的已有 WHERE 子句中,以便正确过滤数据。

用视图重新格式化检索出的数据 #

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

CREATE VIEW vendorlocations AS
SELECT Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') AS vend_title
FROM vendors
ORDER BY vend_name;

SELECT *
FROM vendorlocations;

用视图过滤不想要的数据 #

CREATE VIEW customeremaillist AS
SELECT cust_id, cust_name, cust_email
FROM customers
WHERE cust_email IS NOT NULL;

使用视图与计算字段 #

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;

更新视图 #

使用存储过程 #

存储过程 #

为什么要使用存储过程 #

使用存储过程 #

执行存储过程 #

CALL productpricing(
        @pricelow,
        @pricehigh,
        @priceaverage
);

创建存储过程 #

CREATE PROCEDURE productpricing()
BEGIN
    SELECT Avg(prod_price) AS priceaverage
    FROM products;
END;
// MySQL 命令行也使用 ';' 字符,则它们最终不会成为存储过程的成分,这会使用存储过程中的 SQL 出现句法错误。
// 解决办法是临时更改命令行实用程序的语句分隔符。

DELIMITER //
CREATE PROCEDURE productpricing()
BEGIN
    SELECT Avg(prod_price) AS priceaverage
    FROM products;
END //

DELIMITER ;

// 除了  `\` 符号外,任何字符都可以用作语句分隔符。
// 使用
CALL productpricing();
// 执行刚创建的存储过程并显示返回的结果。因为存储过程是一种函数,所以存储过程名后需要有()符号。

删除存储过程 #

DROP PROCEDURE productpricing;
// 没有使用后面的(),只给出存储过程名。

DROP PROCEDURE IF EXISTS

使用参数 #

CREATE PROCEDURE productpricing(
    OUT pl DECIMAL(8, 2),
    OUT ph DECIMAL(8, 2),
    OUT pa DECIMAL(8, 2)
)
BEGIN
    SELECT Min(prod_price)
    INTO pl
    FROM products;
    SLECT Max(prod_price)
    INTO ph
    FROM products;
    SELECT Avg(prod_price)
    INTO pa
    FROM products;
END;
// 此存储过程接受 3 个参数。每个参数必须具有指定的类型,这里使用十进制值。
// 关键字 OUT 指出相应的参数用来从存储过程传出一个值(返回给调用者)。
// MySQL 支持 IN (传递给存储过程),OUT (从存储过程传出)和 INOUT (对存储过程传入和传出)类型的参数。
// 存储过程的代码位于 BEGIN 和 END 语句内。一系列 SELECT 语句,用来检索值,然后保存到相应的变量(通过指定 INTO 关键字)
CALL productpricing(
    @pricelow,
    @pricehigh,
    @priceaverage
);
// 所有 MySQl 变量都必须以 @ 开始。
// 在调用时,这条语句并不显示任何数据。它返回以后可以显示的变量。
SELECT @priceaverage;
SELECT @pricehigh, @pricelow, @priceaverage

CREATE PROCEDURE ordertotal(
    IN onumber INT,
    OUT ototal DECIMAL(8,2)
)
BEGIN
    SELECT Sum(item_price*quantity)
    FROM orderitems
    INTO ototal;
END;

CALL ordertotal(20005, @total);
SELECT @total;

建立智能存储过程 #

-- Name: ordertotal
-- Parameters: onumber = order number
--             taxable = 0 if not taxable, 1 if taxable
--             ototal = order total variable

CREATE PROCEDURE ordertotal(
    IN onumber INT,
    IN taxable BOOLEAN,
    OUT ototal DECIMAL(8,2)
) COMMENT 'Obtain order total, optionally adding tax'

-- Declare variable for total
DECLARE total DECIMAL(8,2);
-- Declare tax percentage
DECLARE taxrate INT DEFAULT 6;

-- Get the order total
SELECT Sum(item_price*quantity)
FROM orderitems
WHERE order_num = onumber
INTO total;

-- Is this taxable?
IF taxable THEN
    -- Yes, so add taxrate to the total
    SELECT total+(total/100*taxtate) INTO total;
END IF;
    -- And finally, save to out variable
    SELECT total INTO ototal;
END;

// -- 注释
// DECLARE 语句定义了两个局部变量。DECLARE 要求指定变量名和数据类型
// COMMENT 值,它不必需的,但如果给出,将在 SHOW PROCEDURE STATUS 的结果中显示。

CALL ordertotal(20005, 0, @total);
SELECT @total;
CALL ordertotal(20005, 1, @total);
SELECT @total;

检查存储过程 #

// 显示用来创建一个存储过程的 CREATE 语句,使用 SHOW CREATE PROCEDURE 语句
SHOW CREATE PROCEDURE ordertotal;

// 显示包括何时,由谁创建等详细信息的存储过程列表
SHOW PROCEDURE STATUS
// 限制过程状态的结果
SHOW PROCEDURE STATUS LIKE 'ordertotal';

使用游标 #

游标 #

使用游标 #

创建游标 #

// 游标用 DECLARE 语句创建。DECLARE 命名游标,并定义相应的 SELECT 语句,根据需要带 WHERE 和其他子句。
CREATE PROCEDURE processorders()
BEGIN
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
END;
// 存储过程处理完成后,游标就消失(因为它局限于存储过程)
// 在定义游标之后,可以打开它。

打开和关闭游标 #

// OPEN CURSOR 打开
OPEN ordernumbers;
// 在处理 OPEN 语句时执行查询,存储检索出的数据以供浏览和滚动。

// 游标处理完成后,应当使用如下语句关闭游标:
CLOSE ordernumbers;
// CLOSE 释放游标使用的所有内部内存和资源,因此在每个游标不再需要时都应该关闭。
// 如果你不明确关闭游标,MySQL 将会在到达 END 语句时自动关闭它。

CREATE PROCEDURE processorders()
BEGIN
    -- Declare the cursor
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;

    -- Open the cursor
    OPEN ordernumbers;

    -- Close the cursor
    CLOSE ordernumbers;
END;
// 这个存储过程声明打开和关闭一个游标。对检索出的数据什么也没做。

使用游标数据 #

// 从游标中检索单个行(第一行)
CREATE PROCEDURE processorders()
BEGIN

    -- Declare local variables
    DECLARE o INT;

    -- Declare the cursor
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;

    -- Open the cursor
    OPEN ordernumbers;

    -- Get order number
    FETCH numbers INTO o;

    -- Close the cursor
    CLOSE ordernumbers;
END
// FETCH 用来检索当前行的 order_num 列到一个名为 o 的局部变量中。对检索出的数据不做任何处理。
CREATE PROCEDURE processorders()
BEGIN

    -- Declare local variables
    DECLARE done BOOLEAN DEFAULT 0;
    DECLARE o INT;

    -- Declare the cursor
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;

    -- Declare the cursor
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;

    -- Declare continue handler
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done =1; // CONTINUE HANDLER 是在条件出现时被执行的代码。
    // SQLSTATE '02000' 是一个未找到条件,当 REPEAT 由于没有更多的行供循环而不能继续时,出现这个条件。

    -- Open the cursor
    OPEN ordernumbers;

    -- Loop through all rows
    REPEAT

        -- Get order number
        FETCH ordernumbers INTO o;
    -- End of loop
    UNTIL done END REPEAT;

    --Close the cursor
    CLOSE ordernumbers;
END
// 反复执行知道 done 为真
CREATE PROCEDURE processorders()
BEGIN

    -- Declare local variables
    DECLARE done BOOLEAN DEFAUULT 0;
    DECLARE o INT;
    DECLARE t DECIMAL(8,2);

    -- Declare the cursor
    DECLARE ordernumbers FROM orders;
    FOR
    SELECT order_num FROM order;

    -- Declare continue handler
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
    -- Create a table to store the results
    CREATE TABLE IF NOT EXISTS ordertotals(order_num INT, total DECIMAL(8,2));

    -- Open the cursor
    OPEN ordernumbers;

    -- Loop through all rows
    REPEAT

        -- Get order number
        FETCH ordernumbers INTO o;

        -- Get the total for this order
        CALL ordertotal(o, 1, t);

        -- Insert order and total into ordertotals
        INSERT INTO ordertotal(order_num, total)
        VALUES(o, t);

   -- End of loop
   UNTIL done END REPEAT;

   -- Close the cursor
   CLOSE ordernumbers;

END;

// CALL 执行另一个存储来计算每个订单的待税的合计(结果存储到t)

触发器 #

触发器 #

创建触发器 #

CREATE TRIGGER newproduct AFTER INSERT ON products
FOR EACH ROW SELECT 'Product added';
// 使用 INSERT 语句添加一行或多行到 products 中,你将看到对每个成功的插入,显示 Product added 消息。

删除触发器 #

DROP TRIGGER newproduct;
// 触发器不能更新或覆盖。为了修改一个触发器,必须先删除它,然后再重新创建。

使用触发器 #

INSERT 触发器 #

// 更好的新生成值的方法。
CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW SELECT NEW.order_num;
// 在插入一个新订单到 orders 表时,MySQL 生成一个新订单号并保存到 order_num 中。触发器从 NEW.order_num 取得这个值并返回它。
// 对于 orders 的每次插入使用这个触发器将总是返回新的订单号。

// 测试触发器
INSERT INTO orders(order_date, cust_id)
VALUES(Now(), 10001);
// 返回 order_num
// order_num 由 MySQL 自动生成,而现在 order_num 还自动被返回。

DELETE 触发器 #

CREATE TRIGGER deleteorder BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
    INSERT INTO archive_orders(order_num, orderr_date, cust_id)
    VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
END;
// BEFORE DELETE 触发器的优点(相对于 AFTER DELETE 触发器来说),如果由于某种原因,订单不能存档,DELETE 本身将被抛弃。

UPDATE 触发器 #

CREATE TRIGGER uppdatevendor BEFORE UPDATE ON vendors
FOR EACH ROW SET NEW.vend_state = Upper(NEW.vend_state);

关于触发器的进一步介绍 #

管理事务处理 #

事务处理 #

控制事务 #

// 标识事务的开始
START TRANSACTION

使用 ROLLBACK #

SELECT * FROM ordertotals;
START TRANSACTION;
DELETE FROM ordertotals;
SELECT * FROM ordertotals;
ROLLBACK;
SELECT * FROM ordertotals;

使用 COMMIT #

START TRANSACTION;
DELETE FROM orderitems WHERE order_num = 20010;
DELETE FROM orders WHERE order_num = 20010;
COMMIT;
// COMMIT 语句仅在不出错时写出更改。如果第一条 DELETE 起作用,但第二条失败,则 DELETE 不会提交。(实际上,它是被自动撤销的)

使用保留点 #

// 创建占位符
SAVEPOINT delete1;
ROLLBACK TO delete1;

更改默认的提交行为 #

// 默认的 MySQL 行为是自动提交所有更改。为指示 MySQl 不自动提交更改,需要使用以下语句:
SET autocommit=0;

全球化和本地化 #

字符集和校对顺序 #

使用字符集和校对顺序 #

SHOW CHARACTER SET;
// 显示所有可用的字符集以及每个字符集的描述和默认校对

SHOW COLLATION;
// 显示所有可用校对,以及它们适合的字符集。
// 区分大小写(由 _cs 表示)一次不区分大小写(由 _ci 表示)

// 通常系统管理在安装时定义一个默认的字符集和校对。也可以在数据库时,创建默认的字符集和校对
// 确定所用的字符集和校对
SHOW VARIABLES LIKE 'character%';
SHOW VARIABLES LIKE 'collation%';
CREATE TABLE mytable
(
    columnn1 INT,
    columnn2 VARCHAR(10)
) DEFAULT CHARACTER SET hebrew
  COLLATE hebrew_general_ci;

// MySQL 还允许对每个列设置它们
CREATE TABLE mytable
(
    columnn1 INT,
    columnn2 VARCHAR(10),
    column3  VARCHAR(10) CHARACTER SET latin1 COLLATE latin1_general_ci
) DEFAULT CHARACTER SET hebrew
  COLLATE hebrew_generral_ci;
// 对整个表和特定的列指定了 CHARACTER SET 和 COLLATE

// 如果创建表时不同的校对顺序排序特定的 SELECT 语句,可以在 SELECT 语句自身中进行
SELECT * FROM customers
ORDER BY lastname, firstname COLLATE latin1_general_cs;
// 使用 COLLATE 指定一个备用的校对顺序(为区分大小写的校对)

安全管理 #

访问控制 #

管理用户 #

USER mysql;
SELECT user FROM user;
// user 表,user 列,存储用户登录名。

创建用户账号 #

CREATE USER ben IDENTIFIED BY 'p@$$w0rd';
// 创建用户账号不一定需要口令
// IDENTIFIED BY 指定的口令为纯文本,MySQL 将在保存到 user 表之前对其进行加密。
RENAME USER ben TO bforta;
// 仅 MySQL 5或之后的版本支持 RENAME USER。
// 之前的 MySQL ,使用 UPDATE 直接更新 user 表

删除用户账号 #

DROP USER bforta;
// MySQL 5 以来,DROP USER 删除用户的账号和所有相关的账号权限。
// 5 之前,只能删除用户账号,不能删除相关的权限。所以需要先使用 REVOKE 删除与账号相关的权限,然后再用 DROP USER 删除账号

设置访问权限 #

// 看到赋予用户账号的权限
SHOW GRANTS FOR bforta;
// 显示为用户 bforta 有一个权限 `USAGE ON *.*` USAGE 表示根本没有权限。
GRANT SELECT ON crashcourse.* TO bforta;
// 允许用户在 crashcourse 数据库的所有表上使用 SELECT。

// 撤销特定的权限
REVOKE SELECT ON crashcourse.* FROM bforta;
ALL     //
ALTER
ALTER ROUTINE
CREATE
CREATE ROUTINE
CREATE TEMPORARY TABLES
CREATE USER
CREATE VIEW
DELETE
DROP
EXECUTE     // 使用 CALL 和 存储过程
FILE
GRANT OPTION
INDEX
INSERT
LOCK TABLES
PROCESS
RELOAD
REPLICATION CLIENT // 服务器位置的访问
REPLICATION SLAVE // 由复制从属使用
SELECT
SHOW DATABASES
SHOW VIEW
SHUTDOWN
SUPER
UPDATE
USAGE // 无访问权限
GRANT SELECT, INERT ON crashcourse.* TO bforta;

更改口令 #

SET PASSWORD FOR beforta = Password('n3w p@$$w0rd');
// SET PASSWORD 更新用户口令。新口令必须传递到 Password()函数进行加密。

// 设置自己的口令,不指定用户名,更新当前登录的用户的口令
SET PASSWORD = PASSword('n2w p@$$w0rd');

数据库维护 #

备份数据 #

进行数据库维护 #

// ANALYZE TABLE 用来检查表键是否正确
ANALYZE TABLE orders;

// CHECK TABLE 用来针对许多问题对表进行检查。在 MyISAM 表上还对索引进行检查。发现和修复问题
CHECK TABLE orders, orderitems;

// CHANGED 检查来自最后一次检查以来改动过的表
// EXTENDED 执行最彻底的检查
// FAST 只检查为正常关闭的表
// MEDIUM 检查所有被删除的链接并进行键检验
// QUICK 只进行快速扫描

诊断启动问题 #

查看日志文件 #

改善性能 #

附录A MySQL 入门 #

附录B 样例表 #

附录C MySQL 语句的语法 #

附录D MySQL 数据类型 #

附录E MySQL 保留字 #