DROP DATABASE IF EXISTS sql_practice_shop;
CREATE DATABASE sql_practice_shop
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
USE sql_practice_shop;
运行结果截图待上传docs/screenshots/01-01.png
截图路径:docs/screenshots/01-01.png
Section 02
第 2 部分:创建数据表
2.1
创建数据表
创建 customers、products、orders、order_items 四张练习表。
-- 业务场景:一个简单商城
-- customers 客户表
-- products 产品表
-- orders 订单表
-- order_items 订单明细表
DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
city VARCHAR(80) NOT NULL,
email VARCHAR(160) NOT NULL UNIQUE,
level ENUM('normal', 'vip') NOT NULL DEFAULT 'normal',
created_at DATE NOT NULL
) ENGINE=InnoDB;
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(120) NOT NULL,
category VARCHAR(80) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
status ENUM('active', 'inactive') NOT NULL DEFAULT 'active'
) ENGINE=InnoDB;
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_no VARCHAR(40) NOT NULL UNIQUE,
status ENUM('pending', 'paid', 'shipped', 'cancelled') NOT NULL DEFAULT 'pending',
order_date DATE NOT NULL,
note VARCHAR(255),
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON UPDATE CASCADE
ON DELETE RESTRICT
) ENGINE=InnoDB;
CREATE TABLE order_items (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL,
CONSTRAINT fk_order_items_order
FOREIGN KEY (order_id) REFERENCES orders(id)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT fk_order_items_product
FOREIGN KEY (product_id) REFERENCES products(id)
ON UPDATE CASCADE
ON DELETE RESTRICT
) ENGINE=InnoDB;
SELECT
o.id AS order_id,
o.order_no,
o.status,
o.order_date,
c.name AS customer_name,
c.city
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
ORDER BY o.order_date DESC;
运行结果截图待上传docs/screenshots/11-01.png
截图路径:docs/screenshots/11-01.png
11.2
查询订单明细,包括订单号、客户、产品、数量、单价
SELECT
o.order_no,
c.name AS customer_name,
p.name AS product_name,
oi.quantity,
oi.unit_price
FROM order_items oi
INNER JOIN orders o ON oi.order_id = o.id
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN products p ON oi.product_id = p.id
ORDER BY o.order_date DESC, o.order_no;
运行结果截图待上传docs/screenshots/11-02.png
截图路径:docs/screenshots/11-02.png
11.3
计算每条订单明细的小计金额
SELECT
o.order_no,
p.name AS product_name,
oi.quantity,
oi.unit_price,
oi.quantity * oi.unit_price AS line_total
FROM order_items oi
INNER JOIN orders o ON oi.order_id = o.id
INNER JOIN products p ON oi.product_id = p.id;
运行结果截图待上传docs/screenshots/11-03.png
截图路径:docs/screenshots/11-03.png
11.4
查询每个订单的总金额
SELECT
o.order_no,
c.name AS customer_name,
o.status,
SUM(oi.quantity * oi.unit_price) AS order_total
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id, o.order_no, c.name, o.status
ORDER BY order_total DESC;
运行结果截图待上传docs/screenshots/11-04.png
截图路径:docs/screenshots/11-04.png
11.5
查询已付款订单的总金额
SELECT
o.order_no,
c.name AS customer_name,
SUM(oi.quantity * oi.unit_price) AS order_total
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN order_items oi ON o.id = oi.order_id
WHERE o.status = 'paid'
GROUP BY o.id, o.order_no, c.name
ORDER BY order_total DESC;
运行结果截图待上传docs/screenshots/11-05.png
截图路径:docs/screenshots/11-05.png
Section 12
第 12 部分:LEFT JOIN
12.1
查询所有客户,以及他们的订单数量
SELECT
c.id,
c.name,
c.city,
COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name, c.city
ORDER BY order_count DESC;
运行结果截图待上传docs/screenshots/12-01.png
截图路径:docs/screenshots/12-01.png
12.2
查询没有下过订单的客户
SELECT
c.*
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE o.id IS NULL;
运行结果截图待上传docs/screenshots/12-02.png
截图路径:docs/screenshots/12-02.png
12.3
查询所有产品,以及被购买的次数
SELECT
p.id,
p.name,
p.category,
COUNT(oi.id) AS sold_line_count
FROM products p
LEFT JOIN order_items oi ON p.id = oi.product_id
GROUP BY p.id, p.name, p.category
ORDER BY sold_line_count DESC;
运行结果截图待上传docs/screenshots/12-03.png
截图路径:docs/screenshots/12-03.png
Section 13
第 13 部分:子查询 Subquery
13.1
查询价格高于平均价格的产品
SELECT *
FROM products
WHERE price > (
SELECT AVG(price)
FROM products
);
运行结果截图待上传docs/screenshots/13-01.png
截图路径:docs/screenshots/13-01.png
13.2
查询下过订单的客户
SELECT *
FROM customers
WHERE id IN (
SELECT customer_id
FROM orders
);
运行结果截图待上传docs/screenshots/13-02.png
截图路径:docs/screenshots/13-02.png
13.3
查询没有下过订单的客户
SELECT *
FROM customers
WHERE id NOT IN (
SELECT customer_id
FROM orders
);
运行结果截图待上传docs/screenshots/13-03.png
截图路径:docs/screenshots/13-03.png
Section 14
第 14 部分:CASE WHEN 条件判断
14.1
根据价格给产品打标签
SELECT
name,
price,
CASE
WHEN price >= 30 THEN 'high'
WHEN price >= 10 THEN 'middle'
ELSE 'low'
END AS price_level
FROM products;
运行结果截图待上传docs/screenshots/14-01.png
截图路径:docs/screenshots/14-01.png
14.2
根据订单状态显示中文状态
SELECT
order_no,
status,
CASE status
WHEN 'pending' THEN '待付款'
WHEN 'paid' THEN '已付款'
WHEN 'shipped' THEN '已发货'
WHEN 'cancelled' THEN '已取消'
ELSE '未知'
END AS status_cn
FROM orders;
运行结果截图待上传docs/screenshots/14-02.png
截图路径:docs/screenshots/14-02.png
Section 15
第 15 部分:日期函数
15.1
查询订单日期、年份、月份
SELECT
order_no,
order_date,
YEAR(order_date) AS order_year,
MONTH(order_date) AS order_month
FROM orders;
运行结果截图待上传docs/screenshots/15-01.png
截图路径:docs/screenshots/15-01.png
15.2
按月份统计订单数量
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS order_month,
COUNT(*) AS order_count
FROM orders
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY order_month;
运行结果截图待上传docs/screenshots/15-02.png
截图路径:docs/screenshots/15-02.png
15.3
查询最近 30 天内创建的客户
SELECT *
FROM customers
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY);
运行结果截图待上传docs/screenshots/15-03.png
截图路径:docs/screenshots/15-03.png
Section 16
第 16 部分:常用业务查询综合练习
16.1
查询销售额最高的前 5 个产品
SELECT
p.id,
p.name,
p.category,
SUM(oi.quantity) AS total_quantity,
SUM(oi.quantity * oi.unit_price) AS total_sales
FROM products p
INNER JOIN order_items oi ON p.id = oi.product_id
INNER JOIN orders o ON oi.order_id = o.id
WHERE o.status IN ('paid', 'shipped')
GROUP BY p.id, p.name, p.category
ORDER BY total_sales DESC
LIMIT 5;
运行结果截图待上传docs/screenshots/16-01.png
截图路径:docs/screenshots/16-01.png
16.2
查询每个客户的累计消费金额
SELECT
c.id,
c.name,
c.city,
COALESCE(SUM(oi.quantity * oi.unit_price), 0) AS total_spent
FROM customers c
LEFT JOIN orders o
ON c.id = o.customer_id
AND o.status IN ('paid', 'shipped')
LEFT JOIN order_items oi
ON o.id = oi.order_id
GROUP BY c.id, c.name, c.city
ORDER BY total_spent DESC;
运行结果截图待上传docs/screenshots/16-02.png
截图路径:docs/screenshots/16-02.png
16.3
查询累计消费金额大于 500 的客户
SELECT
c.id,
c.name,
SUM(oi.quantity * oi.unit_price) AS total_spent
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
INNER JOIN order_items oi ON o.id = oi.order_id
WHERE o.status IN ('paid', 'shipped')
GROUP BY c.id, c.name
HAVING SUM(oi.quantity * oi.unit_price) > 500
ORDER BY total_spent DESC;
运行结果截图待上传docs/screenshots/16-03.png
截图路径:docs/screenshots/16-03.png
16.4
查询每个城市的销售额
SELECT
c.city,
SUM(oi.quantity * oi.unit_price) AS city_sales
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
INNER JOIN order_items oi ON o.id = oi.order_id
WHERE o.status IN ('paid', 'shipped')
GROUP BY c.city
ORDER BY city_sales DESC;
运行结果截图待上传docs/screenshots/16-04.png
截图路径:docs/screenshots/16-04.png
16.5
查询每个订单的产品种类数和总件数
SELECT
o.order_no,
COUNT(DISTINCT oi.product_id) AS product_type_count,
SUM(oi.quantity) AS total_quantity
FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id, o.order_no
ORDER BY total_quantity DESC;
运行结果截图待上传docs/screenshots/16-05.png
截图路径:docs/screenshots/16-05.png
Section 17
第 17 部分:索引练习
17.1
给订单日期添加普通索引
CREATE INDEX idx_orders_order_date ON orders(order_date);
运行结果截图待上传docs/screenshots/17-01.png
截图路径:docs/screenshots/17-01.png
17.2
给订单状态添加普通索引
CREATE INDEX idx_orders_status ON orders(status);
运行结果截图待上传docs/screenshots/17-02.png
截图路径:docs/screenshots/17-02.png
17.3
给产品分类添加普通索引
CREATE INDEX idx_products_category ON products(category);
运行结果截图待上传docs/screenshots/17-03.png
截图路径:docs/screenshots/17-03.png
17.4
使用 EXPLAIN 查看 SQL 执行计划
EXPLAIN
SELECT *
FROM orders
WHERE status = 'paid'
ORDER BY order_date DESC;
运行结果截图待上传docs/screenshots/17-04.png
截图路径:docs/screenshots/17-04.png
Section 18
第 18 部分:事务练习 Transaction
18.1
事务练习:创建订单并扣减库存
用事务一次完成新增订单、插入订单明细、扣减库存,最后 COMMIT 提交。
-- 场景:创建订单,同时扣减库存
START TRANSACTION;
INSERT INTO orders (customer_id, order_no, status, order_date, note)
VALUES (2, 'SO20240601001', 'paid', CURDATE(), 'Transaction test order');
SET @new_order_id = LAST_INSERT_ID();
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES
(@new_order_id, 1, 2, 29.90),
(@new_order_id, 8, 1, 6.50);
UPDATE products
SET stock = stock - 2
WHERE id = 1;
UPDATE products
SET stock = stock - 1
WHERE id = 8;
COMMIT;
运行结果截图待上传docs/screenshots/18-01.png
截图路径:docs/screenshots/18-01.png
18.2
查看事务执行后的新订单
验证事务执行后订单、订单明细和产品信息是否正确关联。
SELECT
o.order_no,
o.status,
p.name AS product_name,
oi.quantity,
oi.unit_price
FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE o.order_no = 'SO20240601001';
运行结果截图待上传docs/screenshots/18-02.png
截图路径:docs/screenshots/18-02.png
Section 19
第 19 部分:视图 View
19.1
创建订单汇总视图
把订单、客户、订单金额汇总逻辑封装成视图,后续可以直接查询视图。
DROP VIEW IF EXISTS v_order_summary;
CREATE VIEW v_order_summary AS
SELECT
o.id AS order_id,
o.order_no,
o.status,
o.order_date,
c.name AS customer_name,
c.city,
SUM(oi.quantity * oi.unit_price) AS order_total
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
INNER JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id, o.order_no, o.status, o.order_date, c.name, c.city;
运行结果截图待上传docs/screenshots/19-01.png
截图路径:docs/screenshots/19-01.png
19.2
查询订单汇总视图
像查询普通表一样查询视图。
SELECT *
FROM v_order_summary
ORDER BY order_date DESC;