这篇文章是我观看 Mosh 的 MySQL 完整版课程进行题目练习的记录,视频的话去 B 站搜索就能找到,数据库文件的话可以从这里下载。
目录
- 第二章
- 2- SELECT 子句
- 3- WHERE 子句
- 4- 逻辑运算符
- 5- IN 运算符
- 6- BETWEEN 运算符
- 7- LIKE 运算符
- 8- REGEXP 运算符(正则表达式)
- 9- IS NULL 运算符
- 10- ORDER BY 子句
- 11- LIMIT 子句
- 第三章
- 1- 内连接 INNER JOIN
- 2- 跨数据库连接
- 3- 自连接 SELF JOIN
- 4- 多表连接
- 5- 复合连接条件(复合主键)
- 6- 隐式连接(不建议使用)
- 7- 外连接 OUTER JOIN
- 8- 多表外连接
- 9- 自外连接
- 10- USING 子句
- 11- 自然连接(不建议使用)
- 12- 交叉连接
- 13- 联合 Unions
- 第四章
- 1- 列属性
- 2- 插入单行
- 3- 插入多行
- 4- 插入分层行
- 5- 创建表复制
- 6- 更新单行
- 7- 更新多行
- 8- 在 Update 中使用子查询
- 9- 删除行
- 10- 恢复(重建)数据库
- 第五章
- 1- 聚合函数 Aggregate Functions
- 2- GROUP BY 子句
- 3- HAVING 子句
- 4- ROLLUP 运算符
- 第六章
- 2- 子查询
- 3- IN 运算符
- 4- 子查询 vs 连接
- 5- ALL 关键字
- 6- ANY 关键字
- 7- 相关子查询(子查询中用到了父表)
- 8- EXISTS 运算符
- 9- SELECT 子句中的子查询
- 10- FROM 子句中的子查询(更推荐视图)
- 第七章
- 1- 数值函数
- 2- 字符串函数
- 3- 日期函数
- 4- 格式化日期函数
- 5- 计算日期与时间
- 6- 处理 NULL 值的函数
- 7- IF 函数
- 8- CASE 函数
- 第八章
- 1- 创建视图
- 2- 更新或删除视图
- 3- 可更新视图
- 4- WITH OPTION CHECK 子句
- 5- 视图总结
- 第九章
- 1- 什么是存储过程 Stored Procedures
- 2- 创建存储过程
- 3- 使用工作台创建存储过程
- 4- 删除存储过程
- 5- 参数 Parameters
- 6- 处理为 NULL 和多个参数
- 7- 验证参数
- 8- 输出参数(不建议使用)
- 9- 变量
- 10- 函数
- 第十一章
- 1- 事务 Transaction
- 2- 创建事务
- 3- 并发和锁定 Concurrency and Locking
- 4- 并发问题
- 5- 事务隔离级别 Transaction Isolation Level
- 6- 死锁 Deadlocks
第二章
2- SELECT 子句
题目:
解答:
USE sql_store;SELECT name, unit_price, unit_price * 1.1 AS new_price
FROM products
3- WHERE 子句
题目:
注:这是 2019 年的课程
解答:
SELECT *
FROM orders
WHERE order_date >= '2019-01-01'
4- 逻辑运算符
问题:
解答:
SELECT *
FROM order_items
WHERE order_id = 6 AND (quantity * unit_price) > 30
5- IN 运算符
问题:
解答:
SELECT *
FROM products
WHERE quantity_in_stock IN (49, 38, 72)
6- BETWEEN 运算符
问题:
解答:
SELECT *
FROM customers
WHERE birth_date BETWEEN '1990-01-01' AND '2000-01-01'
7- LIKE 运算符
问题:
解答:
SELECT *
FROM customers
WHERE address LIKE '%trail%' OR address LIKE '%avenue%'
SELECT *
FROM customers
WHERE phone LIKE '%9'
8- REGEXP 运算符(正则表达式)
问题:
解答:
SELECT *
FROM customers
WHERE first_name REGEXP 'ELKA|AMBUR'
SELECT *
FROM customers
WHERE last_name REGEXP 'EY$|ON$'
SELECT *
FROM customers
WHERE last_name REGEXP '^MY|SE'
SELECT *
FROM customers
WHERE last_name REGEXP 'B[RU]'
9- IS NULL 运算符
问题:
解答:
SELECT *
FROM orders
WHERE shipper_id IS NULL
10- ORDER BY 子句
问题:
返回 order_id 为 2 的表,且按总价格降序排列
解答:
SELECT order_id, product_id, quantity, unit_price
FROM order_items
WHERE order_id = 2
ORDER BY quantity * unit_price DESC
11- LIMIT 子句
问题:
返回 points 最高的三个顾客
解答:
SELECT *
FROM customers
ORDER BY points DESC
LIMIT 3
第三章
1- 内连接 INNER JOIN
问题:连接 order_items 和 products 表,返回 order_id、product_id 和order_items 中的 quantity、unit_price
解答:JOIN 即为 INNER JOIN,默认就是内连接
SELECT order_id, oi.product_id, quantity, oi.unit_price
FROM order_items AS oi
JOIN products AS pON oi.product_id = p.product_id
2- 跨数据库连接
USE sql_store;SELECT *
FROM order_items AS oi
JOIN sql_inventory.products AS pON oi.product_id = p.product_id
使用 USE 选中一个数据库作为当前数据库,然后对于不在此数据库中的表,例如另一个数据库 sql_inventory 中的 products 表,可以通过加前缀进行连接。
3- 自连接 SELF JOIN
USE sql_hr;SELECTe.employee_id,e.first_name,m.first_name AS Manager
FROM employees AS e
JOIN employees AS mON e.reports_to = m.employee_id
employees 表当中的每一个员工都有员工编号,而员工的直属上司也有编号,可以通过自连接找到每一个编号员工的名字以及其直属上司的名字。自连接时要给表起不同的别名,且要在查询的列前面加前缀。
4- 多表连接
问题:将 sql_invoicing 数据库里面的 payments 表与 payment_methods、clients 表进行连接。
解答:
USE sql_invoicing;SELECT p.date,p.amount,pm.name,c.name
FROM payments AS p
JOIN payment_methods AS pmON p.payment_method = pm.payment_method_id
JOIN clients AS cON p.client_id = c.client_id
5- 复合连接条件(复合主键)
当表中存在复合主键时,连接表就需要多个条件语句了(AND),例如 order_items 表的主键就是由 order_id 和 product_id 组成的:
如果想将 order_items 与 order_item_notes 连接起来,代码如下:
USE sql_store;SELECT *
FROM order_items AS oi
JOIN order_item_notes AS oinON oi.order_id = oin.order_idAND oi.product_id = oin.product_id
6- 隐式连接(不建议使用)
对于本章第一节的连接,我们也可以用 WHERE 子句实现:
SELECT order_id, oi.product_id, quantity, oi.unit_price
FROM order_items AS oi, products AS p
WHERE oi.product_id = p.product_id
虽然可以得到与使用 JOIN 一样的功能,但是不建议使用,因为很容易忘记使用 WHERE 子句,不小心得到交叉连接。而使用 JOIN 则规定一定要写 ON 后面的条件,否则会得到语法错误。
7- 外连接 OUTER JOIN
问题:连接 product 表和 order_items 表,返回 product_id、name、quantity, 但是要包含所有的产品,无论这个产品是否有 order
解答:LEFT JOIN 和 RIGHT JOIN 即为 LEFT OUTER JOIN 和 RIGHT OUTER JOIN,默认就是外连接。LEFT JOIN 就是把左边的表的所有内容进行返回,而不管它是否满足 ON 后面的条件;RIGHT JOIN 就是把右边的表的所有内容进行返回,也是不管它是否满足 ON 后面的条件。
SELECT p.product_id, p.name, oi.quantity
FROM products AS p
LEFT JOIN order_items AS oiON p.product_id = oi.product_id
8- 多表外连接
问题:
解答:对于 customers 表和 order_statuses 表,内连接和外连接是一样的,因为每一个 order 都肯定有 customer 和 status
SELECT o.order_date,o.order_id,c.first_name,s.name AS shipper,os.name AS status
FROM orders AS o
LEFT JOIN customers AS c # 可用 JOINON o.customer_id = c.customer_id
LEFT JOIN order_statuses AS os # 可用 JOINON o.status = os.order_status_id
LEFT JOIN shippers AS sON o.shipper_id = s.shipper_id
9- 自外连接
在自连接中,我们可以找到每一个编号员工的名字以及其直属上司的名字,但是,缺少了直属上司的编号,这是最高的直属上司自己没有任何的上司,所以在自(内)连接中会被忽略,改为使用自外连接就可以得到这个条目。
USE sql_hr;SELECTe.employee_id,e.first_name,m.first_name AS Manager
FROM employees AS e
LEFT JOIN employees AS mON e.reports_to = m.employee_id
10- USING 子句
问题:从 sql_invoicing 库的 payments 表中查询如下结果
解答:连接两个表的关联列如果名字相同,就可以用 USING,如果名字不相同就还得用 ON
USE sql_invoicing;SELECTp.date,p.client_id AS client,p.amount,pm.name
FROM payments AS p
JOIN clients AS cUSING (client_id)
JOIN payment_methods AS pmON p.payment_method = pm.payment_method_id
11- 自然连接(不建议使用)
我们知道,连接两个表的关联列如果名字相同,就可以用 USING。
SELECTo.order_id,c.first_name
FROM orders AS o
JOIN customers AS cUSING (customer_id)
有一种比用 USING 更加偷懒的方法,是让数据库软件自己找出具有相同名字的列作为关联列,这就是所谓的自然连接。
SELECTo.order_id,c.first_name
FROM orders AS o
NATURAL JOIN customers AS c
但是这样会导致我们无法控制数据库的行为,所以不建议使用。
12- 交叉连接
问题:
解答:交叉连接就是返回两个表中的所有内容,所以没有 ON 条件语句。
显式就是用一个表 CROSS JOIN 另一个表,隐式就是 FROM 多个表
显式
SELECTp.name AS product,s.name AS shipper
FROM products AS p
CROSS JOIN shippers AS s
ORDER BY product
隐式
SELECTp.name AS product,s.name AS shipper
FROM products AS p, shippers AS s
ORDER BY product
13- 联合 Unions
问题:从 customers 表中查询下列结果,其中 type 分为青铜、白银和黄金,对应 points 为 2000 以下,2000 - 3000 和 3000 以上,结果按姓氏排序
解答:
SELECTcustomer_id,first_name,points,'Bronze' AS type
FROM customers
WHERE points < 2000
UNION
SELECTcustomer_id,first_name,points,'Silver' AS type
FROM customers
WHERE points BETWEEN 2000 AND 3000
UNION
SELECTcustomer_id,first_name,points,'Gold'
FROM customers AS type
WHERE points > 3000
ORDER BY first_name
第四章
1- 列属性
点击表格的第二个图标,可以看到表格的各种属性
例如,customers 表中,PK 表示主键,所以 customer_id 就是主键,且它的 AI 是勾选的,意思是自动递增,即新增(插入)一行时会自动填入加一的值。NN 表示 None NULL 即不能为 NULL 值。Datatype 是数据类型,VARCHAR(50) 表示可变长的字符串,最长为 50;CHAR(2) 表示不可变长的字符串,长度固定为 2
2- 插入单行
INSERT INTO 表名 (列名,如果全选就忽略)
VALUES (第1行列值),(第2行列值),(第3行列值)
VALUES 实际上是一个函数,负责将行转换为表,所以如果使用了子查询 SELECT 语句,就不需要 VALUES 了。
3- 插入多行
问题:
解答:
INSERT INTO products (name, quantity_in_stock, unit_price)
VALUES ('Product1', 19, 2.2),('Product2', 20, 2.2),('Product3', 21, 2.2)
4- 插入分层行
如何同时向 orders 表和 order_items 表进行插入数据呢?特别地,order_items 表具有复合主键。
方法是先向 orders 表插入一行数据,order_id 因为是自动递增的,所以没有指定其插入值;然后借助函数 last_insert_id() ,我们可以得到最新的 order_id,然后再利用这个 order_id 在 order_items 表中进行插入。
INSERT INTO orders (customer_id, order_date, status)
VALUES (1, '2022-04-24', 1);INSERT INTO order_items # 列全选
VALUES(last_insert_id(), 1, 1, 2.95),(last_insert_id(), 2, 1, 3.95)
5- 创建表复制
问题:在 sql_invoicing 库中,创建 invoices 表的一个副本名为 invoices_archived,用 client_name 代替 client_id 列,且只保留 payment_date 非空的行。
解答:
CREATE TABLE invoices_archived AS
SELECT i.invoice_id, i.number, c.name, i.invoice_total, i.payment_total, i.invoice_date, i.due_date, i.payment_date
FROM invoices AS i
JOIN clients AS cUSING (client_id)
WHERE i.payment_date IS NOT NULL
6- 更新单行
UPDATE 表名
SET 列名1 = 新值1, 列名2 = 新值2
WHERE 选中某一行
如果某一列有默认值,赋值的时候可以写 DEFAULT 表示新值为默认值。
7- 更新多行
MySQL 的 Workbench 默认是不能更新多行,需要点击菜单栏中的 Edit 的 Preference,然后点击 SQL Editor,将其最下面的勾选框取消勾选,然后重启软件即可。
问题:
解答:
USE sql_store;UPDATE customers
SET points = points + 50
WHERE birth_date < '1990-01-01'
8- 在 Update 中使用子查询
问题:对于 sql_store 库里的 orders 表中,customer_id 对应 customers 表中 points 大于 3000 的行,更新其 comments 为 ‘Gold Customer’
解答:
UPDATE orders
SET comments = 'Gold Customer'
WHERE customer_id IN (SELECT customer_idFROM customersWHERE points > 3000
)
9- 删除行
DELETE FROM 表名
WHERE 选中某一或多行(子查询)
如果没有写 WHERE 子句,就会删除整个表,非常危险。
10- 恢复(重建)数据库
经过一系列更改后数据库发生了变化,为了后面的课程能继续使用相同的数据库,需要恢复数据库。点击菜单栏的 File 的 Open SQL Script,选择 create-databases.sql 文件,然后执行它,课程的数据库就重建好了。
第五章
1- 聚合函数 Aggregate Functions
MAX(列名) 求最大值
MIN(列名) 求最小值
AVG(列名) 求平均值
SUM(列名) 求总和
COUNT(列名) 算行数
注意聚合函数只计算非空值 Not NULL,所以对于 COUNT 函数,如果要算表中有多少行,就使用 COUNT(*)
而不是 COUNT(列名)
;如果要去除重复行,就写 DISTINCT 列名
问题:从 sql_invoicing 库的 invoices 表生成以下查询
注:第三列和第四列的结果可以与图片不同
解答:
USE sql_invoicing;SELECT 'First half of 2019' AS date_range, SUM(invoice_total) AS total_sales, SUM(payment_total) AS total_payments, SUM(invoice_total - payment_total) AS what_we_expect
FROM invoices
WHERE invoice_date BETWEEN '2019-01-01' AND '2019-06-30'
UNION
SELECT 'Second half of 2019' AS date_range, SUM(invoice_total) AS total_sales, SUM(payment_total) AS total_payments, SUM(invoice_total - payment_total) AS what_we_expect
FROM invoices
WHERE invoice_date BETWEEN '2019-07-01' AND '2019-12-31'
UNION
SELECT 'Total' AS date_range, SUM(invoice_total) AS total_sales, SUM(payment_total) AS total_payments, SUM(invoice_total - payment_total) AS what_we_expect
FROM invoices
WHERE invoice_date BETWEEN '2019-01-01' AND '2019-12-31'
2- GROUP BY 子句
问题:从 sql_invoicing 库的 payments 和 invoices 表生成以下查询,要求根据 date 和 payment_method 的组合进行分组
解答:
SELECT p.date AS date,pm.name AS payment_method,SUM(amount) AS total_payments
FROM payments AS p
JOIN payment_methods AS pmON p.payment_method = pm.payment_method_id
GROUP BY date, payment_method
ORDER BY date
3- HAVING 子句
WHERE 子句和 HAVING 子句有两点区别:1、前者在行分组前筛选数据,后者在行分组后筛选数据;2、前者可以使用所有的列,后者只能使用 SELECT 子句包含的列。
问题:从 sql_store 库,查询所在 state 为 VA 且消费超过 100 的顾客
解答:
SELECT c.customer_id,c.first_name,c.last_name,SUM(oi.quantity * oi.unit_price) AS total_sales
FROM customers AS c
JOIN orders AS oUSING (customer_id)
JOIN order_items AS oiUSING (order_id)
WHERE state = 'VA'
GROUP BY c.customer_id,c.first_name,c.last_name
HAVING total_sales > 100
4- ROLLUP 运算符
问题:从 sql_invoicing 库的 payments 表生成以下查询
解答:
SELECT pm.name AS payment_method,SUM(p.amount) AS total
FROM payments AS p
JOIN payment_methods AS pmON p.payment_method = pm.payment_method_id
GROUP BY pm.name WITH ROLLUP # 不能用 payment_method
注意如果使用了 ROLLUP,GROUP BY 的列就不能用别名(payment_method),一定要用真名(pm.name),否则会出错。
第六章
2- 子查询
问题:
解答:
USE sql_hr;SELECT *
FROM employees
WHERE salary > (SELECT AVG(salary)FROM employees
)
3- IN 运算符
问题:
解答:
USE sql_invoicing;SELECT *
FROM clients
WHERE client_id NOT IN (SELECT DISTINCT client_idFROM invoices
)
4- 子查询 vs 连接
问题:用子查询和连接两种方法完成以下查询
解答:使用子查询还是连接的可读性更高,具体问题具体分析。
子查询
USE sql_store;SELECT DISTINCT customer_id, first_name, last_name
FROM customers
WHERE customer_id IN (SELECT customer_idFROM ordersWHERE order_id IN (SELECT order_idFROM order_itemsWHERE product_id = 3)
)
连接(这题更推荐用)
USE sql_store;SELECT DISTINCT customer_id, first_name, last_name
FROM customers
JOIN ordersUSING (customer_id)
JOIN order_itemsUSING (order_id)
WHERE order_items.product_id = 3
5- ALL 关键字
问题:
解答:大于 MAX 值 <=> 大于 ALL 值
USE sql_invoicing;SELECT *
FROM invoices
WHERE invoice_total > (SELECT MAX(invoice_total)FROM invoicesWHERE client_id = 3
)
USE sql_invoicing;SELECT *
FROM invoices
WHERE invoice_total > ALL (SELECT invoice_totalFROM invoicesWHERE client_id = 3
)
6- ANY 关键字
问题:
解答:IN <=> 等于 ANY
SELECT *
FROM clients
WHERE client_id IN (SELECT client_idFROM invoicesGROUP BY client_idHAVING COUNT(*) >= 2
)
SELECT *
FROM clients
WHERE client_id = ANY(SELECT client_idFROM invoicesGROUP BY client_idHAVING COUNT(*) >= 2
)
7- 相关子查询(子查询中用到了父表)
普通的子查询只执行一次,返回结果,然后执行父查询;相关子查询则会在父表中的每一行都执行一次,所以执行用时更长。
问题:
解答:
USE sql_invoicing;SELECT *
FROM invoices AS i
WHERE invoice_total > (SELECT AVG(invoice_total)FROM invoicesWHERE client_id = i.client_id
)
8- EXISTS 运算符
如果 IN 运算符后面跟着一个子查询,而子查询返回的表非常大,此时就可以使用 EXISTS 运算符代替它。EXIST 运算符会在父表中逐行判断,是否存在符合的列,如果存在则返回,所以是一次返回一行一列或一行多列,不会返回多行一列或多行多列(非常大)。
问题:
解答:
IN 运算符
USE sql_store;SELECT *
FROM products AS p
WHERE product_id NOT IN (SELECT product_idFROM order_items
)
EXISTS 运算符
USE sql_store;SELECT *
FROM products AS p
WHERE NOT EXISTS (SELECT product_idFROM order_itemsWHERE product_id = p.product_id
)
9- SELECT 子句中的子查询
要从 invoices 表中实现以上的查询,代码如下:
USE sql_invoicing;SELECT invoice_id,invoice_total,(SELECT AVG(invoice_total)FROM invoices) AS invoice_average,invoice_total - (SELECT invoice_average) AS difference
FROM invoices
有两个点要注意:1、必须使用子查询,而不能直接使用聚合函数 AVG,这是因为直接聚合只会返回一行,如图所示:
子查询聚合函数的作用类似于聚合得到一个平均值,然后每行都是这个值:
2、给第三列的平均值起了别名以后,不能直接在第四列使用,否则会报错:
再写一个一样的子查询又比较啰嗦,正确的做法是直接 SELECT 别名然后加括号即可 (SELECT invoice_average)
问题:
解答:
SELECT c.client_id,c.name,(SELECT SUM(invoice_total)FROM invoicesWHERE client_id = c.client_id) AS total_sales,(SELECT AVG(invoice_total)FROM invoices) AS average,(SELECT total_sales) - (SELECT average) AS difference
FROM clients AS c
原本以为要用连接 JOIN,但实际上用了子查询就不需要用连接了。
10- FROM 子句中的子查询(更推荐视图)
子查询可以用于各种地方,FROM 子句也不例外,如下代码:
SELECT *
FROM ( SELECT c.client_id,c.name,(SELECT SUM(invoice_total)FROM invoicesWHERE client_id = c.client_id) AS total_sales,(SELECT AVG(invoice_total)FROM invoices) AS average,(SELECT total_sales) - (SELECT average) AS differenceFROM clients AS c
) AS sales_summary
WHERE total_sales IS NOT NULL
可以得到以下的查询,去除了 total_sales 为空的行:
注意代码中必须给子查询一个别名,否则会报错。
第七章
1- 数值函数
# 四舍五入函数
ROUND(5.745) # 6
ROUND(5.745, 2) # 5.75
# 截断函数
TRUNCATE(5.745, 2) # 5.74
# 取上限函数
CEILING(5.745) # 6
# 取下限函数
FLOOR(5.745) # 5
# 绝对值函数
ABS(-5.745) # 5.745
2- 字符串函数
# 求字符串长度
LENGTH('Sky') # 3
# 变大写
UPPER('Sky') # 'SKY'
# 变小写
LOWER('Sky') # 'sky'
# 去掉左边空格
LTRIM(' Sky') # 'Sky'
# 去掉右边空格
RTRIM('Sky ') # 'Sky'
# 去掉所有空格
TRIM(' Sky ') # 'Sky'
# 从左边开始索引(起始位置为 1)
LEFT('Computer', 3) # 'Com'
# 从右边开始索引
RIGHT('Computer', 3) # 'ter'
# 用(起始位置 + 子串长度)索引
SUBSTRING('Computer', 3, 4) # 'mput'
# 寻找子串,若不存在则返回 0
LOCATE('p', 'Computer') # 4
LOCATE('a', 'Computer') # 0
# 替换子串
REPLACE('Computer', 'ter', 'ted') # 'Computed'
# 连接字符串
CONCAT('apple', ' ', 'tree') # 'apple tree'
3- 日期函数
如图所示,NOW 函数可返回当前的日期+时间,CURDATE 返回当前日期,CURTIME 返回当前时间;对日期使用 YEAR 可获得年,使用 MONTH 可获得月,以此类推。
4- 格式化日期函数
时间为 2022/5/5 16:58,可以用格式化日期函数自定义时间的显示格式。
5- 计算日期与时间
DATE_ADD(NOW(), INTERVAL 1 YEAR) # '2023-05-05 17:08:53'DATE_ADD(NOW(), INTERVAL 1 MONTH) # '2022-06-05 17:08:53'DATE_ADD(NOW(), INTERVAL 1 DAY) # '2022-05-06 17:08:53'DATE_SUB(NOW(), INTERVAL 1 YEAR) # '2021-05-05 17:08:53'DATE_SUB(NOW(), INTERVAL 1 MONTH) # '2022-04-05 17:08:53'DATE_SUB(NOW(), INTERVAL 1 DAY) # '2022-05-04 17:08:53'DATEDIFF(NOW(), '2023-01-01') # '124'
DATE_ADD 与 DATE_SUB 函数可以对某个日期进行加或减(可以为负数),DATEDIFF 则可以求两个日期之差。
6- 处理 NULL 值的函数
问题:
解答:
SELECTCONCAT(first_name, ' ', last_name) AS name,IFNULL(phone, 'Unknown') AS phone
FROM customers
或者
SELECTCONCAT(first_name, ' ', last_name) AS name,COALESCE(phone, 'Unknown') AS phone
FROM customers
两者的区别在于,IFNULL 只是简单地用后一个值替换掉前面为 NULL 的值,而 COALESCE 可以用作 COALESCE(列1, 列2, '...')
,如果列 1 为空就用列 2 替换,但如果列 2 也为空就用字符串替换。
7- IF 函数
问题:生成以下查询,最后一列是根据倒数第二列生成的。
解答:
SELECToi.product_id,p.name,COUNT(*) AS orders,IF(COUNT(*) > 1, 'Many times', 'Once') AS frequency
FROM order_items AS oi
JOIN products AS pON oi.product_id = p.product_id
GROUP BY oi.product_id, p.name
8- CASE 函数
问题:生成以下查询,最后一列是根据倒数第二列生成的。
解答:
SELECTCONCAT(first_name, ' ', last_name) AS customer,points,CASEWHEN points > 3000 THEN 'Gold'WHEN points BETWEEN 2000 AND 3000 THEN 'Silver'WHEN points < 2000 THEN 'Bronze'END AS category
FROM customers
第八章
1- 创建视图
问题:创建有 client_id、name 和 balance 三列的视图,其中 balance 是 invoice_total 与 payment_total 之差。
解答:
CREATE VIEW clients_balance AS
SELECTc.client_id,c.name,invoice_total - payment_total AS balance
FROM invoices AS i
JOIN clients AS cUSING (client_id)
GROUP BY client_id, name
创建了的视图经过刷新后,可以在左边目录栏找到。
2- 更新或删除视图
# 更新视图
REPLACE VIEW 视图名 AS ...
# 删除视图
DROP VIEW 视图名
3- 可更新视图
一般情况下我们都会在表中进行数据插入、更新或删除,但有时我们只拥有视图,如果视图是可更新的话我们就能够通过视图进行数据插入、更新或删除。当视图代码中没有 DISTINCT、聚合函数、GROUP BY(HAVING)和 UNION 时,视图就是可更新的。
4- WITH OPTION CHECK 子句
在生成视图的代码最后,使用 WITH OPTION CHECK 子句,可以阻止对视图中的一行进行更新或者删除。
5- 视图总结
视图是虚拟的表,它包含的不是数据而是根据需要检索数据的查询。假设视图包含的查询十分复杂,在生成视图之后,我们就可以直接在视图上进行后续的操作,更加简单,但性能不变(因为本质还是得先执行复杂查询)。同时我们也可以保护基础数据,增加安全性。
第九章
1- 什么是存储过程 Stored Procedures
存储过程是一个包含一堆 SQL 代码的数据库对象(类似于函数),应用程序通过调用存储过程来获取或保存数据,因为 SQL 代码如果和应用程序代码放在一起会很麻烦(难读、多次编译)。
2- 创建存储过程
问题:创建一个存储过程,其中包含的 SQL 语句是返回所有 balance(invoice_total - payment_total) > 0 的 invoices
解答:
DELIMITER $$
CREATE PROCEDURE get_invoices_with_balance()
BEGINSELECT *FROM invoicesWHERE invoice_total - payment_total > 0;
END$$DELIMITER ;
在 MySQL 中,创建存储过程前需要定义新的分隔符,常用 $$
,然后才是 CREATE PROCEDURE
,后面写存储过程的名称和括号,再然后就是在 BEGIN
和 END
的中间写查询语句,之后再用一次分隔符 $$
,这样两个分隔符之间的内容就是 MySQL 需要执行的内容,最后再把分隔符改回来。
调用创建好的存储过程可以通过工作台,或者用 CALL 语句:
CALL sql_invoicing.get_invoices_with_balance();
3- 使用工作台创建存储过程
如果觉得创建存储过程需要定义新的分隔符太麻烦,可以通过右键点击库中的 Stored Procedures,选择创建存储过程,就会弹出新的窗口,在红色框直接写我们需要的查询语句,然后点击右下角的 Apply 即可。
4- 删除存储过程
要删除我们前面创建过的存储过程,用下面这一行代码即可:
DROP PROCEDURE get_invoices_with_balance
5- 参数 Parameters
问题:创建一个存储过程,它可以返回我们指定的一个 client_id 的所有 invoices
解答:
DELIMITER $$
CREATE PROCEDURE get_invoices_with_client(client_id INT)
BEGINSELECT *FROM invoices AS iWHERE i.client_id = client_id;
END$$DELIMITER ;
创建好存储过程 get_invoices_with_client 之后,调用它需要提供 client_id 参数:
6- 处理为 NULL 和多个参数
问题:创建一个存储过程,它接受两个参数 client_id 和 payment_method_id,返回参数指定的所有 payments 表中的内容,如果参数为 NULL 则返回该列的所有行。
解答:如果参数为 NULL 则等于它自己,相当于返回所有的行
DELIMITER $$
CREATE PROCEDURE get_payments
(client_id INT,payment_method_id TINYINT
)
BEGINSELECT *FROM payments AS pWHERE p.client_id = IFNULL(client_id, p.client_id) AND p.payment_method = IFNULL(payment_method_id, p.payment_method);
END$$DELIMITER ;
想传入 NULL 作为参数,只能通过 CALL 而不是工作台进行过程的调用:
CALL sql_invoicing.get_payments(NULL, NULL);
7- 验证参数
如果参数是金额 payment_amount,我们不希望它出现负值,就可以用 IF 语句,自定义返回 SQL 的状态码和错误信息,如下所示:
8- 输出参数(不建议使用)
如果在参数前面加上 OUT,它们就是输出变量,将值通过 INTO 就能传给输出参数。调用该过程实际上就等于:
相当于 MySQL 自动帮我们初始化了两个变量,然后将变量传入存储过程并调用,最后再 SELECT 这两个变量,得到查询。
9- 变量
之前看到的通过 set @name = value;
定义的是用户变量,如果是在存储过程中定义的则是局部变量,如下所示:
10- 函数
函数和存储过程类似,区别在于函数必须返回值,而过程不一定返回值(例如执行插入和删除操作);过程可以调用函数,函数不能调用过程。更多区别看这里
第十一章
1- 事务 Transaction
定义,事务是代表单个工作单元的一组 SQL 语句,因此这一组语句要么作为整体全部执行成功,要么全部都不执行,避免了出现一些语句执行成功而另一些语句执行失败所导致的意外结果。
事务必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
原子性:一个事务中的所有操作就跟原子一样不可分割,要么全部完成,要么全部不完成,不会结束在中间某个环节。
一致性:在事务开始之前和结束以后,数据库的一致性(某种预设规则)都没有被破坏,反之如果出现某些语句执行成功而某些语句执行失败的情况,一致性就可能被破坏。
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
2- 创建事务
创建事务很简单,就是在一组 SQL 语句(insert, update, delete)前面加上 START TRANSACTION
,后面加上 COMMIT
,这样如果你只执行第5、6行语句,是不会有结果的,必须执行第8、9行才行。
3- 并发和锁定 Concurrency and Locking
所谓并发,就是有两个或以上的用户同时访问相同的数据,当一个用户修改其他用户正在检索或者修改的数据时,并发就可能会出现问题。
模拟两个用户的方法很简单,就是在 MYSQL Workbench 的主页新建一个会话,两个会话即两个用户,然后我们尝试两个用户同时进行 UPDATE 数据的操作:
这是第一个用户的事务,第二个用户的事务也是一模一样,然后我们只执行第一个用户的前三行 SQL语句,此时如果我们去执行第二个用户的 SQL 语句,运行到第 3 行就会卡住(不停旋转):
这是因为第一个用户准备更新的那行已经被锁住了,所以别的用户无法对其进行更新。只有当第一个用户的事务 COMMIT 之后,第二个用户才可以顺利执行自己的事务。
4- 并发问题
1、Lost Updates 丢失更新
如图所示,当用户A和用户B都要对某行数据进行更新时,较晚提交的事务会覆盖较早提交的事务,假设如果用户B较晚提交事务,最终的结果就是 John, NY, 20
,用户A所做的更新将会丢失。
解决方法就是锁定机制。
2、Dirty Reads 脏读
事务A将某顾客的积分从10分变成了20分,如果在事务A提交之前,事务B读取了该顾客的积分,就会是20分,事务B可能根据其作出了一些决策(例如升级会员卡),但是如果事务A提交失败回退了呢?这就相当于事务B读取到了不存在的积分数据,即所谓的脏读。
3、Non-repeatable Reads 不可重复读
事务A查询得到分数是10,如果事务B更新了分数值为0,则事务A中再次查询分数就会得到不一样的结果(0分),即不可重复读,这在某些场合是我们不希望看到的。
4、Phantom Reads 幻读
事务A正在进行查询,如果事务B在同一时间进行了分数的更新,但是提交的时间又晚于事务A,就会使得事务A错过了某些大于10分的顾客,即所谓的幻读。
5- 事务隔离级别 Transaction Isolation Level
共四种事务的隔离级别,最低级的是读未提交,该级别下可能出现之前提到过的所有并发问题,但是性能最好(没有锁);
其次是读已提交,它可以解决脏读的问题,例如前面例子中的事务B,只有当事务A成功提交之后,事务B才会读取该数据,但是如果事务B读取了两次数据呢?第一次读取了数据之后,事务A中修改了数据并成功提交,事务B第二次读取的数据就会不一样,即不可重复读;
MySQL 中默认的是可重复读,顾名思义,该隔离级别下的事务,多次读取同一个数据,不管有没有别的事务更改过该数据,都会得到相同的结果;
最后的是序列化,相当于让事务序列地执行,一个事务成功提交之后再开始下一个事务,但这样就没有了并行操作,性能比较差。
展示当前的事务隔离级别和更改事务隔离级别如下:
6- 死锁 Deadlocks
假设有两个用户,他们各自的事务都是更新 customers 和 orders 表,但是顺序不同。由于在 MySQL 中事务并发的时候,处理中的数据会被锁住,所以当两个用户都执行至第三行语句时,customers 和 orders 表都被锁住了,导致两个用户的第四行语句都无法执行,即为死锁。