前面我们把 select、update、insert、delete 的语法、函数、关联查询、子查询都过了一遍,sql 学的就差不多了。
这节我们来实战下,写一些复杂的 sql。
先创建个单独的数据库:
create database practice
执行它:
点击刷新,就可以看到这个 database(也叫 schema)了:
执行 use practice 切换数据库:
use practice;
然后创建 3 个表:
-- 创建 customers 表,用于存储客户信息
CREATE TABLE IF NOT EXISTS `customers` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '客户ID,自增长',
`name` varchar(255) NOT NULL COMMENT '客户姓名,非空',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户信息表';
-- 创建 orders 表,用于存储订单信息
CREATE TABLE IF NOT EXISTS `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单ID,自增长',
`customer_id` int(11) NOT NULL COMMENT '客户ID,非空',
`order_date` date NOT NULL COMMENT '订单日期,非空',
`total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额,非空',
PRIMARY KEY (`id`),
FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单信息表';
-- 创建 order_items 表,用于存储订单商品信息
CREATE TABLE IF NOT EXISTS `order_items` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品ID,自增长',
`order_id` int(11) NOT NULL COMMENT '订单ID,非空',
`product_name` varchar(255) NOT NULL COMMENT '商品名称,非空',
`quantity` int(11) NOT NULL COMMENT '商品数量,非空',
`price` decimal(10,2) NOT NULL COMMENT '商品单价,非空',
PRIMARY KEY (`id`),
FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单商品信息表';
分别是顾客、订单、订单项。
一个顾客有多个订单,一个订单有多个订单项,通过外键存储这种关联关系。
级联方式为 CASCADE。
上面还涉及到注释的语法,sql 里的注释用 -- 开头:
执行建表 sql:
点击刷新,就可以看到这三个表了:
然后插入一些数据:
-- 向 customers 表插入数据
INSERT INTO `customers` (`name`)
VALUES
('张丽娜'),('李明'),('王磊'),('赵静'),('钱伟'),
('孙芳'),('周涛'),('吴洋'),('郑红'),('刘华'),
('陈明'),('杨丽'),('王磊'),('张伟'),('李娜'),
('刘洋'),('陈静'),('杨阳'),('王丽'),('张强');
-- 向 orders 表插入数据
INSERT INTO `orders` (`customer_id`, `order_date`, `total_amount`)
VALUES
(1, '2022-01-01',100.00),(1, '2022-01-02',200.00),
(2, '2022-01-03',300.00),(2, '2022-01-04',400.00),
(3, '2022-01-05',500.00),(3, '2022-01-06',600.00),
(4, '2022-01-07',700.00),(4, '2022-01-08',800.00),
(5, '2022-01-09',900.00),(5, '2022-01-10',1000.00);
-- 向 order_items 表插入数据
INSERT INTO `order_items` (`order_id`, `product_name`, `quantity`, `price`)
VALUES
(1, '耐克篮球鞋',1,100.00),
(1, '阿迪达斯跑步鞋',2,50.00),
(2, '匡威帆布鞋',3,100.00),
(2, '万斯板鞋',4,50.00),
(3, '新百伦运动鞋',5,100.00),
(3, '彪马休闲鞋',6,50.00),
(4, '锐步经典鞋',7,100.00),
(5, '亚瑟士运动鞋',10,50.00),
(5, '帆布鞋',1,100.00),
(1, '苹果手写笔',2,50.00),
(2, '电脑包',3,100.00),
(3, '苹果手机',4,50.00),
(4, '苹果耳机',5,100.00),
(5, '苹果平板',7,100.00);
执行这些 sql:
然后查询下看看:
select * from customers
select * from orders
select * from order_items
顾客、订单、订单项三个表都成功插入了数据。
然后我们来实现下这些需求:
需求 1: 查询每个客户的订单总金额
客户的订单存在订单表里,可能有多个,这里需要 JOIN ON 关联两个表,然后用 GROUP BY 根据客户 id 分组,再通过 SUM 函数计算价格总和。
SELECT customers.name, SUM(orders.total_amount) AS total_amount
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY customers.id;
这里的 INNER JOIN ON 也可以简化为 JOIN ON。
执行查询:
成功查出了每个客户的订单总金额。
我们还可以再加上排序:
SELECT customers.name, SUM(orders.total_amount) AS total_amount
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY customers.id
ORDER BY total_amount DESC;
如果想取前 3 的,可以用 LIMIT:
SELECT customers.name, SUM(orders.total_amount) AS total_amount
FROM customers
JOIN orders ON customers.id = orders.customer_id
GROUP BY customers.id
ORDER BY total_amount DESC
LIMIT 0,3;
从第 0 个开始取 3 个:
需求 2: 查询每个客户的订单总金额,并计算其占比
每个客户的总金额的需求上面实现了,这里需要算占比,就需要通过一个子查询来计算全部订单的总金额,然后相除:
SELECT customers.name, SUM(orders.total_amount) AS total_amount,
SUM(orders.total_amount) / (SELECT SUM(total_amount) FROM orders) AS percentage
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY customers.id;
当然,这里每次都算一遍总金额性能不好,可以先算出总金额,然后把数值传入。
这里只是练习子查询。
需求 3:查询每个客户的订单总金额,并列出每个订单的商品清单
这里在总金额的基础上,多了订单项的查询,需要多关联一个表:
SELECT customers.name, orders.order_date, orders.total_amount,
order_items.product_name, order_items.quantity, order_items.price
FROM customers
JOIN orders ON customers.id = orders.customer_id
JOIN order_items ON orders.id = order_items.order_id
ORDER BY customers.name, orders.order_date;
内连接关联 3 个表,按照名字和下单日期排序。
需求 4:查询每个客户的订单总金额,并列出每个订单的商品清单,同时只显示客户名字姓“张”的客户的记录:
总金额和商品清单的需求前面实现了,这里只需要加一个 WHERE 来过滤客户名就行:
SELECT customers.name, orders.order_date, orders.total_amount,
order_items.product_name, order_items.quantity, order_items.price
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
INNER JOIN order_items ON orders.id = order_items.order_id
WHERE customers.name LIKE '张%'
ORDER BY customers.name, orders.order_date;
执行下:
需求 5:查询每个客户的订单总金额,并列出每个订单的商品清单,同时只显示订单日期在2022年1月1日到2022年1月3日之间的记录
这里比上面的需求只是多了日期的过滤,范围是一个区间,用 BETWEEN AND:
SELECT customers.name, orders.order_date,
orders.total_amount, order_items.product_name,
order_items.quantity, order_items.price
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
INNER JOIN order_items ON orders.id = order_items.order_id
WHERE orders.order_date BETWEEN '2022-01-01' AND '2022-01-03'
ORDER BY customers.name, orders.order_date;
因为这里的 order_date 是 date 类型,所以指定范围也只是用 2022-01-01 这种格式的。如果是 datetime,那就要用 2022-01-01 10:10:00 这种格式了。
需求 6:查询每个客户的订单总金额,并计算商品数量,只包含商品名称包含“鞋”的商品,商品名用-连接,显示前 3 条记录:
查询订单总金额和商品数量都需要用 group by 根据 customer.id 分组,过滤出只包含鞋的商品。
把分组的多条商品名连接起来需要用 GROUP_CONCAT 函数。
然后 LIMIT 3
SELECT
c.name AS customer_name,
SUM(o.total_amount) AS total_amount,
COUNT(oi.id) AS total_quantity,
GROUP_CONCAT(oi.product_name SEPARATOR '-') AS product_names
FROM customers c
JOIN orders o ON c.id = o.customer_id
JOIN order_items oi ON o.id = oi.order_id
WHERE oi.product_name LIKE '%鞋%'
GROUP BY c.name
ORDER BY total_amount DESC
LIMIT 3;
GROUP_CONCAT 函数是用于 group by 分组后,把多个值连接成一个字符串的。
LIMIT 3 就相当于 LIMIT 0,3 也就是从 0 开始 3 条记录:
需求 7: 查询存在订单的客户
这里使用子查询 + EXISTS 来实现:
SELECT * FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.id
);
如果从 orders 表中查出了当前 customer 的订单记录,EXISTS 就成立。
当然,你也可以用 NO EXISTS 来查询没有下单过的客户:
SELECT * FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.id
);
需求 8: 将王磊的订单总金额打九折
现在王磊的订单总金额是这些:
SELECT * FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE customers.name = '王磊';
更新它们为 90%:
UPDATE orders o SET o.total_amount = o.total_amount * 0.9
WHERE o.customer_id IN (
SELECT id FROM customers WHERE name = '王磊'
);
这里订单不止一条,所以用 IN 来指定一个集合。
再查询下:
确实减少了。
总结
这节我们创建了一个新的 database 并且新增了 customers、orders、order_items 表来练习 sql。
customers 和 orders、orders 和 order_items 都是一对多的关系。
我们练习了 JOIN ON、WHERE、ORDER BY、GROUP BY、LIMIT 等语法,也练习了 SUM、COUNT、GROUP_CONCAT 等函数。
还有子查询和 EXISTS。
sql 常用的语法也就这些,把这些掌握了就能完成各种需求了。