13816 字
69 分钟
03:mysql之单表增删改查

[TOC]

03:mysql之单表增删改查#

今日工作任务路线图

flowchart LR
subgraph BottomRow[单表增删改查]
direction LR
A[分组查询] --> B[对查询结果进行排序] --> C[分组后条件过滤] --> D[新增数据] --> E[修改数据] --> F[删除数据]
end

一、工作场景#

在电商运营工作中,单表的增删改查操作十分常见。运营人员为了丰富商品种类,会向商品信息表中新增商品记录,包括商品名称、价格、库存等信息,以满足市场需求。若某商品价格调整,运营人员会及时修改商品信息表中该商品的价格字段,保证数据准确性。当一款商品下架时,他们会从表中删除该商品的记录,避免无效数据干扰。同时,在分析销售数据时,运营人员会根据不同条件从表中查询特定商品的信息,比如筛选出销量高的商品,为后续的营销策略提供数据支持。

二、为什么学增删改查#

学习 MySQL 单表的增删改查至关重要。在数据管理领域,这是最基础且核心的技能。在日常工作里,我们常常要与各种数据打交道,增删改查操作能让我们灵活处理数据。比如,电商平台需新增商品信息、删除滞销商品数据、修改商品价格、查询特定商品库存。掌握这些操作,能高效维护数据,确保数据实时性与准确性。同时,它们是学习更复杂数据库操作的基石,如多表连接、存储过程等。对于从事数据分析、软件开发等行业的人而言,熟练运用增删改查是必备技能,能提升工作效率与质量。

三、分组查询#

当前任务环节

flowchart LR
subgraph BottomRow[单表增删改查]
direction LR
A[分组查询] --> B[对查询结果进行排序] --> C[分组后条件过滤] --> D[新增数据] --> E[修改数据] --> F[删除数据]
end
classDef highlight stroke:#f00,stroke-width:2px;
class A highlight;

分组函数 group【格鲁普】 by#

GROUP【格鲁普】 BY是 SQL 中非常重要的一个子句,它的主要作用是对查询结果进行分组统计

特性说明示例或关键点
核心功能根据指定列将数据分成若干组,然后对每个组应用聚合函数进行统计。计算每个部门的平均工资、每个产品的总销售额等。
基本语法SELECT【涩莱克特】 列A, 聚合函数(列B) FROM【弗乱】 表名 GROUP【格鲁普】 BY 列A;列A是分组依据。
常用聚合函数COUNT【kàn特】()(计数), SUM【桑à姆】()(求和), AVG()(平均值), MAX【mà 斯】()(最大值), MIN【敏ì】()(最小值)。对每个分组进行计算。
与HAVING子句WHERE【威尔】 在分组过滤行,HAVING【嗨夫营】 在分组过滤组。HAVING【嗨夫营】 AVG(salary【晒了瑞】) > 5000(筛选出平均工资大于5000的组)。
多列分组GROUP【格鲁普】 BY 后面可接多个列,按这些列的组合进行分组。GROUP【格鲁普】 BY department【迪帕踢门特】, job_title
重要注意事项SELECT【涩莱克特】 后面非聚合的列,必须出现在 GROUP【格鲁普】 BY 子句中。这是最容易出错的地方。

💡 理解分组的过程#

你可以把 GROUP【格鲁普】 BY 想象成一个高效的数据整理师。它的工作流程通常是:首先,根据 GROUP【格鲁普】 BY 后面指定的列,将表中所有数据行分成不同的“小组”,每个小组内的这些列的值完全相同。然后,数据库会对每一个这样的小组分别计算聚合函数(如求和、计数等)。最后,查询结果将为每个小组返回一行摘要信息,其中包含分组列的值和聚合函数计算的结果。

例如,有一个员工表,使用 SELECT【涩莱克特】 department【迪帕踢门特】, AVG(salary【晒了瑞】) FROM【弗乱】 employees【ing普洛伊s】 GROUP【格鲁普】 BY department【迪帕踢门特】; 可以计算出每个部门的平均工资。在这里,数据库会先按部门进行分组,然后对每个部门小组计算平均工资。

🛠️ 基本用法与示例#

单列分组 这是最常见的情况,即根据一个字段进行分组。

-- 统计每个城市的用户数量
SELECT【涩莱克特】 city, COUNT【kàn特】(*) AS user_count【kàn特】
FROM【弗乱】 users
GROUP【格鲁普】 BY city;

多列分组 当需要根据多个字段的组合进行更精细的分组时,可以使用多列分组。

-- 统计每个部门下每个职位的员工数量
SELECT【涩莱克特】 department【迪帕踢门特】, job_title, COUNT【kàn特】(*) AS employee【ing普洛伊】_count【kàn特】
FROM【弗乱】 employees【ing普洛伊s】
GROUP【格鲁普】 BY department【迪帕踢门特】, job_title;

这个查询会返回所有唯一的“部门-职位”组合及其对应的员工数量。

⚠️ 关键规则与常见错误#

  1. SELECT【涩莱克特】列表的非聚合列规则 这是使用 GROUP【格鲁普】 BY 时最需要牢记的规则:出现在 SELECT【涩莱克特】 列表中的列,要么必须包含在 GROUP【格鲁普】 BY 子句中,要么必须被包含在聚合函数里。这是因为,对于每个分组来说,分组列的值是唯一的(比如“技术部”),但其他列可能有多条不同的记录(比如“技术部”里有很多不同的员工名)。数据库无法确定应该返回哪一条非分组列的值,因此强制要求通过聚合函数(如MAX【mà 斯】, AVG)将其汇总成一个值,或者将其本身作为分组依据。

  2. WHERE【威尔】 与 HAVING【嗨夫营】 的区别

    • WHERE【威尔】:在 分组之前 过滤数据行。它不能包含聚合函数。
    • HAVING【嗨夫营】:在 分组之后 过滤分组结果。它通常与聚合函数一起使用,用来筛选满足特定条件的组。
    -- 正确示例:先过滤出2023年以后的订单,再按客户分组,最后筛选出总金额大于1000的客户
    SELECT【涩莱克特】 customer_id, SUM【桑à姆】(amount) AS total【偷ò头ō】_amount
    FROM【弗乱】 orders【欧朵s】
    WHERE【威尔】 order【欧朵】_date >= '2023-01-01' -- 分组前过滤行
    GROUP【格鲁普】 BY customer_id
    HAVING【嗨夫营】 SUM【桑à姆】(amount) > 1000; -- 分组后过滤组

🔍 高级用法与技巧#

  • 使用表达式或函数分组:可以根据表达式的结果进行分组,这在处理时间序列数据时特别有用。例如,可以按年份和月份分组统计订单。
  • 使用ROLLUP生成小计和总计WITH ROLLUP 选项可以在分组结果的基础上,生成层次性的小计和总计行。
  • 处理NULL【nò】值:需要注意的是,如果分组列中包含 NULL【nò】 值,所有 NULL【nò】 值会被归为同一组。

命令操作如下所示:#

输出符合条件的shell 和 name#
mysql> select【涩莱克特】 shell,name from【弗乱】 moershi.user where【威尔】shell in ("/bin/bash","/sbin/nologin【诺劳京(白话)】");
+---------------+-----------------+
| shell | name |
+---------------+-----------------+
| /bin/bash | root |
| /sbin/nologin | bin |
| /sbin/nologin | daemon |
| /sbin/nologin | adm |
.................
| /sbin/nologin | rpc |
| /sbin/nologin | rpcuser |
| /sbin/nologin | nfsnobody |
| /sbin/nologin | haproxy |
| /bin/bash | plj |
| /sbin/nologin | apache |
+---------------+-----------------+
22 rows in set【赛特】 (0.00 sec)
统计每种解释器用户的个数 (按照shell表头值分组统计name表头值个数)#
mysql> select【涩莱克特】 shell as 解释器 , count【kàn特】(name) as 总人数 from【弗乱】 moershi.user where【威尔】shell in ("/bin/bash","/sbin/nologin【诺劳京(白话)】") group【格鲁普】 by shell;
+---------------+-----------+
| 解释器 | 总人数 |
+---------------+-----------+
| /bin/bash | 2 |
| /sbin/nologin | 20 |
+---------------+-----------+
子句/部分说明
SELECT指定要查询的列。
shell AS 解释器查询 shell 字段,并将其别名设置为“解释器”。
COUNT(name) AS 总人数使用 COUNT 聚合函数统计每个分组中 name 的数目,别名是“总人数”。
FROM moershi.user指定查询的表为 moershi 数据库下的 user 表。
WHERE shell IN (...)过滤条件,只统计使用/bin/bash/sbin/nologin的用户。
GROUP BY shell根据 shell 字段的值进行分组,将使用相同Shell的用户归为一组进行统计。
统计每个部门的总人数 (按照部门表头分组统计name表头值的个数)#
mysql> select【涩莱克特】 dept_id , count【kàn特】(name) from【弗乱】moershi.employees【ing普洛伊s】 group【格鲁普】 by dept_id ;
+---------+-------------+
| dept_id | count【kàn特】(name) |
+---------+-------------+
| 1 | 8 |
| 2 | 5 |
| 3 | 6 |
| 4 | 55 |
| 5 | 12 |
| 6 | 9 |
| 7 | 35 |
| 8 | 3 |
+---------+-------------+
8 rows in set【赛特】 (0.00 sec)

四、对查询结果进行排序#

当前任务环节

flowchart LR
subgraph BottomRow[单表增删改查]
direction LR
A[分组查询] --> B[对查询结果</n>进行排序] --> C[分组后条件过滤] --> D[新增数据] --> E[修改数据] --> F[删除数据]
end
classDef highlight stroke:#f00,stroke-width:2px;
class B highlight;

排序函数 order【欧朵】 by#

特性说明示例或关键点
核心功能根据指定的列对查询结果集进行排序。让数据展示更清晰,便于分析和查看。
基本语法`SELECT【涩莱克特】 … FROM【弗乱】 … ORDER【欧朵】 BY column【科勒姆】_name [ASC【阿斯克】desc];`
排序方式ASC【阿斯克】:升序(默认,可省略)。 desc:降序。若不指定,默认为 ASC【阿斯克】
多列排序按多个字段排序,优先级从左到右。ORDER【欧朵】 BY column1【科勒姆】 desc, column2【科勒姆】 ASC【阿斯克】;
排序键类型字段名、字段在SELECT【涩莱克特】中的序号(从1开始)、字段别名。ORDER【欧朵】 BY 1(按第一列排序)。
与聚合函数可使用聚合函数结果排序。ORDER【欧朵】 BY COUNT【kàn特】(*) desc;
NULL【nò】值处理NULL【nò】值可能集中出现在开头或末尾,具体取决于数据库系统。需特别注意其默认位置。

💡 理解排序规则与语法#

ORDER【欧朵】 BY 子句的核心作用是将查询结果按照特定顺序排列。其基本语法遵循以下结构:

SELECT【涩莱克特】 column1【科勒姆】, column2【科勒姆】, ...
FROM【弗乱】 table【忒-部】_name
ORDER【欧朵】 BY column1【科勒姆】 [ASC【阿斯克】|desc], column2【科勒姆】 [ASC【阿斯克】|desc], ...;

这里,ASC【阿斯克】 表示升序(Ascending),desc 表示降序(Descending)。如果省略排序方式,数据库将默认按照升序(ASC【阿斯克】)排列结果 。

🛠️ 掌握多种排序方式#

单列排序 这是最基础的排序方式,即根据单个字段的值进行排序。

-- 按产品名称升序排列(ASC【阿斯克】可省略)
SELECT【涩莱克特】 prod_name FROM【弗乱】 products ORDER【欧朵】 BY prod_name ASC【阿斯克】;

多列排序与优先级 当需要根据多个条件排序时,可以在 ORDER【欧朵】 BY 后列出多个列,它们具有明确的优先级:写在前面的字段优先级最高。数据库会先按第一个字段排序,当第一个字段的值相同时,再按第二个字段排序,以此类推 。

-- 先按价格降序,价格相同的再按产品名称升序排列
SELECT【涩莱克特】 prod_id, prod_price, prod_name
FROM【弗乱】 products
ORDER【欧朵】 BY prod_price desc, prod_name ASC【阿斯克】;

按列位置和别名排序 除了直接使用列名,还可以使用列在 SELECT【涩莱克特】 子句中的位置序号(从1开始)或定义的别名进行排序 。

-- 按位置排序:按SELECT【涩莱克特】中的第二列(prod_price)降序,第三列(prod_name)升序
SELECT【涩莱克特】 prod_id, prod_price, prod_name
FROM【弗乱】 products
ORDER【欧朵】 BY 2 desc, 3;
-- 按别名排序
SELECT【涩莱克特】 prod_id, prod_price AS price, prod_name
FROM【弗乱】 products
ORDER【欧朵】 BY price desc;

⚠️ 注意关键细节与技巧#

  1. 子句顺序:在 SQL 语句中,ORDER【欧朵】 BY 子句必须位于所有其他子句之后,即正确的书写顺序是:SELECT【涩莱克特】 -> FROM【弗乱】 -> WHERE【威尔】 -> GROUP【格鲁普】 BY -> HAVING【嗨夫营】 -> ORDER【欧朵】 BY
  2. NULL【nò】 值的排序:对于包含 NULL【nò】 值的列进行排序时,NULL【nò】 会被集中排列,但具体出现在结果集的开头还是末尾,不同的数据库管理系统可能有不同的默认行为 。
  3. 与聚合函数结合使用ORDER【欧朵】 BY 可以根据聚合函数的结果进行排序,这在数据统计和分析时非常有用 。
    -- 统计每个产品类型的数量,并按数量降序排列
    SELECT【涩莱克特】 product_type, COUNT【kàn特】(*)
    FROM【弗乱】 Product
    GROUP【格鲁普】 BY product_type
    ORDER【欧朵】 BY COUNT【kàn特】(*) desc;
  4. 性能考虑:对经常需要排序的列建立索引,可以显著提高排序查询的速度 。

💎 简单总结#

ORDER【欧朵】 BY 是控制查询结果展示顺序的关键。记住:ORDER【欧朵】 BY 默认升序,多列排序时左侧列优先级最高,并且它总是 SELECT【涩莱克特】 语句的最后一个子句。

desc : 降序

asc【阿斯克】:升序

命令操作如下所示:#

查看满足条件记录的name和uid 字段的值#
mysql> select【涩莱克特】 name , uid from【弗乱】 moershi.user where【威尔】uid is not【呐-特】 null【nò】 and uid between【B豚ing】 100 and 1000 ;
+-----------------+------+
| name | uid |
+-----------------+------+
| systemd-network | 192 |
| polkitd | 999 |
| chrony | 998 |
| haproxy | 188 |
| plj | 1000 |
+-----------------+------+
5 rows in set【赛特】 (0.00 sec)
按照uid升序排序#
mysql> select【涩莱克特】 name , uid from【弗乱】 moershi.user where【威尔】uid is not【呐-特】 null【nò】 and uid between【B豚ing】 100 and 1000 order【欧朵】 by uid asc【阿斯克】;
+-----------------+------+
| name | uid |
+-----------------+------+
| haproxy | 188 |
| systemd-network | 192 |
| chrony | 998 |
| polkitd | 999 |
| plj | 1000 |
+-----------------+------+
5 rows in set【赛特】 (0.00 sec)
按照uid降序排序#
mysql> select【涩莱克特】 name , uid from【弗乱】 moershi.user where【威尔】uid is not【呐-特】 null【nò】 and uid between【B豚ing】 100 and 1000 order【欧朵】 by uid desc;
+-----------------+------+
| name | uid |
+-----------------+------+
| plj | 1000 |
| polkitd | 999 |
| chrony | 998 |
| systemd-network | 192 |
| haproxy | 188 |
+-----------------+------+
5 rows in set【赛特】 (0.00 sec)
查看2015年1月10号员工编号小于10的工资总额#
mysql> select【涩莱克特】 employee【ing普洛伊】_id , date , basic【贝斯克】 , bonus【波诺斯】 , basic【贝斯克】+bonus【波诺斯】 as total【偷ò头ō】 from【弗乱】 moershi.salary【晒了瑞】 where【威尔】date=20150110 and employee【ing普洛伊】_id <= 10;
+-------------+------------+-----------------+----------------+-----------------+
| employee_id | date | basic【贝斯克】 | bonus【波诺斯】 | total【偷ò头ō】 |
+-------------+------------+-----------------+----------------+-----------------+
| 2 | 2015-01-10 | 17000 | 10000 | 27000 |
| 3 | 2015-01-10 | 8000 | 2000 | 10000 |
| 4 | 2015-01-10 | 14000 | 9000 | 23000 |
| 6 | 2015-01-10 | 14000 | 10000 | 24000 |
| 7 | 2015-01-10 | 19000 | 10000 | 29000 |
+-------------+------------+-----------------+----------------+-----------------+
5 rows in set【赛特】 (0.00 sec)
以工资总额升序排 ,总额相同按照员工编号升序排#
mysql> select【涩莱克特】 employee【ing普洛伊】_id , basic【贝斯克】+bonus【波诺斯】 as total【偷ò头ō】 from【弗乱】 moershi.salary【晒了瑞】 where【威尔】date=20150110 and employee【ing普洛伊】_id <= 10 order【欧朵】 by total【偷ò头ō】 asc【阿斯克】 ,employee【ing普洛伊】_id asc【阿斯克】;
+-------------+-------+
| employee_id | total【偷ò头ō】 |
+-------------+-------+
| 3 | 10000 |
| 4 | 23000 |
| 6 | 24000 |
| 2 | 27000 |
| 7 | 29000 |
+-------------+-------+
5 rows in set【赛特】 (0.00 sec)

五、分组后条件过滤#

当前任务环节

flowchart LR
subgraph BottomRow[单表增删改查]
direction LR
A[分组查询] --> B[对查询结果</n>进行排序] --> C[分组后条件过滤] --> D[新增数据] --> E[修改数据] --> F[删除数据]
end
classDef highlight stroke:#f00,stroke-width:2px;
class C highlight;

对分组后的组进行筛选: having【嗨夫营】#

特性HAVING【嗨夫营】 子句WHERE【威尔】 子句
核心功能分组后的组 进行筛选分组前的原始数据行 进行筛选
作用对象由 GROUP【格鲁普】 BY 产生的分组或聚合函数的结果来自表的单行记录
聚合函数可以且经常 使用(如 HAVING【嗨夫营】 SUM【桑à姆】(amount) > 100不可以 直接使用
执行顺序GROUP【格鲁普】 BY 之后GROUP【格鲁普】 BY 之前
典型应用筛选满足条件的组,例如”总销售额大于1万的部门”筛选满足条件的行,例如”年龄大于30岁的员工”

💡 理解 HAVING【嗨夫营】 的工作原理#

你可以将 SQL 查询的执行顺序理解为:FROM【弗乱】 → WHERE【威尔】 → GROUP【格鲁普】 BY → HAVING【嗨夫营】 → SELECT【涩莱克特】 → ORDER【欧朵】 BY

  1. FROM【弗乱】:首先从表中获取数据。
  2. WHERE【威尔】:接着,WHERE【威尔】 子句会像一道筛子,根据你设定的条件(比如 amount > 100)对原始数据行进行过滤,只保留符合条件的行。这一步会在分组之前有效减少需要处理的数据量 。
  3. GROUP【格鲁普】 BY:然后,数据库会按照指定的列对 WHERE【威尔】 过滤后的结果进行分组,将具有相同值的行归到同一组中。
  4. HAVING【嗨夫营】:最后,HAVING【嗨夫营】 子句登场,它基于聚合函数的结果(例如组的平均工资、总销售额等)对这些分组进行筛选,只保留满足条件的分组 。

🛠️ HAVING【嗨夫营】 的典型用法场景#

1. 基础用法:筛选聚合结果 这是 HAVING【嗨夫营】 最经典的场景,用于找出符合某项统计条件的组。

-- 找出总消费金额超过1000元的客户
SELECT【涩莱克特】 customer_id, SUM【桑à姆】(amount) AS total【偷ò头ō】_spending
FROM【弗乱】 orders
GROUP【格鲁普】 BY customer_id
HAVING【嗨夫营】 SUM【桑à姆】(amount) > 1000;

2. 结合 WHERE【威尔】 使用:提高查询效率 你可以同时使用 WHERE【威尔】HAVING【嗨夫营】,让 WHERE【威尔】 先过滤掉不需要的原始数据,减少分组计算的压力,再由 HAVING【嗨夫营】 完成最终筛选 。

-- 找出在2024年下单次数超过5次的客户
SELECT【涩莱克特】 customer_id, COUNT【kàn特】(order_id) AS order_count【kàn特】
FROM【弗乱】 orders
WHERE【威尔】 YEAR(order_date) = 2024 -- 先筛选出2024年的订单,提高效率
GROUP【格鲁普】 BY customer_id
HAVING【嗨夫营】 COUNT【kàn特】(order_id) > 5; -- 再筛选出订单数超过5的客户

3. 多条件组合HAVING【嗨夫营】 子句中,你可以使用 ANDOR 来连接多个聚合条件 。

-- 找出总消费超过2000元且平均订单金额大于500元的客户
SELECT【涩莱克特】 customer_id, SUM【桑à姆】(amount) AS total【偷ò头ō】, AVG(amount) AS avg_amount
FROM【弗乱】 orders
GROUP【格鲁普】 BY customer_id
HAVING【嗨夫营】 SUM【桑à姆】(amount) > 2000 AND AVG(amount) > 500;

⚠️ 常见误区与注意事项#

  • 不要在 HAVING【嗨夫营】 中过滤非聚合的原始字段:如果过滤条件不涉及聚合计算,只是针对某一行的原始字段,那么应该把它放在 WHERE【威尔】 子句,而不是 HAVING【嗨夫营】 中。放在 WHERE【威尔】 子句效率更高,因为它能在分组前就减少数据量 。当然,如果该字段是分组字段,则可以在 HAVING【嗨夫营】 中使用 。
  • HAVING【嗨夫营】 通常依赖于 GROUP【格鲁普】 BYHAVING【嗨夫营】 子句通常与 GROUP【格鲁普】 BY 一起出现。除非你的查询是对整个表进行聚合(例如 SELECT【涩莱克特】 COUNT【kàn特】(*) FROM【弗乱】 table【忒-部】 HAVING【嗨夫营】 COUNT【kàn特】(*) > 10),否则缺少 GROUP【格鲁普】 BY 会导致语法错误 。
  • 别名使用:在某些数据库系统中,HAVING【嗨夫营】 子句可以直接使用在 SELECT【涩莱克特】 中为聚合函数定义的别名,例如 HAVING【嗨夫营】 total【偷ò头ō】_spending > 1000,这可以使查询更简洁 。但为了更好的兼容性,直接在 HAVING【嗨夫营】 中写完整的聚合表达式是更稳妥的做法。

💎 简单总结#

记住一个核心原则:当你需要对 分组后的统计结果 设置条件时,就是 HAVING【嗨夫营】 子句大显身手的时候。它与 GROUP【格鲁普】 BY 是形影不离的好搭档,一个负责分组,一个负责对分组后的结果进行筛选 。

命令操作如下所示:#

查找部门总人数少于10人的部门名称及人数#
//第一步,查看所有员工的部门名称
select【涩莱克特】 dept_id , name from【弗乱】 moershi.employees【ing普洛伊s】;
//第二步,按部门编号分组 统计人名个数
mysql> select【涩莱克特】 dept_id , count【kàn特】(name) as numbers【nèn波s】 from【弗乱】 moershi.employees【ing普洛伊s】 group【格鲁普】 by dept_id;
+---------+-------------------+
| dept_id | numbers【nèn波s】 |
+---------+-------------------+
| 1 | 8 |
| 2 | 5 |
| 3 | 6 |
| 4 | 55 |
| 5 | 12 |
| 6 | 9 |
| 7 | 35 |
| 8 | 3 |
+---------+-------------------+
8 rows in set【赛特】 (0.00 sec)
//第三步,查找部门人数少于10人的部门名称及人数
mysql> select【涩莱克特】 dept_id , count【kàn特】(name) as numbers【nèn波s】 from【弗乱】 moershi.employees【ing普洛伊s】 group【格鲁普】 by dept_id having【嗨夫营】 numbers【nèn波s】 < 10;
+---------+-------------------+
| dept_id | numbers【nèn波s】 |
+---------+-------------------+
| 1 | 8 |
| 2 | 5 |
| 3 | 6 |
| 6 | 9 |
| 8 | 3 |
+---------+-------------------+
5 rows in set【赛特】 (0.00 sec)

六、分页查询 limit【类梅特】#

在 MySQL 中,LIMIT【类梅特】是用于实现分页查询的关键字,它能够精确控制查询结果返回的记录条数和起始位置 。下面这个表格能帮你快速抓住它的核心用法。

特性语法格式A语法格式B (MySQL 8.0+ 推荐)说明
基本语法LIMIT【类梅特】 offset【哦夫赛特】, row_countLIMIT【类梅特】 row_count OFFSET【哦夫赛特】 offset【哦夫赛特】从第 offset【哦夫赛特】 条记录开始,返回 row_count 条记录。
偏移量(offset【哦夫赛特】)0 开始计数0 开始计数例如,offset【哦夫赛特】 为 0 表示从第一条记录开始。
简化查询LIMIT【类梅特】 row_count-等同于 LIMIT【类梅特】 0, row_count,返回前 row_count 条。
分页公式LIMIT【类梅特】 (pageNum - 1) * pageSize, pageSizeLIMIT【类梅特】 pageSize OFFSET【哦夫赛特】 (pageNum - 1) * pageSizepageNum: 当前页码(从1开始); pageSize: 每页条数。

💡 如何使用 LIMIT【类梅特】 进行分页#

掌握 LIMIT【类梅特】 的最佳方式就是动手实践。假设我们有一张 users 表,需要实现每页显示10条记录的分页查询。

  • 查询第1页数据

    -- 使用语法A
    SELECT【涩莱克特】 * FROM【弗乱】 users ORDER BY id LIMIT【类梅特】 0, 10;
    -- 使用语法B (推荐,语义更清晰)
    SELECT【涩莱克特】 * FROM【弗乱】 users ORDER BY id LIMIT【类梅特】 10 OFFSET【哦夫赛特】 0;

    这两条语句是等价的,都返回最前面的10条记录。

  • 查询第2页数据

    -- 偏移量 offset【哦夫赛特】 = (2 - 1) * 10 = 10
    SELECT【涩莱克特】 * FROM【弗乱】 users ORDER BY id LIMIT【类梅特】 10, 10;
    -- 或
    SELECT【涩莱克特】 * FROM【弗乱】 users ORDER BY id LIMIT【类梅特】 10 OFFSET【哦夫赛特】 10;

    这里从第11条记录开始(偏移10条),再取10条记录。

⚠️ 重要提示分页查询必须与 ORDER BY 子句配合使用,以确保每次查询结果的顺序一致。否则,数据库返回的记录顺序可能是随机的,导致分页混乱。

🚀 如何优化大偏移量下的分页性能#

当需要查询的页码非常靠后时(例如 LIMIT【类梅特】 100000, 10),直接使用 LIMIT【类梅特】 会产生性能问题,因为数据库需要先扫描并丢弃掉偏移量指定的前大量记录,效率很低。

一个高效的优化策略是使用基于主键(或唯一索引)的范围查询来替代大的偏移量。这种方法的原理是记录上一页最后一条记录的唯一ID,然后以此为起点获取下一页。

-- 假设已知上一页最后一条记录的id是10000
-- 传统的低效写法:SELECT【涩莱克特】 * FROM【弗乱】 table【忒-部】 ORDER BY id LIMIT【类梅特】 10000, 10;
-- 优化后的高效写法:
SELECT【涩莱克特】 * FROM【弗乱】 table【忒-部】 WHERE【威尔】 id > 10000 ORDER BY id LIMIT【类梅特】 10;

这种方法通过索引直接定位到起始位置,跳过了大量数据的扫描,使得查询速度与页码基本无关。它非常适合实现“上一页/下一页”这种连续翻页的场景。

💎 简单总结#

LIMIT【类梅特】 是实现MySQL分页的核心。记住:务必搭配 ORDER BY 保证顺序,在需要跳转到很靠后的页面时,尽量使用基于唯一ID的范围查询来优化性能

命令操作如下所示:#

关键字:limit【类梅特】 数字1,数字2

数字1:起始行

数字2:每页显示总行数

例如:

limit【类梅特】 1 ; 显示查询结果的第1行
limit【类梅特】 3 ; 显示查询结果的前3行
limit【类梅特】 10 ; 显示查询结果的前10行
limit【类梅特】 0,1 ; 从查询结果的第1行开始显示,共显示1行
limit【类梅特】 3,5 ; 从查询结果的第4行开始显示,共显示5行
limit【类梅特】 10,10; 从查询结果的第11行开始显示,共显示10行
查看有解释器的用户信息#
mysql> select【涩莱克特】 * from【弗乱】moershi.user where【威尔】shell is not【呐-特】 null【nò】 ;
只显示查询结果的第1行#
mysql> select【涩莱克特】 * from【弗乱】moershi.user where【威尔】shell is not【呐-特】 null【nò】 limit【类梅特】 1;
只显示查询结果的前3行#
mysql> select【涩莱克特】 * from【弗乱】moershi.user where【威尔】shell is not【呐-特】 null【nò】 limit【类梅特】 3;
仅仅显示查询结果的第1行 到 第3 (0 表示查询结果的第1行)#
mysql> select【涩莱克特】 * from【弗乱】user where【威尔】shell is not【呐-特】 null【nò】 limit【类梅特】 0,3;
从查询结果的第4行开始显示,共显示3行#
mysql> select【涩莱克特】 name,uid , gid , shell from【弗乱】user where【威尔】shell is not【呐-特】 null【nò】 limit【类梅特】 3,3;
查看uid 号最大的用户名和UID#
mysql> select【涩莱克特】 name , uid from【弗乱】 moershi.user order【欧朵】 by uid desc limit【类梅特】 1 ;
+-----------+-------+
| name | uid |
+-----------+-------+
| nfsnobody | 65534 |
+-----------+-------+
1 row in set【赛特】 (0.00 sec)

六、新增数据#

当前任务环节

flowchart LR
subgraph BottomRow[单表增删改查]
direction LR
A[分组查询] --> B[对查询结果</n>进行排序] --> C[分组后条件过滤] --> D[新增数据] --> E[修改数据] --> F[删除数据]
end
classDef highlight stroke:#f00,stroke-width:2px;
class D highlight;

命令操作如下所示:

查看表头

desc#

在SQL中,desc 是一个常用的关键字,但它根据使用场景的不同,主要有两种截然不同的含义和用法。为了帮助你快速区分,我先用一个表格来总结它的核心用途。

用途分类语法/上下文核心功能简要说明
查看表结构desc table【忒-部】_name;显示数据表的详细结构信息(列名、类型、约束等)。主要用于数据库管理和调试,是一个命令
指定查询排序ORDER BY column【科勒姆】_name desc;在查询结果中,将指定列按降序(从大到小)排列。必须与 ORDER BY 子句搭配使用,是一个关键字

📊 作为表结构查看命令#

这是 desc 命令最直接的用途,相当于数据表的“说明书”。执行后,它会返回一个包含以下字段的表格,帮助你全面了解表的设计 :

输出列含义说明备注/示例
Field表中每一列(字段)的名称。例如 id, name, email
Type该列字段的数据类型。例如 int(11), varchar(255), date
Null【nò】该列是否允许存储空值(NULL【nò】)。YES 表示允许,NO 表示不允许。
Key表示该列是否定义了键(索引)。PRI(主键),UNI(唯一索引),MUL(可重复的非唯一索引前导列或唯一索引的组成部分但可含空值),或为空(无索引)。
Default该列的默认值。如果未显式赋值且未指定默认值,多数情况下显示 NULL【nò】例如,可设置为 CURRENT_TIMESTAMP
Extra额外信息,如 auto_increment(自动递增)。常见于主键ID列。

如何使用:

-- 查看名为 'employees【ing普洛伊s】' 的表结构
desc employees【ing普洛伊s】;

💡 如何避免混淆?#

记住一个简单的规则:看它的位置

  • 如果 desc 后面直接跟着表名(如 desc employees【ing普洛伊s】;),它就是查看表结构的命令。
  • 如果 desc 前面有 ORDER BY 关键字(如 ORDER BY salary【晒了瑞】 desc;),它就是指定降序排序的关键字。

命令操作如下所示:#

mysql> desc moershi.user;
+---------------------+-------------+------------+-----+---------------------+----------------+
| Field | Type | Null【nò】 | Key| Default【迪-佛特】 | Extra |
+---------------------+-------------+------------+-----+---------------------+----------------+
| id | int | NO | PRI | NULL【nò】 | auto_increment |
| name | char(20) | YES | | NULL【nò】 | |
| password | char(1) | YES | | NULL【nò】 | |
| uid | int | YES | | NULL【nò】 | |
| gid | int | YES | | NULL【nò】 | |
| comment【坑门特】 | varchar(50) | YES | | NULL【nò】 | |
| homedir【home-跌儿】 | varchar(80) | YES | | NULL【nò】 | |
| shell | char(30) | YES | | NULL【nò】 | |
+---------------------+-------------+------------+-----+---------------------+----------------+
8 rows in set【赛特】 (0.00 sec)

插入1条记录给所有表头赋值#

(给所有表头赋值表头可以省略不写)id表头的值不能重复,主键的知识在后边课程里讲

mysql> insert【因涩特】 into【因兔】 moershi.user values【挖柳斯】(40,"jingyaya","x",1001,1001,"teacher","/home/jingyaya","/bin/bash");
Query OK, 1 row affected (0.05 sec)

查看表记录#

mysql> select【涩莱克特】 * from【弗乱】 moershi.user where【威尔】name="jingyaya";
+----+----------+----------+------+------+------------------+------------------------+-----------+
| id | name | password | uid | gid | comment【坑门特】 | homedir【home-跌儿】 | shell |
+----+----------+----------+------+------+------------------+------------------------+-----------+
| 40 | jingyaya | x | 1001 | 1001 | teacher | /home/jingyaya | /bin/bash |
+----+----------+----------+------+------+------------------+------------------------+-----------+
1 row in set【赛特】 (0.00 sec)
mysql>

插入多行记录给所有列赋值#

insert【因涩特】 into【因兔】 moershi.user values【挖柳斯】
(41,"jingyaya2","x",1002,1002,"teacher","/home/jingyaya2","/bin/bash"),
(42,"jingyaya3","x",1003,1003,"teacher","/home/jingyaya3","/bin/bash");

插入1行给指定列赋值,必须写列名,没赋值的列 没有数据 后通过设置的默认值赋值#

mysql> insert【因涩特】 into【因兔】 moershi.user(name,uid,shell)values【挖柳斯】("benben",1002,"/sbin/nologin");

插入多行给指定列赋值,必须写列名,没赋值的列 没有数据 后通过设置的默认值赋值#

mysql> insert【因涩特】 into【因兔】 moershi.user(name,uid,shell)values【挖柳斯】("benben2",1002,"/sbin/nologin"),("benben3",1003,"/sbin/nologin");

查看记录#

mysql> select【涩莱克特】 * from【弗乱】moershi.user where【威尔】name like "benben%";
+----+---------+----------+------+------+-------------------+---------------------+------------------------------+
| id | name | password | uid | gid | comment【坑门特】 | homedir【home-跌儿】 | shell |
+----+---------+----------+------+------+-------------------+---------------------+------------------------------+
| 41 | benben | NULL | 1002 | NULL | NULL | NULL | /sbin/nologin【诺劳京(白话)】 |
| 42 | benben2 | NULL | 1002 | NULL | NULL | NULL | /sbin/nologin【诺劳京(白话)】 |
| 43 | benben3 | NULL | 1003 | NULL | NULL | NULL | /sbin/nologin【诺劳京(白话)】 |
+----+---------+----------+------+------+-------------------+---------------------+------------------------------+
3 rows in set【赛特】 (0.00 sec)

使用select【涩莱克特】查询结果赋值(查询表头个数和 插入记录命令表头个数要一致)#

mysql> select【涩莱克特】 user from【弗乱】 mysql.user;
+------------------+
| user |
+------------------+
| mysql.infoschema |
| mysql.session |
| mysql.sys |
| root |
+------------------+
4 rows in set【赛特】 (0.00 sec)
mysql> insert【因涩特】 into【因兔】 moershi.user(name) (select【涩莱克特】 user from【弗乱】 mysql.user);
Query OK, 4 rows affected (0.09 sec)
Records: 4 Duplicates: 0 Warnings: 0

查看插入后的数据#

mysql> select【涩莱克特】 * from【弗乱】 moershi.user where【威尔】name like "mysql%" or name="root";
+----+------------------+----------+------+------+-----------------------+------------------------+------------+
| id | name | password | uid | gid | comment【坑门特】 | homedir【home-跌儿】 | shell |
+----+------------------+----------+------+------+-----------------------+------------------------+------------+
| 1 | root | x | 0 | 0 | root | /root | /bin/bash |
| 26 | mysql | x | 27 | 27 | MySQL Server | /var/lib/mysql | /bin/false |
| 44 | mysql.infoschema | NULL | NULL | NULL | NULL | NULL | NULL |
| 45 | mysql.session | NULL | NULL | NULL | NULL | NULL | NULL |
| 46 | mysql.sys | NULL | NULL | NULL | NULL | NULL | NULL |
| 47 | root | NULL | NULL | NULL | NULL | NULL | NULL |
+----+------------------+----------+------+------+-----------------------+------------------------+------------+
6 rows in set【赛特】 (0.00 sec)

使用set【赛特】命令赋值#

mysql> insert【因涩特】 into【因兔】 moershi.user set【赛特】 name="yaya" , uid=99 , gid=99 ;
Query OK, 1 row affected (0.06 sec)
mysql> select【涩莱克特】 * from【弗乱】 moershi.user where【威尔】name="yaya";
+----+------+----------+------+------+------------------+----------------------+-------+
| id | name | password | uid | gid | comment【坑门特】 | homedir【home-跌儿】 | shell |
+----+------+----------+------+------+------------------+----------------------+-------+
| 28 | yaya | NULL | 99 | 99 | NULL | NULL | NULL |
+----+------+----------+------+------+------------------+----------------------+-------+
1 row in set【赛特】 (0.00 sec)

七、修改数据#

当前任务环节

flowchart LR
subgraph BottomRow[单表增删改查]
direction LR
A[分组查询] --> B[对查询结果</n>进行排序] --> C[分组后条件过滤] --> D[新增数据] --> E[修改数据] --> F[删除数据]
end
classDef highlight stroke:#f00,stroke-width:2px;
class E highlight;

命令操作如下所示:#

//修改前查看
mysql> select【涩莱克特】 name , comment【坑门特】 from【弗乱】moershi.user where【威尔】id <= 10 ;
+----------+--------------------+
| name | comment【坑门特】 |
+----------+--------------------+
| root | root |
| bin | bin |
| daemon | daemon |
| adm | adm |
| lp | lp |
| sync | sync |
| shutdown | shutdown |
| halt | halt |
| mail | mail |
| operator | operator |
+----------+--------------------+
10 rows in set【赛特】 (0.00 sec)
//修改符合条件
mysql> update【阿普 dei 特】 moershi.user set【赛特】 comment【坑门特】=NULL【nò】 where【威尔】id <= 10 ;
Query OK, 10 rows affected (0.09 sec)
Rows matched: 10 Changed: 10 Warnings: 0
//修改后查看
mysql> select【涩莱克特】 name , comment【坑门特】 from【弗乱】moershi.user where【威尔】id <= 10 ;
+----------+------------------+
| name | comment【坑门特】 |
+----------+------------------+
| root | NULL |
| bin | NULL |
| daemon | NULL |
| adm | NULL |
| lp | NULL |
| sync | NULL |
| shutdown | NULL |
| halt | NULL |
| mail | NULL |
| operator | NULL |
+----------+------------------+
10 rows in set【赛特】 (0.00 sec)
[root@localhost ~]#
//修改前查看
mysql> select【涩莱克特】 name , homedir from【弗乱】moershi.user;
+------------------+--------------------+
| name | homedir【home-跌儿】|
+------------------+--------------------+
| root | /root |
| bin | /bin |
| daemon | /sbin |
| adm | /var/adm |
| lp | /var/spool/lpd |
| sync | /sbin |
| shutdown | /sbin |
| halt | /sbin |
| mail | /var/spool/mail |
| operator | /root |
| games | /usr/games |
| ftp | /var/ftp |
| nobody | / |
| systemd-network | / |
| dbus | / |
| polkitd | / |
| sshd | /var/empty/sshd |
| postfix | /var/spool/postfix |
| chrony | /var/lib/chrony |
| rpc | /var/lib/rpcbind |
| rpcuser | /var/lib/nfs |
| nfsnobody | /var/lib/nfs |
| haproxy | /var/lib/haproxy |
| plj | /home/plj |
| apache | /usr/share/httpd |
| mysql | /var/lib/mysql |
| bob | NULL |
| jerrya | NULL |
| jingyaya | /home/jingyaya |
| benben | NULL |
| benben2 | NULL |
| benben3 | NULL |
| mysql.infoschema | NULL |
| mysql.session | NULL |
| mysql.sys | NULL |
| root | NULL |
+------------------+--------------------+
36 rows in set【赛特】 (0.00 sec)
//不加条件批量修改
mysql> update【阿普 dei 特】 moershi.user set【赛特】 homedir="/student" ;
Query OK, 36 rows affected (0.09 sec)
Rows matched: 36 Changed: 36 Warnings: 0
//修改后查看
mysql> select【涩莱克特】 name , homedir from【弗乱】moershi.user;
+------------------+---------------------+
| name | homedir【home-跌儿】|
+------------------+---------------------+
| root | /student |
| bin | /student |
| daemon | /student |
| adm | /student |
| lp | /student |
| sync | /student |
| shutdown | /student |
| halt | /student |
| mail | /student |
| operator | /student |
| games | /student |
| ftp | /student |
| nobody | /student |
| systemd-network | /student |
| dbus | /student |
| polkitd | /student |
| sshd | /student |
| postfix | /student |
| chrony | /student |
| rpc | /student |
| rpcuser | /student |
| nfsnobody | /student |
| haproxy | /student |
| plj | /student |
| apache | /student |
| mysql | /student |
| bob | /student |
| jerrya | /student |
| jingyaya | /student |
| benben | /student |
| benben2 | /student |
| benben3 | /student |
| mysql.infoschema | /student |
| mysql.session | /student |
| mysql.sys | /student |
| root | /student |
+------------------+---------------------+
36 rows in set【赛特】 (0.00 sec)

八、删除数据#

当前任务环节

flowchart LR
subgraph BottomRow[单表增删改查]
direction LR
A[分组查询] --> B[对查询结果</n>进行排序] --> C[分组后条件过滤] --> D[新增数据] --> E[修改数据] --> F[删除数据]
end
classDef highlight stroke:#f00,stroke-width:2px;
class F highlight;

命令操作如下所示:

//删除前查看
mysql> select【涩莱克特】 * from【弗乱】moershi.user where【威尔】id <= 10 ;
+----+----------+----------+------+------+------------------+---------------------+----------------+
| id | name | password | uid | gid | comment【坑门特】 | homedir【home-跌儿】| shell |
+----+----------+----------+------+------+------------------+---------------------+----------------+
| 1 | root | x | 0 | 0 | NULL | /student | /bin/bash |
| 2 | bin | x | 1 | 1 | NULL | /student | /sbin/nologin |
| 3 | daemon | x | 2 | 2 | NULL | /student | /sbin/nologin |
| 4 | adm | x | 3 | 4 | NULL | /student | /sbin/nologin |
| 5 | lp | x | 4 | 7 | NULL | /student | /sbin/nologin |
| 6 | sync | x | 5 | 0 | NULL | /student | /bin/sync |
| 7 | shutdown | x | 6 | 0 | NULL | /student | /sbin/shutdown |
| 8 | halt | x | 7 | 0 | NULL | /student | /sbin/halt |
| 9 | mail | x | 8 | 12 | NULL | /student | /sbin/nologin |
| 10 | operator | x | 11 | 0 | NULL | /student | /sbin/nologin |
+----+----------+----------+------+------+------------------+---------------------+----------------+
10 rows in set【赛特】 (0.00 sec)
//仅删除与条件匹配的行
mysql> delete【迪利特】 from【弗乱】moershi.user where【威尔】id <= 10 ;
Query OK, 10 rows affected (0.06 sec)
//查不到符合条件的记录了
mysql> select【涩莱克特】 * from【弗乱】moershi.user where【威尔】id <= 10 ;
Empty set【赛特】 (0.00 sec)

九、作业#

1 选择题#

  1. employees【ing普洛伊s】 表中,要将查询结果按照 hire_date 降序排列,应使用以下哪个关键字?B A. ASC【阿斯克】 B. desc C. GROUP【格鲁普】 BY D. HAVING【嗨夫营】

  2. 若要在 departments【迪帕踢门特s】 表中新增一条记录,dept_name 为 ‘市场部’,以下 SQL 语句正确的是? A A. INSERT【因涩特】 INTO【因兔】 departments【迪帕踢门特s】 (dept_name) VALUES【挖柳斯】 ('市场部'); B. UPDATE【阿普 dei 特】 departments【迪帕踢门特s】 SET【赛特】 dept_name = '市场部'; C. DELETE【迪利特】 from【弗乱】departments【迪帕踢门特s】 where【威尔】dept_name = '市场部'; D. SELECT * from【弗乱】departments【迪帕踢门特s】 where【威尔】dept_name = '市场部';

  3. salary【晒了瑞】 表中,要统计每个员工的总薪资(basic【贝斯克】bonus【波诺斯】 之和),并按照总薪资降序排列,以下 SQL 语句正确的是?A A. SELECT employee【ing普洛伊】_id, SUM(basic【贝斯克】 + bonus【波诺斯】) AS total【偷ò头ō】_salary【晒了瑞】 from【弗乱】salary【晒了瑞】 GROUP【格鲁普】 BY employee【ing普洛伊】_id ORDER【欧朵】 BY total【偷ò头ō】_salary【晒了瑞】 desc; B. SELECT employee【ing普洛伊】_id, AVG(basic【贝斯克】 + bonus【波诺斯】) AS total【偷ò头ō】_salary【晒了瑞】 from【弗乱】salary【晒了瑞】 GROUP【格鲁普】 BY employee【ing普洛伊】_id ORDER【欧朵】 BY total【偷ò头ō】_salary【晒了瑞】 desc; C. SELECT employee【ing普洛伊】_id, SUM(basic【贝斯克】 + bonus【波诺斯】) AS total【偷ò头ō】_salary【晒了瑞】 from【弗乱】salary【晒了瑞】 ORDER【欧朵】 BY total【偷ò头ō】_salary【晒了瑞】 desc; D. SELECT employee【ing普洛伊】_id, AVG(basic【贝斯克】 + bonus【波诺斯】) AS total【偷ò头ō】_salary【晒了瑞】 from【弗乱】salary【晒了瑞】 ORDER【欧朵】 BY total【偷ò头ō】_salary【晒了瑞】 desc;

  4. 在分组查询中,HAVING【嗨夫营】 子句的作用是?B A. 对分组前的数据进行过滤 B. 对分组后的数据进行过滤 C. 对查询结果进行排序 D. 对数据进行分组

  5. 要删除 employees【ing普洛伊s】 表中 employee【ing普洛伊】_id 为 10 的记录,以下 SQL 语句正确的是?B A. UPDATE【阿普 dei 特】 employees SET【赛特】 employee_id = NULL【nò】 where【威尔】employee_id = 10; B. DELETE【迪利特】 from【弗乱】employees where【威尔】employee_id = 10; C. SELECT * from【弗乱】employees where【威尔】employee_id = 10; D. INSERT【因涩特】 INTO【因兔】 employees (employee_id) VALUES【挖柳斯】 (10);

2 简答题#

  1. 简述 WHERE 子句和 HAVING 子句的区别。 答: WHERE是对分组前的原始数据行进行筛选 HAVING是对分组后的组进行筛选

  2. UPDATE 语句中,SET 关键字的作用是什么? 答:SET 关键字的作用是指定要修改的内容

  3. 新增数据时,INSERT INTO 语句有哪几种常见的使用方式? 答:1、向表中插入一条完整记录。 2、向表中插入部分字段的值(未指定的字段将采用默认值或自动生成的值,如自增主键)。 3、一次性插入多条记录(批量插入)。 4、结合 SELECT语句,将查询结果插入到另一个表中。

3 操作题#

  1. employees 表中新增一条记录,name 为 ‘李四’,hire_date 为 ‘2025-06-01’,birth_date 为 ‘1990-05-10’,email 为 ‘lisi@example.com’,phone_number 为 ‘13800138000’,dept_id 为 2。
mysql> insert into moershi.employees values(134,"李四","2025-06-01","1990-05-10","lisi@example.com",13800138000,2);
Query OK, 1 row affected (0.19 sec)
mysql> select * from moershi.employees where employee_id=134;
+-------------+--------+------------+------------+------------------+--------------+---------+
| employee_id | name | hire_date | birth_date | email | phone_number | dept_id |
+-------------+--------+------------+------------+------------------+--------------+---------+
| 134 | 李四 | 2025-06-01 | 1990-05-10 | lisi@example.com | 13800138000 | 2 |
+-------------+--------+------------+------------+------------------+--------------+---------+
1 row in set (0.00 sec)
  1. departments 表中 dept_id 为 3 的 dept_name 修改为 ‘研发部’。
mysql> select * from moershi.departments where dept_id=3;
+---------+-----------+
| dept_id | dept_name |
+---------+-----------+
| 3 | 运维部 |
+---------+-----------+
1 row in set (0.00 sec)
mysql> update moershi.departments set dept_name="研发部" where dept_id=3;
Query OK, 1 row affected (0.13 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from moershi.departments where dept_id=3;
+---------+-----------+
| dept_id | dept_name |
+---------+-----------+
| 3 | 研发部 |
+---------+-----------+
1 row in set (0.00 sec)
  1. 统计 salary 表中每个员工的平均基本工资(basic 列),只显示平均基本工资大于 5000 的员工信息,并按照平均基本工资降序排列。
mysql> select employee_id,avg(basic) as 平均工资 from moershi.salary group by employee_id having avg(basic) > 5000 order by 平均工资 desc;
+-------------+--------------+
| employee_id | 平均工资 |
+-------------+--------------+
| 103 | 27023.3333 |
| 19 | 26520.0000 |
| 104 | 24251.1429 |
| 117 | 23905.0000 |
| 8 | 23741.5600 |
| 86 | 22766.9444 |
| 112 | 22766.9444 |
| 10 | 22314.9286 |
| 16 | 22314.9286 |
| 102 | 21911.2462 |
| 119 | 21628.2083 |
| 118 | 21628.2083 |
| 7 | 21628.2083 |
| 106 | 21628.2083 |
| 105 | 20490.1528 |
| 122 | 20490.1528 |
| 91 | 20311.2979 |
| 76 | 19351.5972 |
| 88 | 19351.5972 |
| 2 | 19351.5972 |
| 95 | 18213.9028 |
| 98 | 18213.9028 |
| 123 | 18213.9028 |
| 92 | 18213.9028 |
| 17 | 18213.9028 |
| 107 | 18015.6818 |
| 80 | 17644.5789 |
| 1 | 17279.1000 |
| 5 | 17125.8000 |
| 110 | 17074.6528 |
| 108 | 17074.6528 |
| 13 | 17074.6528 |
| 131 | 16653.7692 |
| 125 | 16525.1887 |
| 127 | 16427.4107 |
| 81 | 15991.9286 |
| 4 | 15936.5972 |
| 6 | 15936.5972 |
| 11 | 15936.5972 |
| 12 | 15506.8125 |
| 85 | 14798.0417 |
| 79 | 14798.0417 |
| 99 | 14798.0417 |
| 75 | 13760.0417 |
| 129 | 13659.9861 |
| 94 | 13659.9861 |
| 96 | 13659.9861 |
| 124 | 13659.9861 |
| 100 | 13659.9861 |
| 9 | 12931.4545 |
| 115 | 12521.2500 |
| 14 | 11383.2083 |
| 120 | 11044.4242 |
| 15 | 11044.4242 |
| 128 | 10337.5000 |
| 78 | 10244.4722 |
| 126 | 10244.4722 |
| 84 | 10244.4722 |
| 113 | 10244.4722 |
| 132 | 10244.4722 |
| 83 | 9106.9444 |
| 121 | 9106.9444 |
| 116 | 9106.9444 |
| 3 | 9106.9444 |
| 130 | 9106.9444 |
| 82 | 7967.6944 |
| 93 | 7967.6944 |
| 109 | 7967.6944 |
| 77 | 7967.6944 |
| 87 | 7281.6410 |
| 89 | 6829.6389 |
| 101 | 6829.6389 |
| 18 | 6829.6389 |
| 97 | 6829.6389 |
| 114 | 5690.9028 |
| 111 | 5690.9028 |
| 90 | 5690.9028 |
| 133 | 5690.9028 |
+-------------+--------------+
78 rows in set (0.01 sec)
mysql>
要统计 `salary` 表中每个员工的平均基本工资,并筛选出平均工资大于5000且按降序排列,可以使用以下SQL语句。其核心在于使用 `GROUP BY` 对员工分组,使用 `HAVING` 对分组后的聚合结果进行过滤。
### 🔍 查询语句及说明
完整的SQL查询语句如下:
SELECT employee_id, AVG(basic) AS avg_basic_salary
FROM salary
GROUP BY employee_id
HAVING AVG(basic) > 5000
ORDER BY avg_basic_salary DESC;
下面这个表格详细解释了每个子句的作用:
| SQL 子句 | 作用说明 | 备注 |
| :--- | :--- | :--- |
| `SELECT employee_id, AVG(basic) AS avg_basic_salary` | 选择员工编号和计算出的平均基本工资,并为平均工资列设置别名 `avg_basic_salary`。 | `AVG()` 是聚合函数,用于计算平均值。 |
| `FROM salary` | 指定查询的数据来源是 `salary` 表。 | - |
| `GROUP BY employee_id` | 根据 `employee_id` 进行分组。这是使用聚合函数(如`AVG`)的前提,它将数据按每个员工归类。 | 确保 `SELECT` 中非聚合列在 `GROUP BY` 中出现。 |
| `HAVING AVG(basic) > 5000` | 对分组(即每个员工)后的结果进行筛选,只保留平均基本工资大于5000的记录。**`HAVING` 专门用于过滤聚合函数的结果**。 | 这与在分组前过滤行的 `WHERE` 子句有本质区别。 |
| `ORDER BY avg_basic_salary DESC` | 将最终结果按照平均基本工资(别名)从高到低进行排序。 | `DESC` 表示降序,`ASC` 表示升序。 |
### 💡 关键点与技巧
- **`HAVING` 与 `WHERE` 的区别**:这是编写此类查询的关键。`WHERE` 子句在 **分组之前** 过滤原始数据行,并且其条件中不能直接使用聚合函数(如 `AVG(), COUNT()`)。`HAVING` 子句在 **分组之后** 过滤分组结果,条件通常包含聚合函数。如果你的过滤条件不涉及聚合计算(例如 `employee_id > 100`),则应使用 `WHERE`,这能先减少数据量,提升查询效率。
- **使用列别名**:在 `ORDER BY` 子句中,我们可以直接使用在 `SELECT` 中定义的列别名(`avg_basic_salary`),这使得语句更简洁易读。但请注意,在某些数据库系统中,`HAVING` 子句可能不支持直接使用别名,此时需要重复聚合函数(如 `HAVING AVG(basic) > 5000`),如示例中所示。
### 📚 扩展思考
你可以通过修改聚合函数和条件来满足不同的统计需求。例如:
- 统计每个员工的**总工资**(假设还有其他工资项):`SELECT employee_id, SUM(basic + bonus) AS total_salary FROM salary GROUP BY employee_id HAVING SUM(basic + bonus) > 10000 ORDER BY total_salary DESC;`
- 先通过 `WHERE` 子句排除某些记录后再进行分组统计,例如,只统计某个月份之后的数据。
  1. 删除 employees 表中 dept_id 为 4 的所有记录。
mysql> --查询删除前数据
mysql> select * from moershi.employees where dept_id=4;
+-------------+-----------+------------+------------+---------------------------+--------------+---------+
| employee_id | name | hire_date | birth_date | email | phone_number | dept_id |
+-------------+-----------+------------+------------+---------------------------+--------------+---------+
| 20 | 蒋红 | 2017-12-29 | 1978-01-24 | jianghong@tedu.cn | 15852915398 | 4 |
| 21 | 曹宁 | 2004-06-07 | 1988-01-05 | caoning@tedu.cn | 14513022304 | 4 |
| 22 | 吕刚 | 2014-01-18 | 1980-06-24 | lvgang@tedu.cn | 18136987619 | 4 |
| 23 | 王莉 | 2021-02-04 | 1972-12-19 | wangli@moershi.com | 15376329290 | 4 |
| 24 | 邓秀芳 | 2020-09-08 | 1980-12-12 | dengxiufang@moershi.com | 15884927117 | 4 |
| 25 | 邵佳 | 2011-08-15 | 1978-11-28 | shaojia@tedu.cn | 13296016750 | 4 |
| 26 | 党丽 | 2014-03-14 | 1978-12-30 | dangli@moershi.com | 13166234580 | 4 |
| 27 | 梁勇 | 2007-01-19 | 1997-11-12 | liangyong@tedu.cn | 15307868657 | 4 |
| 28 | 郑秀珍 | 2011-04-28 | 1972-09-01 | zhengxiuzhen@moershi.com | 14543186401 | 4 |
| 29 | 胡秀云 | 2003-11-03 | 2000-05-14 | huxiuyun@tedu.cn | 18212266720 | 4 |
| 30 | 邢淑兰 | 2011-07-02 | 1989-06-17 | xingshulan@moershi.com | 13032270370 | 4 |
| 31 | 刘海燕 | 2018-01-06 | 1982-08-21 | liuhaiyan@moershi.com | 15064354651 | 4 |
| 32 | 冯建国 | 2014-05-28 | 1980-01-26 | fengjianguo@tedu.cn | 13344957322 | 4 |
| 33 | 曹杰 | 2017-01-04 | 1975-07-03 | caojie@moershi.com | 13244741822 | 4 |
| 34 | 苗桂花 | 2001-10-06 | 1974-01-26 | miaoguihua@tedu.cn | 18124884107 | 4 |
| 35 | 袁建平 | 2020-09-17 | 1990-07-25 | yuanjianping@moershi.com | 13580624147 | 4 |
| 36 | 黄淑兰 | 2019-08-29 | 1988-02-08 | huangshulan@tedu.cn | 14568738205 | 4 |
| 37 | 朱淑兰 | 2002-04-26 | 1977-12-10 | zhushulan@tedu.cn | 13635439422 | 4 |
| 38 | 曹凯 | 2006-07-23 | 1995-11-07 | caokai@moershi.com | 15172694474 | 4 |
| 39 | 张倩 | 2009-10-27 | 2000-04-27 | zhangqian@tedu.cn | 15053279648 | 4 |
| 40 | 王淑珍 | 2013-09-21 | 1995-05-06 | wangshuzhen@tedu.cn | 18583012709 | 4 |
| 41 | 陈玉 | 2011-08-20 | 1985-06-14 | chenyu@tedu.cn | 13632957562 | 4 |
| 42 | 陈玉英 | 2020-10-03 | 1996-05-19 | chenyuying@tedu.cn | 15100520600 | 4 |
| 43 | 王波 | 2008-06-14 | 1979-05-26 | wangbo@tedu.cn | 15236208918 | 4 |
| 44 | 黄文 | 2010-06-05 | 1983-01-30 | huangwen@tedu.cn | 13688719481 | 4 |
| 45 | 陈刚 | 2010-10-26 | 1978-05-10 | chengang@tedu.cn | 13679861175 | 4 |
| 46 | 罗建华 | 2004-06-20 | 1989-02-23 | luojianhua@tedu.cn | 13176305978 | 4 |
| 47 | 黄建平 | 2009-04-09 | 1995-07-15 | huangjianping@moershi.com | 18517722322 | 4 |
| 48 | 范秀英 | 2001-03-13 | 1996-06-01 | fanxiuying@moershi.com | 18252125689 | 4 |
| 49 | 李平 | 2003-10-28 | 1998-07-24 | liping@tedu.cn | 18526198243 | 4 |
| 50 | 臧龙 | 2011-10-06 | 1976-05-11 | zanglong@moershi.com | 13474064425 | 4 |
| 51 | 吴静 | 2017-09-26 | 1983-08-04 | wujing@tedu.cn | 14762137325 | 4 |
| 52 | 张冬梅 | 2010-10-26 | 1982-05-09 | zhangdongmei@tedu.cn | 13690746261 | 4 |
| 53 | 邢成 | 2018-05-07 | 1991-07-13 | xingcheng@moershi.com | 13983238261 | 4 |
| 54 | 孙丹 | 2012-01-28 | 1997-02-22 | sundan@moershi.com | 13684254376 | 4 |
| 55 | 梁静 | 2013-03-21 | 1995-05-20 | liangjing@moershi.com | 18077866993 | 4 |
| 56 | 陈洁 | 2006-08-17 | 1977-04-23 | chenjie@tedu.cn | 14737309895 | 4 |
| 57 | 许辉 | 2014-09-16 | 1992-10-21 | xuhui@moershi.com | 13143391754 | 4 |
| 58 | 张伟 | 2007-06-22 | 1999-04-30 | zhangwei@moershi.com | 18172302428 | 4 |
| 59 | 钟倩 | 2011-02-23 | 1983-06-28 | zhongqian@moershi.com | 18611282765 | 4 |
| 60 | 贺磊 | 2001-06-19 | 1993-02-07 | helei@tedu.cn | 15897839325 | 4 |
| 61 | 沈秀梅 | 2003-04-27 | 1992-08-13 | shenxiumei@tedu.cn | 18044302910 | 4 |
| 62 | 林刚 | 2007-09-19 | 1990-09-23 | lingang@tedu.cn | 13355063263 | 4 |
| 63 | 王玉华 | 2016-10-09 | 1973-09-14 | wangyuhua@tedu.cn | 18010485419 | 4 |
| 64 | 徐金凤 | 2015-09-09 | 1972-01-31 | xujinfeng@tedu.cn | 15124816733 | 4 |
| 65 | 张淑英 | 2010-11-08 | 1996-09-12 | zhangshuying@moershi.com | 18846908114 | 4 |
| 66 | 罗岩 | 2001-06-13 | 1986-10-17 | luoyan@tedu.cn | 14544559539 | 4 |
| 67 | 潘玲 | 2017-08-31 | 1993-02-18 | panling@tedu.cn | 13311560534 | 4 |
| 68 | 柴冬梅 | 2003-11-24 | 1976-01-01 | chaidongmei@moershi.com | 14506733843 | 4 |
| 69 | 谢莹 | 2017-12-06 | 1996-04-01 | xieying@tedu.cn | 15137904023 | 4 |
| 70 | 傅雪 | 2001-04-23 | 1976-09-06 | fuxue@moershi.com | 15210059051 | 4 |
| 71 | 龙旭 | 2019-05-15 | 1974-11-02 | longxu@moershi.com | 15155879580 | 4 |
| 72 | 赵杰 | 2004-05-08 | 1984-04-06 | zhaojie@tedu.cn | 13466360255 | 4 |
| 73 | 王璐 | 2003-09-22 | 2000-02-01 | wanglu@moershi.com | 13056592974 | 4 |
| 74 | 赵成 | 2012-11-30 | 1985-10-11 | zhaocheng@tedu.cn | 18545824587 | 4 |
+-------------+-----------+------------+------------+---------------------------+--------------+---------+
55 rows in set (0.00 sec)
mysql> --外键约束,先删除salary表对应数据
mysql> delete from moershi.salary where employee_id in (select employee_id from moershi.employees where dept_id=4);
Query OK, 3308 rows affected (0.36 sec)
mysql> --删除employees表dept_id=4的数据
mysql> delete from moershi.employees where dept_id=4;
Query OK, 55 rows affected (0.22 sec)
mysql> --查询删除后结果
mysql> select * from moershi.employees where dept_id=4;
Empty set (0.00 sec)
您在删除 `dept_id=4` 的部门记录时遇到了外键约束错误,这表明有其他表(比如 `salary` 表)的数据通过外键关联着这个部门的员工记录。下面为您梳理解决方案。
### 🔍 理解错误原因
错误信息 `Cannot delete or update a parent row: a foreign key constraint fails` 表明,由于外键约束的存在,您无法直接删除 `employees` 表中 `dept_id=4` 的记录。这通常是数据库为了维护数据一致性而设定的保护机制,防止出现“孤悬数据”(例如,工资记录对应的员工信息不存在了)。
核心问题在于,`salary` 表通过 `employee_id` 字段关联了 `employees` 表。当您尝试删除 `employees` 表中的某条员工记录时,如果 `salary` 表中还存在与之对应的工资记录,数据库就会阻止这个删除操作。
### ✅ 解决方案选择
您可以根据具体情况选择下表列出的方法来解决这个问题:
| 解决方案 | 核心操作 | 适用场景 |
| :--- | :--- | :--- |
| **先删关联数据** | 先手动删除 `salary` 表中相关的员工工资记录,再删除 `employees` 表中的员工记录。 | 希望精确控制删除范围,或数据库未设置级联删除时。 |
| **临时禁用约束** | 暂时关闭外键约束检查,执行删除操作后重新开启。 | 需要快速处理数据,且确认删除不会破坏业务逻辑;**需谨慎使用**。 |
| **设置级联删除** | 修改外键约束,设置 `ON DELETE CASCADE`。 | 希望一劳永逸,后续删除员工能自动同步删除其工资记录。 |
### ⚠️ 重要操作步骤与提醒
无论选择哪种方法,**在进行任何删除或结构修改操作前,强烈建议备份数据**。
- **方案一:先删除关联数据**
这种方法思路直接,即先清除子表(`salary`)中的相关记录,再删除父表(`employees`)中的记录。
```sql
-- 1. 首先删除 salary 表中所有属于 dept_id=4 部门的员工的工资记录
DELETE FROM moershi.salary
WHERE employee_id IN (SELECT employee_id FROM moershi.employees WHERE dept_id = 4);
-- 2. 然后再删除 employees 表中 dept_id=4 的员工记录
DELETE FROM moershi.employees WHERE dept_id = 4;
  • 方案二:临时禁用外键约束 此方法通过 SET FOREIGN_KEY_CHECKS = 0; 临时关闭外键约束检查,操作完成后务必记得重新开启。

    -- 临时禁用外键约束检查
    SET FOREIGN_KEY_CHECKS = 0;
    -- 执行删除操作
    DELETE FROM moershi.employees WHERE dept_id = 4;
    -- 立即重新启用外键约束检查
    SET FOREIGN_KEY_CHECKS = 1;

    注意:这种方法会暂时破坏数据库的引用完整性,如果操作不当,可能导致数据不一致,请务必确保在可控范围内使用。

  • 方案三:设置级联删除(长远之计) 级联删除是一种“一劳永逸”的解决方案。设置后,当您删除 employees 表中的某位员工时,数据库会自动删除 salary 表中与之相关的所有工资记录。 首先,你需要删除原有的外键约束,然后重新添加一个带有级联删除选项的新约束。

    -- 1. 先删除现有的外键约束(需要知道约束名称,错误信息中提示是 `emp_id_fk`)
    ALTER TABLE moershi.salary DROP FOREIGN KEY emp_id_fk;
    -- 2. 重新添加外键约束,并设置级联删除
    ALTER TABLE moershi.salary
    ADD CONSTRAINT emp_id_fk
    FOREIGN KEY (employee_id) REFERENCES employees(employee_id)
    ON DELETE CASCADE;

    设置完成后,您再次执行 DELETE FROM moershi.employees WHERE dept_id=4;,数据库就会自动处理关联的工资记录了。

💡 决策建议与总结#

  • 当前操作是临时的、一次性的,建议使用方案一(先删关联数据),它最清晰、可控。
  • 经常需要进行此类关联删除,建议使用方案三(设置级联删除),一劳永逸,但务必理解其自动删除的后果。
  • 方案二(临时禁用约束) 风险较高,通常用于数据迁移或清理等特定场景,日常操作请谨慎选择

请根据您的具体需求选择最合适的方法。如果对约束名称或具体操作步骤仍有疑问,可以随时追问。

  1. employees 表中查询出所有在 2025 年入职的员工信息,并按照 hire_date 升序排列。
mysql>
mysql> select * from moershi.employees where year(hire_date) = 2025 order by hire_date asc;
+-------------+--------+------------+------------+------------------+--------------+---------+
| employee_id | name | hire_date | birth_date | email | phone_number | dept_id |
+-------------+--------+------------+------------+------------------+--------------+---------+
| 134 | 李四 | 2025-06-01 | 1990-05-10 | lisi@example.com | 13800138000 | 2 |
+-------------+--------+------------+------------+------------------+--------------+---------+
1 row in set (0.00 sec)
mysql>
要从 `employees` 表中查询出所有在 2025 年入职的员工信息并按入职日期升序排列,可以使用以下 SQL 语句:
```sql
SELECT *
FROM employees
WHERE YEAR(hire_date) = 2025
ORDER BY hire_date ASC;
```
下面是这个查询语句中各部分作用的详细说明:
| SQL 部分 | 作用说明 | 备注 |
| :--- | :--- | :--- |
| `SELECT *` | 选择所有列 | 返回员工表的所有字段信息。 |
| `FROM employees` | 指定数据来源 | 从名为 `employees` 的表中查询数据。 |
| `WHERE YEAR(hire_date) = 2025` | 筛选条件 | **`YEAR()` 函数用于从日期中提取年份**,这里限定只查询入职年份为 2025 年的记录。 |
| `ORDER BY hire_date ASC` | 排序结果 | 按照 `hire_date`(入职日期)字段进行升序排列。`ASC` 表示升序,可以省略,因为它是默认的排序方式。 |
### 💡 其他可行的日期筛选方法
除了使用 `YEAR()` 函数,你也可以考虑以下方法来实现相同的筛选目标,它们在特定场景下可能更有用:
- **使用 `BETWEEN` 指定日期范围**
你可以明确指定入职日期的起止范围来筛选2025年的记录:
```sql
SELECT *
FROM employees
WHERE hire_date BETWEEN '2025-01-01' AND '2025-12-31'
ORDER BY hire_date;
```
- **使用 `DATE_FORMAT()` 函数**
`DATE_FORMAT()` 函数功能强大,可以按各种格式输出日期。用它来提取年份部分进行匹配也是一种方法:
```sql
SELECT *
FROM employees
WHERE DATE_FORMAT(hire_date, '%Y') = '2025'
ORDER BY hire_date;
```
03:mysql之单表增删改查
https://fuwari.vercel.app/posts/数据库/03-mysql之单表增删改查/
作者
肥猫少杰
发布于
2026-06-03
许可协议
CC BY-NC-SA 4.0