三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

web前端基础到入门——14day

web前端基础到入门——14day

摘要:掌握 SQL 查询是数据库应用的核心。本文以实战外卖骑手数据为例,系统讲解 SELECT 查询的完整技能栈:从基础查询、条件过滤、聚合统计、分组排序,到多表关联设计。通过 20+ 个真实案例,带你从零到一掌握数据查询的精髓,让你能从任何数据库中精准捞出想要的数据。

一、 DQL 是什么

DQL(Data Query Language),数据查询语言。关键字就一个:SELECT。在一个真实业务系统里,查询占了数据库操作的 80% 以上。你打开淘宝搜"篮球鞋",后端发了一条 SELECT 去数据库;你看订单列表,又是一条 SELECT;后台老板想看今天的销售额报表,还是 SELECT。

一条完整的 DQL 语句长这样:

SELECT -- 要查哪些字段 FROM -- 从哪张表查 WHERE -- 什么条件下查 GROUP BY -- 怎么分组 HAVING -- 分组后过滤 ORDER BY -- 怎么排序 LIMIT -- 取多少条

今天按这个顺序逐个拆解。先建一张练习用的表。

准备测试数据

用外卖场景——骑手表。

创建数据库和测试表:

CREATE DATABASE IF NOT EXISTS delivery_db; USE delivery_db; CREATE TABLE rider ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '骑手ID', username VARCHAR(20) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(32) DEFAULT '123456' COMMENT '密码', name VARCHAR(10) NOT NULL COMMENT '真实姓名', gender TINYINT UNSIGNED NOT NULL COMMENT '性别: 1男 2女', phone CHAR(11) COMMENT '手机号', level TINYINT UNSIGNED COMMENT '骑手等级: 1青铜 2白银 3黄金 4钻石', join_date DATE COMMENT '入职日期', city VARCHAR(20) DEFAULT '北京' COMMENT '所在城市', score DECIMAL(3,1) DEFAULT 5.0 COMMENT '评分', create_time DATETIME NOT NULL COMMENT '创建时间', update_time DATETIME NOT NULL COMMENT '修改时间' ) COMMENT '骑手表';

插入一些测试数据:

INSERT INTO rider (username, name, gender, phone, level, join_date, city, score, create_time, update_time) VALUES ('wangqiang', '王强', 1, '13800001111', 4, '2020-03-15', '北京', 4.9, NOW(), NOW()), ('liumei', '刘梅', 2, '13800002222', 2, '2022-07-01', '上海', 4.5, NOW(), NOW()), ('zhangwei', '张伟', 1, '13800003333', 3, '2021-01-20', '北京', 4.7, NOW(), NOW()), ('chenjing', '陈静', 2, '13800004444', 1, '2023-11-05', '深圳', 4.2, NOW(), NOW()), ('zhaolei', '赵磊', 1, '13800005555', 4, '2019-06-10', '广州', 4.8, NOW(), NOW()), ('sunli', '孙丽', 2, '13800006666', 3, '2021-09-12', '上海', 4.6, NOW(), NOW()), ('zhoudong', '周东', 1, '13800007777', 2, '2022-02-28', '北京', 4.3, NOW(), NOW()), ('wujing', '吴静', 2, '13800008888', 1, '2024-04-01', '深圳', 4.0, NOW(), NOW()), ('zhengyang', '郑洋', 1, '13800009999', 4, '2018-11-20', '杭州', 4.9, NOW(), NOW()), ('huangli', '黄丽', 2, '13800010000', 2, '2023-05-15', '广州', 4.4, NOW(), NOW()), ('xuming', '徐明', 1, '13800011111', 3, '2020-08-08', '北京', 4.5, NOW(), NOW()), ('linfang', '林芳', 2, '13800012222', 1, '2024-01-10', '上海', 3.8, NOW(), NOW()), ('maochao', '毛超', 1, '13800013333', 2, '2021-12-25', '杭州', 4.1, NOW(), NOW()), ('heping', '何平', 1, '13800014444', 3, '2019-04-18', '深圳', 4.6, NOW(), NOW()), ('guyan', '顾燕', 2, '13800015555', 4, '2020-10-01', '北京', 5.0, NOW(), NOW()), ('xiaofeng', '肖峰', 1, '13800016666', 2, '2022-09-03', '广州', 3.9, NOW(), NOW()), ('fanjie', '范洁', 2, '13800017777', 1, '2023-03-20', NULL, 4.2, NOW(), NOW()), ('daishan', '代山', 1, NULL, 3, '2020-05-06', '上海', 4.3, NOW(), NOW()), ('leiyu', '雷宇', 1, '13800019999', NULL, '2021-08-14', '深圳', 3.5, NOW(), NOW()), ('dengna', '邓娜', 2, '13800020000', 2, '2022-11-30', '北京', 4.7, NOW(), NOW());

20 条数据,故意留了几个坑:范洁的 city 是 NULL,雷宇的 level 是 NULL,代山的 phone 是 NULL。后面讲条件查询时会用到。

二、基本查询

2.1 查指定字段

SELECT name, join_date FROM rider;

只返回姓名和入职日期两列。用哪个字段就写哪个字段,别上来就 SELECT *——返回一堆不需要的列,浪费带宽和内存。

2.2 查所有字段

SELECT * FROM rider;

* 是通配符。快速看看表里有什么数据时可以用,但正式代码里别写 *——表结构一变,你的代码读到预期之外的列,排序和分页都可能出 bug。

2.3 起别名

-- 三种写法等价 SELECT name AS 姓名, join_date AS 入职日期 FROM rider; SELECT name AS '姓 名', join_date AS '入职日期' FROM rider; SELECT name AS "姓名", join_date AS "入职日期" FROM rider;

别名里有空格时,用单引号或双引号包起来。别名只影响显示,不改变表结构。

2.4 去重

-- 看看骑手都在哪些城市 SELECT DISTINCT city FROM rider;

结果:北京、上海、深圳、广州、杭州、NULL。DISTINCT 对整行去重。如果写SELECT DISTINCT city, gender FROM rider,只有城市和性别完全相同的行才会被合并。

三、条件查询(WHERE)

这才是查询的灵魂。

语法:

SELECT 字段 FROM 表 WHERE 条件;

3.1 比较运算符

运算符功能示例
> >= < <= =比较score > 4.5
<> 或 !=不等于city <> '北京'
BETWEEN a AND b在范围内(含边界)score BETWEEN 4.0 AND 4.9
IN (v1, v2, ...)在列表中level IN (3, 4)
LIKE '模式'模糊匹配name LIKE '王%'
IS NULL / IS NOT NULL判空phone IS NULL

3.2 逻辑运算符

运算符功能
AND 或 &&并且
OR 或 ||或者
NOT 或 !取反

3.3 实战案例

用 rider 表逐个试:

-- 1. 评分高于 4.5 的骑手 SELECT name, score FROM rider WHERE score > 4.5; -- 2. 非北京的骑手 SELECT name, city FROM rider WHERE city <> '北京'; -- 3. 评分在 4.0 到 4.5 之间(含边界) SELECT name, score FROM rider WHERE score BETWEEN 4.0 AND 4.5; -- 4. 黄金或钻石等级的骑手 SELECT name, level FROM rider WHERE level IN (3, 4); -- 5. 姓"王"的骑手 —— % 匹配任意个字符 SELECT name FROM rider WHERE name LIKE '王%'; -- 6. 姓"王"且名字是两个字 —— _ 匹配单个字符 SELECT name FROM rider WHERE name LIKE '王_'; -- 7. 没填手机号的骑手 SELECT name, phone FROM rider WHERE phone IS NULL; -- 8. 有等级的骑手 SELECT name, level FROM rider WHERE level IS NOT NULL;

四、聚合统计

当我们需要对数据进行汇总计算时,就需要用到聚合函数。

4.1 常用聚合函数

函数功能示例
COUNT()统计行数SELECT COUNT(*) FROM rider;
SUM()求和SELECT SUM(score) FROM rider;
AVG()求平均值SELECT AVG(score) FROM rider;
MAX()求最大值SELECT MAX(score) FROM rider;
MIN()求最小值SELECT MIN(score) FROM rider;

4.2 实战案例

-- 1. 统计骑手总数 SELECT COUNT(*) AS 总人数 FROM rider; -- 2. 统计有手机号的骑手数量 SELECT COUNT(phone) AS 有手机号人数 FROM rider; -- 3. 计算平均评分 SELECT AVG(score) AS 平均评分 FROM rider; -- 4. 找出最高评分和最低评分 SELECT MAX(score) AS 最高分, MIN(score) AS 最低分 FROM rider; -- 5. 统计各城市骑手数量 SELECT city, COUNT(*) AS 人数 FROM rider GROUP BY city;

五、分组查询(GROUP BY)

GROUP BY 用于将数据按指定字段分组,通常与聚合函数一起使用。

5.1 基本语法

SELECT 分组字段, 聚合函数(字段) FROM 表名 WHERE 条件 GROUP BY 分组字段;

5.2 实战案例

-- 1. 按城市分组,统计每个城市的骑手数量 SELECT city, COUNT(*) AS 人数 FROM rider GROUP BY city; -- 2. 按等级分组,计算每个等级的平均评分 SELECT level, AVG(score) AS 平均评分 FROM rider GROUP BY level; -- 3. 按性别和城市分组,统计人数 SELECT gender, city, COUNT(*) AS 人数 FROM rider GROUP BY gender, city;

5.3 HAVING 子句

HAVING 用于对分组后的结果进行过滤,类似于 WHERE,但作用于分组后的数据。

-- 找出平均评分大于 4.5 的城市 SELECT city, AVG(score) AS 平均评分 FROM rider GROUP BY city HAVING AVG(score) > 4.5; -- 找出骑手数量超过 3 人的城市 SELECT city, COUNT(*) AS 人数 FROM rider GROUP BY city HAVING COUNT(*) > 3;

六、排序(ORDER BY)

ORDER BY 用于对查询结果进行排序。

6.1 基本语法

SELECT 字段 FROM 表名 ORDER BY 排序字段 [ASC|DESC];

ASC:升序(默认),DESC:降序。

6.2 实战案例

-- 1. 按评分降序排列 SELECT name, score FROM rider ORDER BY score DESC; -- 2. 按入职日期升序排列 SELECT name, join_date FROM rider ORDER BY join_date ASC; -- 3. 多字段排序:先按城市升序,再按评分降序 SELECT name, city, score FROM rider ORDER BY city ASC, score DESC;

七、分页(LIMIT)

LIMIT 用于限制查询结果的数量,常用于分页查询。

7.1 基本语法

SELECT 字段 FROM 表名 LIMIT 偏移量, 记录数;

或者:

SELECT 字段 FROM 表名 LIMIT 记录数 OFFSET 偏移量;

7.2 实战案例

-- 1. 查询前5条记录 SELECT * FROM rider LIMIT 5; -- 2. 查询第6-10条记录(每页5条,第2页) SELECT * FROM rider LIMIT 5, 5; -- 3. 按评分降序排列,取前3名 SELECT name, score FROM rider ORDER BY score DESC LIMIT 3;

八、多表关系设计

实际业务中,数据通常分布在多张表中,需要通过关联查询获取完整信息。

8.1 表关系类型

  • 一对一:一个表中的一条记录对应另一个表中的一条记录
  • 一对多:一个表中的一条记录对应另一个表中的多条记录
  • 多对多:需要中间表来建立关联

8.2 创建关联表

继续使用外卖场景,创建订单表:

CREATE TABLE orders ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID', order_no VARCHAR(20) NOT NULL UNIQUE COMMENT '订单号', rider_id INT UNSIGNED COMMENT '骑手ID', amount DECIMAL(10,2) NOT NULL COMMENT '订单金额', status TINYINT UNSIGNED DEFAULT 1 COMMENT '状态: 1待接单 2配送中 3已完成 4已取消', create_time DATETIME NOT NULL COMMENT '创建时间', FOREIGN KEY (rider_id) REFERENCES rider(id) ON DELETE SET NULL ) COMMENT '订单表';

8.3 关联查询(JOIN)

JOIN 用于将多个表中的数据关联起来。

8.3.1 INNER JOIN(内连接)

只返回两个表中匹配的记录。

-- 查询骑手及其配送的订单 SELECT r.name AS 骑手姓名, o.o
← 返回列表