SQL 单表操作全解

SQL 单表操作全解

本文所有语法和实例,均基于开发最常用的users用户表,表结构完全符合生产规范,后续所有操作均围绕此表展开:

sql 复制代码
CREATE TABLE IF NOT EXISTS users (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID,主键',
  username VARCHAR(50) NOT NULL COMMENT '用户名',
  email VARCHAR(100) DEFAULT '' COMMENT '用户邮箱',
  password CHAR(32) NOT NULL COMMENT '加密密码',
  age TINYINT UNSIGNED DEFAULT 0 COMMENT '年龄',
  city VARCHAR(20) DEFAULT '' COMMENT '所在城市',
  gender TINYINT DEFAULT 0 COMMENT '性别:0-未知,1-男,2-女',
  is_delete TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除:0-未删除,1-已删除(逻辑删除)',
  create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  -- 单表索引(后续优化章节详解)
  PRIMARY KEY (id),
  UNIQUE KEY uk_username (username),
  KEY idx_city_age (city, age),
  KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

单表操作的本质,就是对一张二维表的两个维度的精准控制,以及基于筛选结果的附加加工:

  1. 列维度(垂直筛选) :通过SELECT控制要查询的字段,只提取需要的列;
  2. 行维度(水平筛选) :通过WHERE控制要查询的记录,只过滤符合条件的行;
  3. 结果加工:对筛选后的结果做排序、分页、去重等格式化处理;
  4. 数据计算:对筛选后的结果做聚合、分组、统计等数值计算;
  5. 数据操纵:对表中的数据进行新增、更新、删除操作。

列维度筛选:SELECT 语法全解

SELECT是单表操作的入口,核心作用是精准提取需要的字段 ,生产环境有一条铁律:严禁使用SELECT *查询全字段,仅查询业务需要的列,避免敏感字段泄露、性能损耗和表结构变更带来的业务报错。

基础指定字段查询

sql 复制代码
-- 语法:SELECT 字段1, 字段2, 字段3 FROM 表名;

-- 实例:查询用户的非敏感基础信息(忽略密码等敏感字段)
SELECT id, username, email, age, city FROM users;

字段别名:AS

用于简化字段名、解决字段重名、自定义返回列名,提升结果可读性,别名支持中文(生产建议用英文)。

sql 复制代码
-- 语法:SELECT 字段 AS 别名 FROM 表名;

-- 实例:给字段起业务别名
SELECT 
  id AS user_id,
  username AS user_name,
  email AS user_email,
  age AS user_age
FROM users;

去重查询:DISTINCT

用于剔除查询结果中的重复行,核心规则:DISTINCT对查询的所有字段组合去重,而非单个字段

sql 复制代码
-- 语法:SELECT DISTINCT 字段1, 字段2 FROM 表名;

-- 实例1:查询表中所有不重复的城市(单字段去重)
SELECT DISTINCT city FROM users;

-- 实例2:查询城市+年龄的不重复组合(多字段组合去重)
SELECT DISTINCT city, age FROM users;

常量列与计算列

用于给结果补充固定值,或对字段做简单的数值计算,无需修改表结构即可实现业务需求。

sql 复制代码
-- 实例1:补充常量列,给所有用户标记固定的用户类型
SELECT 
  id,
  username,
  '正式用户' AS user_type -- 常量列,所有行均为该固定值
FROM users;

-- 实例2:计算列,对数值字段做简单计算
SELECT 
  id,
  username,
  age,
  age + 1 AS next_year_age, -- 计算用户明年的年龄
  2026 - age AS birth_year   -- 计算用户出生年份
FROM users;

行维度筛选:WHERE 语法全解

WHERE是单表精准查询的核心,作用是按条件过滤符合业务规则的行,避免全表扫描,是开发中使用频率最高的语法之一。

常用条件运算符

运算符类型 具体运算符 作用说明 开发高频度
比较运算符 =><>=<=!=/<> 数值、字符串、日期的等值 / 不等值比较 ★★★★★
逻辑运算符 ANDORNOT 多条件组合,AND优先级高于OR,可用括号调整优先级 ★★★★★
范围运算符 BETWEEN ... AND ...IN 区间匹配、集合匹配 ★★★★☆
模糊匹配 LIKE 模糊查询,%匹配任意字符,_匹配单个字符 ★★★★☆
空值判断 IS NULLIS NOT NULL 空值判断,NULL 不能用=/!=判断 ★★★★☆
sql 复制代码
-- 1. 基础等值查询:查询指定ID的用户
SELECT id, username, email FROM users WHERE id = 100;

-- 2. 多条件组合查询:查询北京的成年、未删除的男性用户
-- 注意:AND优先级高于OR,复杂条件必须用括号明确执行顺序
SELECT id, username, age, city FROM users 
WHERE age >= 18 
  AND city = '北京' 
  AND gender = 1 
  AND is_delete = 0;

-- 3. 范围查询:查询年龄在18-30岁之间的用户
SELECT id, username, age FROM users 
WHERE age BETWEEN 18 AND 30;

-- 4. 集合匹配:查询北上广深四个城市的用户
SELECT id, username, city FROM users 
WHERE city IN ('北京', '上海', '广州', '深圳');

-- 5. 模糊查询:查询姓张的用户(前缀匹配,可命中索引)
SELECT id, username FROM users 
WHERE username LIKE '张%';

-- 6. 模糊查询:查询用户名包含「三」的用户(中缀匹配,无法命中索引,大表慎用)
SELECT id, username FROM users 
WHERE username LIKE '%三%';

-- 7. 空值判断:查询未填写邮箱的用户
SELECT id, username FROM users 
WHERE email IS NULL OR email = '';

-- 8. 取反查询:查询除北京之外的用户
SELECT id, username, city FROM users 
WHERE NOT city = '北京';

必避坑

  1. NULL值判断必须用IS NULL/IS NOT NULLNULL = ''NULL = NULL的结果均为UNKNOWN,不会匹配到任何行;
  2. AND优先级高于OR ,例如WHERE a=1 OR b=2 AND c=3,实际执行逻辑是a=1 OR (b=2 AND c=3),如需调整顺序,必须用括号包裹;
  3. 模糊查询%xxx%中缀匹配无法命中索引,大表查询会触发全表扫描,生产环境慎用。

结果集加工:排序、分页、去重

SELECT + WHERE筛选后的结果,做格式化处理,满足业务的展示、分页、排序需求,是列表类接口的必用语法。

排序:ORDER BY

用于对结果集按指定字段排序,ASC为升序(默认值,可省略),DESC为降序,支持多字段组合排序。

sql 复制代码
-- 语法:SELECT 字段 FROM 表名 [WHERE 条件] ORDER BY 字段1 ASC/DESC, 字段2 ASC/DESC;

-- 实例1:按用户创建时间倒序,查询最新注册的用户
SELECT id, username, create_time FROM users 
WHERE is_delete = 0
ORDER BY create_time DESC;

-- 实例2:多字段排序:先按城市升序,同城市内按年龄降序
SELECT id, username, city, age FROM users 
WHERE is_delete = 0
ORDER BY city ASC, age DESC;

分页:LIMIT

用于限制返回的行数,避免一次性查询全表海量数据导致内存溢出,是后台列表、前端分页接口的核心语法。

sql 复制代码
-- 语法:LIMIT 偏移量, 每页条数; 偏移量计算公式:(页码-1) * 每页条数
SELECT 字段 FROM 表名 [WHERE 条件] [ORDER BY 排序] LIMIT 偏移量, 条数;

-- 实例:用户列表第2页,每页10条数据,按注册时间倒序
SELECT id, username, city, create_time FROM users 
WHERE is_delete = 0
ORDER BY create_time DESC
LIMIT 10, 10; -- 第2页:(2-1)*10=10,偏移量10,取10条

-- 简化语法:查询前10条数据,无需偏移量
SELECT id, username FROM users LIMIT 10;

分页优化提示

大表深分页(如LIMIT 100000, 10)性能极差,优化方案:用主键过滤替代偏移量,示例:

sql 复制代码
-- 优化前:深分页,需扫描100010行数据
SELECT id, username FROM users ORDER BY id DESC LIMIT 100000, 10;

-- 优化后:主键过滤,仅扫描10行数据
SELECT id, username FROM users WHERE id < 100000 ORDER BY id DESC LIMIT 10;

单表聚合与分组统计

用于对筛选后的结果做数值计算、分组统计,是报表、数据看板、业务统计类需求的核心语法。

常用聚合函数

聚合函数会对多行数据做计算,最终返回单个结果,聚合函数会自动忽略NULL值的行

函数 核心作用 开发高频度
COUNT(*) 统计结果集的总行数,包含NULL值行、空值行 ★★★★★
COUNT(字段) 统计指定字段非空的行数,排除NULL值行 ★★★★☆
SUM(字段) 对数值字段求和,非数值字段返回 0 ★★★★☆
AVG(字段) 对数值字段求平均值,排除NULL值行 ★★★★☆
MAX(字段) 求字段的最大值,支持数值、日期、字符串 ★★★★☆
MIN(字段) 求字段的最小值,支持数值、日期、字符串 ★★★★☆
sql 复制代码
-- 实例1:统计平台总用户数(未删除)
SELECT COUNT(*) AS total_user FROM users WHERE is_delete = 0;

-- 实例2:统计北京用户的平均年龄、最大年龄、最小年龄
SELECT 
  AVG(age) AS avg_age,
  MAX(age) AS max_age,
  MIN(age) AS min_age
FROM users
WHERE city = '北京' AND is_delete = 0;

-- 实例3:统计已填写邮箱的用户数量
SELECT COUNT(email) AS email_user_count FROM users WHERE email IS NOT NULL AND email != '';

分组统计:GROUP BY

将结果集按指定字段分组,对每个分组单独做聚合计算,实现「分维度统计」的业务需求。

MySQL 5.7+ 默认开启ONLY_FULL_GROUP_BY模式,SELECT后的非聚合字段,必须全部出现在GROUP BY的分组字段中,否则会直接报错。

sql 复制代码
-- 语法
SELECT 分组字段, 聚合函数 FROM 表名 
[WHERE 行过滤条件]
GROUP BY 分组字段
[ORDER BY 排序];

-- 实例1:统计每个城市的用户数量,按用户数倒序
SELECT 
  city,
  COUNT(*) AS user_count
FROM users
WHERE is_delete = 0
GROUP BY city
ORDER BY user_count DESC;

-- 实例2:多字段分组:统计每个城市+性别的用户数量
SELECT 
  city,
  gender,
  COUNT(*) AS user_count
FROM users
WHERE is_delete = 0
GROUP BY city, gender
ORDER BY city ASC;

分组过滤:HAVING

用于对GROUP BY分组后的结果做过滤,是新手最容易和WHERE搞混的语法,核心区别如下:

特性 WHERE HAVING
执行时机 分组前执行,先过滤行,再分组 分组后执行,先分组聚合,再过滤组
聚合函数 不支持,不能在 WHERE 中使用聚合函数 支持,核心作用就是过滤聚合结果
作用对象 表中的行,按行过滤 分组后的结果集,按组过滤
sql 复制代码
-- 语法
SELECT 分组字段, 聚合函数 FROM 表名 
[WHERE 行过滤条件]
GROUP BY 分组字段
HAVING 分组后过滤条件
[ORDER BY 排序];

-- 实例:统计每个城市的用户数,仅保留用户数大于100的城市,按用户数倒序
SELECT 
  city,
  COUNT(*) AS user_count
FROM users
WHERE is_delete = 0 -- 先过滤掉已删除的用户(行过滤)
GROUP BY city
HAVING user_count > 100 -- 再过滤掉用户数不足100的城市(组过滤)
ORDER BY user_count DESC;

-- 实例2:统计平均年龄大于25岁的城市
SELECT 
  city,
  AVG(age) AS avg_age
FROM users
WHERE is_delete = 0
GROUP BY city
HAVING avg_age > 25;

单表查询完整执行顺序

理解执行顺序,就能彻底搞懂WHEREHAVING的区别,单表查询的完整执行顺序如下:

plaintext 复制代码
FROM 表名 → 确定要操作的表
WHERE 行过滤条件 → 过滤符合条件的行
GROUP BY 分组字段 → 对过滤后的行进行分组
聚合函数计算 → 对每个分组做聚合计算
HAVING 分组过滤条件 → 对分组后的结果做过滤
SELECT 字段/别名 → 提取最终要返回的列
ORDER BY 排序 → 对最终结果排序
LIMIT 分页 → 限制返回的行数

单表数据操纵:增删改(INSERT/UPDATE/DELETE)

核心是对单表中的数据进行写入、修改、删除操作,生产环境有严格的使用规范,必须重点关注。

新增数据:INSERT

单行插入

生产环境必须指定插入的字段,避免表结构变更(如新增字段)导致插入报错。

sql 复制代码
-- 语法
INSERT INTO 表名(字段1, 字段2, 字段3) 
VALUES(值1, 值2, 值3);

-- 实例:新增单个用户
INSERT INTO users(username, email, password, age, city, gender)
VALUES('张三', 'zhangsan@example.com', 'e10adc3949ba59abbe56e057f20f883e', 25, '北京', 1);

批量插入(高性能推荐)

一次性插入多条数据,远高于循环单行插入的性能,减少数据库 IO 次数,是批量导入数据的首选。

sql 复制代码
-- 语法
INSERT INTO 表名(字段1, 字段2, 字段3)
VALUES(值1, 值2, 值3), (值4, 值5, 值6), (值7, 值8, 值9);

-- 实例:批量新增3个用户
INSERT INTO users(username, email, password, age, city, gender)
VALUES
('李四', 'lisi@example.com', 'e10adc3949ba59abbe56e057f20f883e', 28, '上海', 1),
('王五', 'wangwu@example.com', 'e10adc3949ba59abbe56e057f20f883e', 22, '广州', 2),
('赵六', 'zhaoliu@example.com', 'e10adc3949ba59abbe56e057f20f883e', 30, '深圳', 1);

幂等插入:INSERT IGNORE

插入时如果唯一键(如 username、id)重复,不会报错,直接忽略本次插入,避免重复插入导致的业务异常。

sql 复制代码
-- 实例:用户名重复则忽略,不报错
INSERT IGNORE INTO users(username, email, password, age)
VALUES('张三', 'zhangsan_new@example.com', 'e10adc3949ba59abbe56e057f20f883e', 26);

插入或更新:ON DUPLICATE KEY UPDATE

开发高频语法,插入时如果唯一键重复,则执行更新操作;不重复则执行插入,完美实现「有则更新,无则新增」的业务需求。

sql 复制代码
-- 实例:用户存在则更新邮箱和年龄,不存在则新增
INSERT INTO users(id, username, email, password, age)
VALUES(1, '张三', 'zhangsan_update@example.com', 'e10adc3949ba59abbe56e057f20f883e', 26)
ON DUPLICATE KEY UPDATE 
email = VALUES(email), 
age = VALUES(age),
update_time = NOW();

更新数据:UPDATE

  1. UPDATE 必须加 WHERE 条件,严禁无 WHERE 的全表更新;
  2. 执行更新前,先用SELECT语句确认 WHERE 条件匹配的行数,避免误更新;
  3. 大表批量更新必须拆分,避免长事务锁表。
sql 复制代码
-- 语法
UPDATE 表名 SET 字段1=值1, 字段2=值2 [WHERE 条件];

-- 实例1:更新指定ID用户的邮箱和年龄
UPDATE users 
SET email = 'zhangsan_new@example.com', age = 26, update_time = NOW()
WHERE id = 1;

-- 实例2:批量更新:将北京的未成年用户的性别重置为0(未知)
UPDATE users 
SET gender = 0, update_time = NOW()
WHERE city = '北京' AND age < 18;

删除数据:DELETE & 逻辑删除

  1. DELETE 必须加 WHERE 条件,严禁无 WHERE 的全表删除;
  2. 执行删除前,先用SELECT语句确认 WHERE 条件匹配的行数,避免误删;
  3. 生产环境优先使用逻辑删除,而非物理删除,避免数据无法恢复。

物理删除:DELETE

直接从表中删除数据,删除后无法直接恢复。

sql 复制代码
-- 语法
DELETE FROM 表名 [WHERE 条件];

-- 实例:删除指定ID的用户
DELETE FROM users WHERE id = 100;

逻辑删除(生产推荐)

不实际删除数据,而是通过is_delete字段标记数据为已删除状态,查询时过滤掉已删除的数据,保证数据可追溯、可恢复,是互联网业务的通用规范。

sql 复制代码
-- 实例:逻辑删除指定ID的用户(更新is_delete字段为1)
UPDATE users SET is_delete = 1, update_time = NOW() WHERE id = 1;

-- 业务查询时,固定过滤is_delete=0,只查询未删除的数据
SELECT id, username FROM users WHERE is_delete = 0 AND id = 1;

清空全表:TRUNCATE

sql 复制代码
TRUNCATE TABLE users;

TRUNCATEDELETE的核心区别:

特性 DELETE TRUNCATE
条件过滤 支持 WHERE 不支持,直接清空全表
事务回滚 支持 不支持
自增主键 不重置 重置为初始值
执行效率 大表慢(逐行记录日志) 大表极快
触发器 会触发 不会触发

单表操作性能核心:索引优化

单表查询的性能瓶颈,99% 都来自于索引使用不当,这里聚焦单表场景下的索引核心知识,完全贴合单表操作的优化需求。

单表常用索引类型

索引类型 核心作用 单表适用场景
主键索引 基于主键创建,唯一、非空,一张表只能有一个 主键 id 查询,精准定位单行数据
普通索引 最基础的索引,仅提升查询速度 WHERE、ORDER BY、GROUP BY 后的高频查询字段
唯一索引 保证字段值唯一,查询性能极高 用户名、邮箱等唯一标识字段
联合索引 基于多个字段创建的索引,性能最优 多字段组合查询的场景,如WHERE city=? AND age=?

单表索引基础操作

sql 复制代码
-- 1. 创建普通索引
CREATE INDEX idx_age ON users(age);

-- 2. 创建唯一索引
CREATE UNIQUE INDEX uk_email ON users(email);

-- 3. 创建联合索引(高区分度字段放最左侧)
CREATE INDEX idx_city_age_gender ON users(city, age, gender);

-- 4. 查看表中所有索引
SHOW INDEX FROM users;

-- 5. 删除索引
DROP INDEX idx_age ON users;

单表场景索引生效核心规则

  1. 最左前缀原则 :联合索引会按创建时的字段顺序,从左到右依次匹配,遇到范围查询会停止匹配。例如联合索引idx_city_age_gender(city, age, gender)

    • ✅ 生效:WHERE city='北京'WHERE city='北京' AND age=25WHERE city='北京' AND age=25 AND gender=1
    • ❌ 失效:WHERE age=25WHERE gender=1WHERE city LIKE '北%' AND age=25
  2. ORDER BY/GROUP BY的字段尽量匹配索引,避免文件排序,提升性能;

  3. 精准查询优先使用主键索引、唯一索引,性能远高于普通索引。

单表查询高频索引失效场景

  1. 索引字段使用函数、表达式计算:WHERE YEAR(create_time) = 2026
  2. 索引字段发生隐式类型转换:WHERE phone = 13800138000(phone 是 VARCHAR 类型)
  3. 模糊查询以%开头:WHERE username LIKE '%三'
  4. 使用NOT!=<>反向查询(优化器评估全表扫描更快时,会放弃索引)
  5. OR连接非索引字段:WHERE username='张三' OR age=25(age 无索引)
  6. 使用IS NOT NULL对低区分度字段查询

单表索引最佳实践

  1. 优先为WHEREORDER BYGROUP BY后的字段创建索引;
  2. 联合索引优先把高区分度字段放在最左侧(如 username、city,区分度远高于 gender、is_delete);
  3. 单表索引数量控制在 5 个以内,避免过多索引影响增删改性能;
  4. 小表(数据量 < 1000 行)无需创建索引,全表扫描比索引查询更快;
  5. 避免创建冗余索引,如已有联合索引idx_city_age(city,age),无需再为 city 单独建索引;
  6. 上线前必须用EXPLAIN查看 SQL 执行计划,确认索引是否生效。

单表操作规范

查询规范

复制代码
-   严禁使用`SELECT *`,必须指定业务需要的字段;
-   大表查询必须加 WHERE 条件,严禁无过滤的全表查询;
-   避免使用`SELECT COUNT(*) FROM 大表`统计总行数,可使用统计表替代;
-   禁用`SELECT ... FOR UPDATE`行锁操作,除非强一致性场景,且必须控制锁范围。

增删改规范

复制代码
-   `UPDATE`、`DELETE`必须加 WHERE 条件,执行前先用 SELECT 确认条件;
-   生产环境优先使用逻辑删除,而非物理删除;
-   大表批量操作必须拆分,避免长事务锁表,如批量删除 10 万条数据,分 10 次每次删除 1 万条;
-   严禁在循环中执行单条 INSERT/UPDATE,必须使用批量操作。

语法规范

复制代码
-   避免在 WHERE 条件中对索引字段做函数计算、类型转换;
-   多条件组合时,用括号明确执行优先级,避免 AND/OR 优先级混乱;
-   NULL 值判断必须用 IS NULL/IS NOT NULL,禁止用 =/!= 判断 NULL;
-   GROUP BY 语句严格遵守`ONLY_FULL_GROUP_BY`规则,非聚合字段必须出现在 GROUP BY 中。
相关推荐
Devin~Y2 小时前
大厂 Java 面试实战:从电商微服务到 AI 智能客服(含 Spring 全家桶、Redis、Kafka、RAG/Agent 解析)
java·spring boot·redis·elasticsearch·spring cloud·docker·kafka
tang777892 小时前
小红书平台用什么代理IP?数据采集IP封禁解决方法
数据库·爬虫·python·网络协议·ip
无籽西瓜a2 小时前
【西瓜带你学设计模式 | 第十五期 - 策略模式】策略模式 —— 算法封装与动态替换实现、优缺点与适用场景
java·后端·设计模式·软件工程·策略模式
珍朱(珠)奶茶2 小时前
Spring Boot3整合FreeMark、itextpdf 5/7 实现pdf文件导出及注意问题
java·spring boot·后端·pdf·itextpdf
凤年徐2 小时前
Linux权限详解:从入门到掌握
linux·运维·服务器
zzzyyy5382 小时前
进程优先级
linux·运维·服务器
XDHCOM2 小时前
ORA-23336: priority group不存在故障修复远程处理
数据库·oracle
无风听海2 小时前
.NET10之C# 中的is null深入理解
服务器·c#·.net
llm大模型算法工程师weng2 小时前
Python拉取视频流的性能优化实战
开发语言·python·性能优化