MySQL 学习练习(配套 01 入门资料)
目标:每学完一章就动手练,巩固肌肉记忆。所有练习基于同一个示例库
shop_demo。建议:先自己写 SQL,再看文末参考答案,对比差异。
难度标记:★ 基础 ★★ 进阶 ★★★ 综合
编程资源
https://pan.quark.cn/s/7f7c83756948
更多资源
https://pan.quark.cn/s/bda57957c548
准备工作:初始化示例库
把下面整段在 MySQL 里执行一次,得到练习用的 shop_demo 库(含 users / products / orders / order_items 四张表与数据)。
sql
DROP DATABASE IF EXISTS shop_demo;
CREATE DATABASE shop_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE shop_demo;
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
city VARCHAR(50),
age TINYINT UNSIGNED,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
category VARCHAR(50)
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
product_id INT NOT NULL,
qty INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
INSERT INTO users (username, city, age) VALUES
('alice','Beijing',28), ('bob','Shanghai',34), ('carol','Beijing',22),
('dave','Shenzhen',41), ('erin','Shanghai',19), ('frank','Beijing',30);
INSERT INTO products (name, price, stock, category) VALUES
('Keyboard',199.00,120,'electronics'), ('Mouse',89.50,200,'electronics'),
('Notebook',12.00,500,'stationery'), ('Monitor',1299.00,40,'electronics'),
('Pen',5.50,1000,'stationery');
INSERT INTO orders (user_id, amount, status) VALUES
(1,288.50,'paid'), (2,1299.00,'paid'), (1,12.00,'pending'),
(3,89.50,'paid'), (4,1407.50,'shipped'), (5,199.00,'pending');
INSERT INTO order_items (order_id, product_id, qty, price) VALUES
(1,1,1,199.00), (1,2,1,89.50),
(2,4,1,1299.00),
(3,3,1,12.00),
(4,2,1,89.50),
(5,4,1,1299.00), (5,1,1,199.00) /* 注意:order 5 amount 写的是 199,刻意为之,见练习19 */,
(6,1,1,199.00);
第 3 章 建库建表 ★
练习 1 :创建一个库 test_db,字符集 utf8mb4。
练习 2 :在 shop_demo 中新建表 categories(id, name),并给 products 加上外键 category_id 指向它。
练习 3 :给 users 增加列 phone VARCHAR(20),再删除它。
第 5 章 CRUD ★
练习 4 :插入一名用户 gary,城市 Chengdu,年龄 27。
练习 5 :把 bob 的年龄改成 35。
练习 6 :删除用户 frank(注意外键:先处理其订单或临时禁用外键检查)。
练习 7 :查询 products 所有商品,只显示 name 和 price。
第 6 章 条件查询 ★★
练习 8 :查询年龄 ≥ 30 的用户。
练习 9 :查询城市在 Beijing 或 Shanghai 的用户。
练习 10 :查询用户名以 a 开头的用户(LIKE)。
练习 11:查询没有填城市(city 为 NULL)的用户。
第 7 章 排序与分页 ★★
练习 12 :按年龄降序查询用户,取前 3 名。
练习 13:按每页 2 条,查询第 2 页用户(按 id 排序)。
第 8 章 聚合与分组 ★★
练习 14 :统计各城市的用户数量。
练习 15 :统计各城市平均年龄大于 25 的城市及其平均年龄。
练习 16 :计算 products 中 electronics 类商品的总库存与平均价格。
第 9 章 多表连接 ★★★
练习 17 :列出每笔订单的订单号、下单用户名、金额(orders JOIN users)。
练习 18 :列出每个用户名及其订单数(含 0 单的用户,用 LEFT JOIN + COUNT)。
练习 19 :找出 orders.amount 与实际 order_items 明细金额不一致 的订单(数据质量校验,提示:用子查询或 JOIN + GROUP BY 汇总明细再比较)。
练习 20 :查询买了 Monitor(显示器)的用户名。
第 11 章 索引 ★★
练习 21 :为 orders.user_id 建普通索引,再查看索引列表。
练习 22 :为 users(city, age) 建联合索引,思考哪些查询能命中(最左前缀)。
第 14 章 事务 ★★★
练习 23 :模拟转账------从 users 角度设计一个「积分账户表 wallet(user_id, balance)」,用事务完成 A 给 B 转 100 分,并提交;再演示中途 ROLLBACK 的效果。
练习 24 :故意在事务中写一条会报错的 SQL(如插入重复主键),观察 ROLLBACK 后数据是否回退。
进阶综合 ★★★
练习 25 :统计「已支付(paid)订单」中,每个用户的累计消费金额,按金额降序。
练习 26 :找出销量(order_items.qty 总和)最高的前 3 个商品及其销量。
练习 27 :用 CASE 给用户打标签:age<25「青年」、25--40「中年」、>40「资深」,并统计各标签人数。
参考答案
练习 1
sql
CREATE DATABASE test_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
练习 2
sql
CREATE TABLE categories (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50));
ALTER TABLE products ADD COLUMN category_id INT;
ALTER TABLE products ADD CONSTRAINT fk_cat
FOREIGN KEY (category_id) REFERENCES categories(id);
练习 3
sql
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE users DROP COLUMN phone;
练习 4
sql
INSERT INTO users (username, city, age) VALUES ('gary','Chengdu',27);
练习 5
sql
UPDATE users SET age = 35 WHERE username = 'bob';
练习 6(先删子表引用,再删父表)
sql
DELETE FROM orders WHERE user_id = (SELECT id FROM users WHERE username='frank');
DELETE FROM users WHERE username = 'frank';
-- 或临时:SET FOREIGN_KEY_CHECKS=0; ... SET FOREIGN_KEY_CHECKS=1;
练习 7
sql
SELECT name, price FROM products;
练习 8
sql
SELECT * FROM users WHERE age >= 30;
练习 9
sql
SELECT * FROM users WHERE city IN ('Beijing','Shanghai');
练习 10
sql
SELECT * FROM users WHERE username LIKE 'a%';
练习 11
sql
SELECT * FROM users WHERE city IS NULL;
练习 12
sql
SELECT * FROM users ORDER BY age DESC LIMIT 3;
练习 13
sql
SELECT * FROM users ORDER BY id LIMIT 2, 2; -- 偏移2,取2条 = 第2页
练习 14
sql
SELECT city, COUNT(*) AS cnt FROM users GROUP BY city;
练习 15
sql
SELECT city, AVG(age) AS avg_age
FROM users GROUP BY city HAVING AVG(age) > 25;
练习 16
sql
SELECT SUM(stock) AS total_stock, AVG(price) AS avg_price
FROM products WHERE category = 'electronics';
练习 17
sql
SELECT o.id AS order_no, u.username, o.amount
FROM orders o
JOIN users u ON o.user_id = u.id;
练习 18
sql
SELECT u.username, COUNT(o.id) AS order_cnt
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username;
练习 19(汇总明细比对)
sql
SELECT o.id, o.amount, SUM(oi.qty * oi.price) AS detail_sum
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id, o.amount
HAVING o.amount <> SUM(oi.qty * oi.price);
-- 预期命中 order_id = 5(amount 199 vs 明细 1498)
练习 20
sql
SELECT DISTINCT u.username
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE p.name = 'Monitor';
练习 21
sql
CREATE INDEX idx_user_id ON orders(user_id);
SHOW INDEX FROM orders;
练习 22
sql
CREATE INDEX idx_city_age ON users(city, age);
-- 命中:(city) 或 (city, age);不命中:仅 (age)
练习 23
sql
CREATE TABLE wallet (user_id INT PRIMARY KEY, balance INT NOT NULL DEFAULT 0);
INSERT INTO wallet VALUES (1, 500), (2, 300);
START TRANSACTION;
UPDATE wallet SET balance = balance - 100 WHERE user_id = 1;
UPDATE wallet SET balance = balance + 100 WHERE user_id = 2;
COMMIT;
-- 中途若执行 ROLLBACK; 则两条 UPDATE 都撤销
练习 24
sql
START TRANSACTION;
INSERT INTO users (id, username) VALUES (1, 'dup'); -- 假设 id=1 已存在 → 报错
ROLLBACK; -- 即使前面有成功的语句也会一起回退
练习 25
sql
SELECT u.username, SUM(o.amount) AS total
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'paid'
GROUP BY u.id, u.username
ORDER BY total DESC;
练习 26
sql
SELECT p.name, SUM(oi.qty) AS sales
FROM order_items oi
JOIN products p ON oi.product_id = p.id
GROUP BY p.id, p.name
ORDER BY sales DESC
LIMIT 3;
练习 27
sql
SELECT
CASE WHEN age < 25 THEN '青年'
WHEN age <= 40 THEN '中年'
ELSE '资深' END AS tag,
COUNT(*) AS cnt
FROM users
GROUP BY tag;
自测清单
- 能独立写出建库、建表、插数据的完整脚本
- 清楚
WHERE与HAVING的区别 - 能画出 INNER / LEFT / RIGHT JOIN 的结果差异
- 理解
UPDATE/DELETE漏写WHERE的后果 - 能说出索引「最左前缀」是什么
- 能手写一段转账事务
全部打勾 → 进入 03-精通学习资料.md。
编程资源
https://pan.quark.cn/s/7f7c83756948
更多资源
https://pan.quark.cn/s/bda57957c548