目录
[6.1.单行拼接(同一条数据内,多字段 / 文本合并)](#6.1.单行拼接(同一条数据内,多字段 / 文本合并))
1.排序和分页
1.排序(ORDER BY)
cpp
-- 升序(ASC,默认)、降序(DESC)
SELECT * FROM user ORDER BY age ASC;
SELECT * FROM user ORDER BY create_time DESC;
-- 多字段排序(先按年龄降序,年龄相同按创建时间升序)
SELECT * FROM user ORDER BY age DESC, create_time ASC;
2.分页(LIMIT)
cpp
-- LIMIT 起始索引, 每页条数(起始索引从0开始)
-- 第1页,10条数据
SELECT * FROM user LIMIT 0,10;
-- 第2页,10条数据
SELECT * FROM user LIMIT 10,10;
2.聚合统计与分组
1.常用聚合函数
cpp
-- 统计数量
SELECT COUNT(*) FROM user; -- 统计总行数
SELECT COUNT(email) FROM user; -- 统计非空email数量
-- 求和/平均值/最大/最小值
SELECT SUM(age) FROM user;
SELECT AVG(age) FROM user;
SELECT MAX(age) FROM user;
SELECT MIN(age) FROM user;
2.分组统计(GROUP BY)
核心 :GROUP BY 后只能跟分组字段 + 聚合函数
cpp
-- 按性别统计人数
SELECT gender, COUNT(*) AS 人数 FROM user GROUP BY gender;
-- 按城市统计平均年龄
SELECT city, AVG(age) AS 平均年龄 FROM user GROUP BY city;
3.分组后筛选(HAVING)
区别 :WHERE 筛选原始数据 ,HAVING 筛选分组后结果
cpp
-- 统计人数大于5的城市
SELECT city, COUNT(*) AS 人数
FROM user
GROUP BY city
HAVING COUNT(*) > 5;
3.数据增删改(DML)
1.插入数据(INSERT)
cpp
-- 单条插入
INSERT INTO user(name, age, gender) VALUES ('张三', 20, '男');
-- 批量插入(性能更高)(高效推荐)
INSERT INTO user(name, age, gender)
VALUES ('李四',21,'女'),('王五',22,'男');
2.更新数据(UPDATE)
必须加 WHERE 条件,否则全表更新!
cpp
-- 条件更新
UPDATE user SET age = 21, email = 'xxx@163.com' WHERE id = 1;
3.删除数据(DELETE/TRUNCATE)
cpp
-- 条件删除
DELETE FROM user WHERE id = 1;
-- 清空全表(不可回滚,速度远快于DELETE)
TRUNCATE TABLE user;
4.插入或更新数据
INSERT ... SET + ON DUPLICATE KEY UPDATE 标准写法
MySQL 支持
INSERT INTO 表 SET 字段=值语法,可直接拼接ON DUPLICATE KEY UPDATE,依赖主键 / 唯一索引冲突触发更新
cpp
INSERT INTO `table_name`
SET
id = 1,
username = 'test',
age = 20
ON DUPLICATE KEY UPDATE
username = 'test',
age = 20;
5.查询结果插入(INSERT ... SELECT)
将一个查询结果集批量插入目标表,适合数据迁移 / 复制:
cpp
-- 替换:库名、表名、字段名
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '数据库名'
AND TABLE_NAME = '表名'
AND COLUMN_NAME = '字段名';
如:
cpp
-- 复制部门10的员工到新表
INSERT INTO new_employees(id, name, dept)
SELECT id, name, 20 FROM employees
WHERE dept = 10;
支持复杂子查询、JOIN 与聚合函数。
4.判断表是否存在指定字段
查询 information_schema(通用,推荐)
cpp
-- 替换:库名、表名、字段名
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '数据库名'
AND TABLE_NAME = '表名'
AND COLUMN_NAME = '字段名';
返回>0:字段存在;=0:不存在
示例:
cpp
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'testdb'
AND TABLE_NAME = 'user'
AND COLUMN_NAME = 'phone';
5.向数据库中的每个表都增加或删除一个字段
1.增加字段
先判断表中是否有这个字段,没有则增加,有则跳过,sql语句如下:
cpp
-- 如果存储过程存在则删除
DROP PROCEDURE IF EXISTS add_username_all_table;
DELIMITER $$ /*修改语句结束符为$$,类似sql server中的go语句*/
--db_name :数据库名称
--col_name :字段名
CREATE PROCEDURE add_username_all_table(IN db_name VARCHAR(64), IN col_name VARCHAR(64))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE tb_name VARCHAR(64);
-- 游标:查询指定库下所有数据表
DECLARE cur_tb CURSOR FOR
SELECT TABLE_NAME FROM information_schema.TABLES
WHERE TABLE_SCHEMA = db_name AND TABLE_TYPE='BASE TABLE';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
OPEN cur_tb;
tb_loop:LOOP
FETCH cur_tb INTO tb_name;
IF done=1 THEN LEAVE tb_loop; END IF;
-- 判断字段是否存在
SET @is_exist = (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA=db_name AND TABLE_NAME=tb_name AND TABLE_NAME<>'usertable' AND COLUMN_NAME=col_name);
-- 不存在则添加字段,varchar(50)可自行修改类型
SET @sql = IF(@is_exist=0,
CONCAT('ALTER TABLE `',db_name,'`.`',tb_name,'` ADD COLUMN `',col_name,'` VARCHAR(255) NOT NULL COMMENT \'*unVisible\';'),
'SELECT 1;');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP tb_loop;
CLOSE cur_tb;
END$$
DELIMITER ; /*改回默认分号结束符*/
--调用此过程
CALL add_username_all_table('xxxxxx', 'userName');
2.删除字段
cpp
DROP PROCEDURE IF EXISTS drop_column_all_tables;
DELIMITER $$ /*修改语句结束符为$$,类似sql server中的go语句*/
--db_name :数据库名称
--col_name :字段名
CREATE PROCEDURE drop_column_all_tables(IN db_name VARCHAR(64), IN col_name VARCHAR(64))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE tb_name VARCHAR(64);
DECLARE cur_tb CURSOR FOR
SELECT TABLE_NAME FROM information_schema.`TABLES`
WHERE TABLE_SCHEMA = db_name AND TABLE_TYPE='BASE TABLE';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
OPEN cur_tb;
tb_loop:LOOP
FETCH cur_tb INTO tb_name;
IF done=1 THEN LEAVE tb_loop; END IF;
-- 判断字段是否存在
SET @is_exist = (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA=db_name AND TABLE_NAME=tb_name AND TABLE_NAME<>'usertable' AND COLUMN_NAME=col_name);
-- 存在则删除字段
SET @sql = IF(@is_exist>0,
CONCAT('ALTER TABLE `',db_name,'`.`',tb_name,'` DROP COLUMN `',col_name,'`;'),
'SELECT 1;');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP tb_loop;
CLOSE cur_tb;
END$$
DELIMITER ; /*改回默认分号结束符*/
-- 调用示例:删除 xxxxx 库中所有表的 userName 字段
CALL drop_column_all_tables('xxxxxx', 'userName');
6.字符串合并(拼接)
MySQL 字符串拼接分单行多字段拼接 、多行数据合并为一行 两大场景,常用函数:CONCAT、CONCAT_WS、GROUP_CONCAT,MySQL 8.0+ 还支持标准函数 STRING_AGG。
先准备测试表 + 测试数据,后续所有示例共用:
cpp
-- 创建测试表
CREATE TABLE `user` (
id INT,
name VARCHAR(20),
dept VARCHAR(20),
city VARCHAR(20),
phone VARCHAR(20)
);
-- 插入测试数据(包含 NULL 场景)
INSERT INTO `user` VALUES
(1,'张三','技术部','北京','13800138000'),
(2,'李四','技术部','上海',NULL),
(3,'王五','市场部','广州','13900139000'),
(4,'赵六','市场部','广州','13900139000'),
(5,'钱七','人事部',NULL,'13700137000');
6.1.单行拼接(同一条数据内,多字段 / 文本合并)
适用于:一条记录里,把多个字段、固定文字拼接成一个字符串。
1.CONCAT(str1,str2,...) 基础拼接
cpp
CONCAT(字符串1, 字符串2, 字符串3 ...)
- 支持多个参数拼接;
- 只要任意一个参数为
NULL,整体结果直接返回NULL(最大坑点)。
cpp
-- 拼接:姓名 + 固定符号 + 城市
SELECT CONCAT(name, ' / ', city) AS user_info FROM `user`;
-- 包含 NULL 字段演示(李四的phone为NULL,整条结果为NULL)
SELECT name, CONCAT(name, phone) AS name_phone FROM `user`;
2.CONCAT_WS(分隔符, str1, str2,...) 带分隔符拼接
WS = With Separator,生产环境首选 ,完美规避 NULL 问题。
cpp
CONCAT_WS(分隔符, 字段1, 字段2, 字段3 ...)
- 第一个参数固定为分隔符;
- 自动跳过值为
NULL的字段 ,不会因为NULL导致整体为空; - 不会产生多余的分隔符。
cpp
-- 用 | 拼接 姓名、城市、手机号(李四手机号为NULL,自动忽略)
SELECT
name,
CONCAT_WS(' | ', name, city, phone) AS full_info
FROM `user`;
-- 拼接固定文本 + 字段
SELECT CONCAT_WS(':', '姓名', name) AS name_label FROM `user`;
6.2.多行合并(多条记录,按分组拼接成一行)
适用于:分组后,把一组内多条行数据的字段合并为单个字符串(行转列)。
1.GROUP_CONCAT() 全版本通用(MySQL 所有版本支持)
cpp
GROUP_CONCAT( [DISTINCT] 字段
[ORDER BY 排序字段]
[SEPARATOR '自定义分隔符'] )
DISTINCT:去重;ORDER BY:拼接前对内容排序;SEPARATOR:指定分隔符,默认逗号,;
示例:基础用法:按部门合并员工姓名(默认逗号分隔)
cpp
SELECT
dept,
GROUP_CONCAT(name) AS emp_names
FROM `user`
GROUP BY dept;
自定义分隔符(改用分号 ;)
cpp
SELECT
dept,
GROUP_CONCAT(name SEPARATOR ';') AS emp_names
FROM `user`
GROUP BY dept;
拼接前排序:
cpp
-- 按姓名升序后再拼接
SELECT
dept,
GROUP_CONCAT(name ORDER BY name SEPARATOR ',') AS emp_names
FROM `user`
GROUP BY dept;
拼接时去重:
cpp
-- 合并同部门的手机号,并去重
SELECT
dept,
GROUP_CONCAT(DISTINCT phone SEPARATOR ',') AS phone_list
FROM `user`
GROUP BY dept;
2.STRING_AGG(字段, 分隔符)(MySQL 8.0+ 新函数)
SQL 标准拼接函数,语法更简洁,无默认长度限制,推荐 8.0 及以上版本使用。
cpp
STRING_AGG(字段, '分隔符' [ORDER BY 排序字段])
cpp
-- 按部门合并姓名,逗号分隔
SELECT
dept,
STRING_AGG(name, ',') AS emp_names
FROM `user`
GROUP BY dept;
-- 带排序
SELECT
dept,
STRING_AGG(name, ',' ORDER BY name DESC) AS emp_names
FROM `user`
GROUP BY dept;
7.字符串查找
判断表中某一列的单个值,是否存在于另一列 / 指定的逗号分隔字符串中 ,分精确匹配(逗号分隔独立项,业务主流) 和模糊匹配(纯文本包含) 两大类。
先统一准备测试表和数据,所有示例共用:
cpp
-- 表1:存储单个值(待匹配列)
CREATE TABLE `user` (
id INT,
name VARCHAR(20)
);
INSERT INTO `user` VALUES
(1,'张三'),
(2,'李四'),
(3,'王五'),
(4,'赵六'),
(5,'张三三'); -- 用于测试模糊匹配坑点
-- 表2:存储逗号分隔字符串(目标字符串列)
CREATE TABLE `article` (
aid INT,
title VARCHAR(50),
tag_list VARCHAR(100) -- 逗号分隔字符串
);
INSERT INTO `article` VALUES
(1,'文章1','张三,李四,王五'),
(2,'文章2','李四,赵六'),
(3,'文章3',',王五,钱七,'), -- 首尾带逗号
(4,'文章4',''), -- 空字符串
(5,'文章5',NULL); -- NULL 值
7.1.精确匹配(FIND_IN_SET)
列值是逗号字符串里的完整独立项 (比如标签、多选 ID、人员名单),这是逗号分隔串最常用场景。 MySQL 专用函数:FIND_IN_SET(),专门适配逗号分隔列表,全版本兼容,无子串误匹配问题。
cpp
FIND_IN_SET( 待查找的值, 逗号分隔字符串 )
返回规则:
- 匹配到独立项:返回所在位置(1、2、3...)
- 未匹配到:返回
0 - 任意一个参数为
NULL:返回NULL
判断条件:FIND_IN_SET(...) > 0 代表找到。
示例如下:查询 user 表中 name 存在于固定字符串 '张三,李四,王五' 的数据:
cpp
SELECT * FROM `user`
WHERE FIND_IN_SET(name, '张三,李四,王五') > 0;
同表内,A 列值 匹配 本表 B 列逗号串,查询 article 表中,固定值 王五 存在于 tag_list 的行:
cpp
SELECT * FROM `article`
WHERE FIND_IN_SET('王五', tag_list) > 0;
两表联查,关联 user 和 article,找出 user.name 存在于 article.tag_list 的关联数据:
cpp
SELECT u.*, a.title, a.tag_list
FROM `user` u
JOIN `article` a
ON FIND_IN_SET(u.name, a.tag_list) > 0;
边界问题处理:
1.字段值带空格
如果数据存在前后空格(如 ' 张三 '),先用 TRIM() 去空格:
cpp
SELECT * FROM `user`
WHERE FIND_IN_SET(TRIM(name), '张三,李四,王五') > 0;
2.空字符串 / NULL
tag_list = ''(空串):FIND_IN_SET返回 0,自动过滤;tag_list = NULL:FIND_IN_SET返回 NULL,WHERE条件不成立,自动过滤; 无需额外判断。
3.分隔符不是逗号
FIND_IN_SET 仅支持逗号,如果是分号、竖线、空格等分隔符,先替换成逗号再使用:
cpp
-- 原分隔符为 ; ,先替换为 ,
SELECT * FROM `article`
WHERE FIND_IN_SET('王五', REPLACE(tag_list, ';', ',')) > 0;
7.2.模糊匹配
不要求是独立项,只要字符串中包含该列的字符 (纯文本检索,和分隔符无关)。 适用函数:LIKE、INSTR、LOCATE。
1.LIKE 写法(最直观)
使用 % 通配符表示模糊匹配:
cpp
-- 匹配:name 出现在 tag_list 任意位置
SELECT u.*, a.tag_list
FROM `user` u
JOIN `article` a
WHERE a.tag_list LIKE CONCAT('%', u.name, '%');
重大坑点 :会出现子串误匹配 例如
name='张三三',也会匹配到tag_list='张三,李四',因为文本包含「张三」。只要是逗号分隔的选项列表,坚决不要用模糊匹配。
2.INSTR / LOCATE 写法(等价 LIKE,性能略优)
INSTR (原字符串,查找内容)
返回字符所在位置,0 = 未找到:
cpp
SELECT * FROM `article`
WHERE INSTR(tag_list, '张三') > 0;
LOCATE (查找内容,原字符串)
参数顺序和 INSTR 相反,功能一致:
cpp
SELECT * FROM `article`
WHERE LOCATE('张三', tag_list) > 0;