MySQL 作为关系型数据库的主流产品,其功能覆盖数据存储、筛选、统计、关联等全流程。本文将以 “基础语法→核心功能→实战场景→细节误区” 为逻辑链,逐符号、逐关键字拆解知识点,从最基础的正则匹配到复杂的窗口函数,确保每个小细节都清晰易懂,帮助你彻底掌握 MySQL 数据处理能力。
一、正则表达式(REGEXP):文本匹配的 “精准筛选器”
正则表达式通过特定字符组合定义匹配规则,在 MySQL 中通过REGEXP操作符触发,常用于手机号、邮箱、身份证号等格式化数据的校验。每个符号的含义直接决定匹配结果,必须精准理解。
1. 核心语法:逐符号解析
正则表达式的匹配能力依赖 “锚点”“字符集”“量词” 三大组件,下表按功能分类,逐符号解释含义与用法:
| 类别 | 符号 / 语法 | 逐符号解析 | 示例 | 匹配结果(✅= 匹配,❌= 不匹配) |
|---|---|---|---|---|
| 锚点 | ^ |
匹配字符串的开头位置(确保从第一个字符开始匹配,避免 “部分匹配”) | ^A |
✅ "Apple"(以 A 开头),❌ "Banana" |
$ |
匹配字符串的结尾位置(确保匹配到最后一个字符,避免 “截断匹配”) | 9$ |
✅ "1239"(以 9 结尾),❌ "1234" | |
| 字符集 | [a-z] |
[ ]:定义 “可选字符集合”;a-z:表示 “从 a 到 z 的小写字母范围”,匹配 1 个字符 |
[0-9] |
✅ "5"(单个数字),❌ "a"(字母) |
[abc] |
匹配 “a、b、c” 中的任意 1 个字符(非范围,是明确列举) | [xyz] |
✅ "x",✅ "y",❌ "m" | |
[^abc] |
^在[ ]内表示 “反义”,匹配 “不在 a、b、c 中的任意 1 个字符” |
[^0-9] |
✅ "a"(非数字),❌ "3"(数字) | |
\w |
等价于[a-zA-Z0-9_],匹配 “字母、数字、下划线” 中的 1 个字符(简化写法) |
\w |
✅ "_",✅ "8",❌ "#" | |
| 量词 | {n} |
修饰前一个元素,匹配 “恰好 n 次”(n 为非负整数) | [0-9]{3} |
✅ "123"(3 位数字),❌ "12"(2 位) |
{n,} |
匹配 “至少 n 次”(n 为非负整数,无上限) | [a-z]{2,} |
✅ "ab"(2 位),✅ "abc"(3 位),❌ "a"(1 位) | |
{n,m} |
匹配 “n 到 m 次”(n≤m,闭区间) | [0-9]{2,4} |
✅ "12"(2 位),✅ "1234"(4 位),❌ "1"(1 位) | |
+ |
等价于{1,},匹配 “至少 1 次”(常用,比{1,}简洁) |
[a-z]+ |
✅ "abc"(≥1 位),❌ ""(0 次) | |
* |
等价于{0,},匹配 “0 次或多次”(允许空值) |
[0-9]* |
✅ ""(0 次),✅"123"(3 次) | |
? |
等价于{0,1},匹配 “0 次或 1 次”(表示 “可选”) |
A? |
✅ ""(0 次),✅"A"(1 次),❌"AA"(2 次) | |
| 边界 | \b |
匹配 “单词边界”(避免 “部分匹配”,如 “cat” 不匹配 “category”) | \bcat\b |
✅ "cat"(独立单词),❌ "category"(包含 cat) |
2. 实战场景:逐案例拆解(含符号细节)
场景 1:校验邮箱格式(基础版)
需求:从user_info表中筛选 “用户名@域名.后缀” 格式的邮箱(后缀 2-4 位字母,不允许空值)。SQL 语句:
SELECT user_id, email
FROM user_info
WHERE email REGEXP '^[a-zA-Z0-9_]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,4}$';
逐符号解析:
-
^[a-zA-Z0-9_]+:-
^:锚定邮箱开头; -
[a-zA-Z0-9_]:匹配邮箱前缀的 “字母、数字、下划线”; -
+:前缀至少 1 位(避免空前缀,如 “@gmail.***”);
-
-
@:匹配邮箱中的 “@” 符号(固定字符,必须存在); -
[a-zA-Z0-9]+:匹配域名(如 “gmail”“163”),+确保域名至少 1 位; -
\.:\.是转义后的 “.”,因为正则中\.表示 “匹配实际的点号”(若直接写.,会匹配 “任意字符”,错误匹配 “@gmail#***”); -
[a-zA-Z]{2,4}:匹配后缀(如 “***”“***”),{2,4}限制后缀长度 2-4 位; -
$:锚定邮箱结尾(避免后缀后加多余字符,如 “user@gmail.***123”)。
场景 2:筛选 11 位手机号(仅数字,首位 1)
需求:从contacts表中筛选 “11 位纯数字,且首位为 1” 的手机号。SQL 语句:
SELECT id, phone_number
FROM contacts
WHERE phone_number REGEXP '^1[0-9]{10}$';
逐符号解析:
-
^1:锚定开头,且首位必须是 “1”(符合手机号规则); -
[0-9]{10}:匹配后续 10 位数字({10}表示恰好 10 次,确保总长度 11 位); -
$:锚定结尾(避免 “138001380001” 这类 12 位数字)。
3. 常见细节误区(符号使用错误)
- 误区 1:忘记锚点导致部分匹配例如用
[0-9]{11}匹配手机号,会错误匹配 “138001380001”(12 位)中的前 11 位,必须加^和$(^[0-9]{11}$); - 误区 2:特殊字符未转义匹配 “.”“*”“+” 等正则特殊字符时,需用
\转义(如\.匹配点号),否则会触发符号的正则功能(如.匹配任意字符); - 误区 3:量词与字符集搭配错误例如用
[a-z]{3}+(多写一个+),MySQL 会报错 “invalid repetition count (s)”,量词不能嵌套使用。
二、窗口函数:有序统计的 “高效工具”
窗口函数是 MySQL 8.0 + 的核心特性,能够在 “不合并行” 的前提下,对数据进行分组内有序计算(如累计求和、排名)。其语法中的OVER()子句是核心,每个关键字都决定计算范围。
1. 基础语法:逐关键字解析
窗口函数的完整语法结构为:
窗口函数() OVER (
[PARTITION BY 分组字段1, 分组字段2] -- 可选:按字段拆分数据(分组)
[ORDER BY 排序字段 [ASC/DESC]] -- 必选:分组内的计算顺序
[ROWS BETWEEN 窗口范围] -- 可选:定义计算的“行范围”
) AS 别名
逐关键字解析:
-
窗口函数():需指定具体函数(如SUM()用于求和,RANK()用于排名); -
OVER():固定关键字,表示 “开启窗口计算”,括号内定义窗口规则; -
PARTITION BY:-
PARTITION:“拆分、分区” 之意; -
BY:“依据” 之意; - 功能:按指定字段将数据拆分为多个 “独立分组”(类似
GROUP BY),组内计算互不干扰(如按 “部门” 分组,计算各部门的累计工资); - 若省略:整个表视为一个分组;
-
-
ORDER BY:- 功能:定义 “分组内的计算顺序”(如按 “日期” 升序,计算每日累计销售额);
- 必须搭配:若窗口函数需要 “有序计算”(如
SUM()累计、RANK()排名),ORDER BY不可省略;
-
ROWS BETWEEN 窗口范围:-
ROWS:表示 “按行定义范围”; -
BETWEEN ... AND ...:定义范围的 “起始” 和 “结束”; - 常用范围:
-
CURRENT ROW:当前行; -
n PRECEDING:当前行的前 n 行(如2 PRECEDING表示前 2 行); -
n FOLLOWING:当前行的后 n 行(如1 FOLLOWING表示后 1 行); -
UNBOUNDED PRECEDING:分组内的第一行; -
UNBOUNDED FOLLOWING:分组内的最后一行;
-
- 若省略:默认范围为 “从分组第一行到当前行”(适合累计计算)。
-
2. 常用窗口函数:分类解析
按功能可将窗口函数分为 “聚合类”“排名类”“取值类”,每种类型的函数用途明确:
| 函数类型 | 代表函数 | 功能说明 | 关键细节 |
|---|---|---|---|
| 聚合类窗口函数 | SUM(字段) |
分组内有序求和(如累计销售额) | 需搭配ORDER BY,默认范围 “第一行到当前行” |
AVG(字段) |
分组内有序求平均(如移动平均) | 可通过ROWS BETWEEN定义移动范围 |
|
COUNT(字段) |
分组内有序计数(如累计订单数) |
COUNT(*)计数包含 NULL,COUNT(字段)不包含 |
|
| 排名类窗口函数 | RANK() |
分组内排名,相同值跳号(如 1,2,2,4) | 无参数,依赖ORDER BY的排序方向 |
DENSE_RANK() |
分组内排名,相同值不跳号(如 1,2,2,3) | 适合 “同分数同排名,后续排名连续” 场景 | |
ROW_NUMBER() |
分组内按顺序生成唯一序号(如 1,2,3,4) | 即使值相同,序号也不同 | |
| 取值类窗口函数 | LAG(字段, n, 默认值) |
取分组内 “当前行前 n 行” 的字段值 |
n默认 1,无匹配时返回默认值(如 NULL) |
LEAD(字段, n, 默认值) |
取分组内 “当前行后 n 行” 的字段值 | 常用于 “对比相邻行数据”(如当日与次日销售额) |
3. 实战场景:逐案例拆解(含范围细节)
场景 1:按部门计算工资累计和
需求:从employee表中,按 “部门(dept)” 分组,按 “工资(salary)” 升序,计算每个员工的 “部门内工资累计和”。SQL 语句:
SELECT
dept,
emp_name,
salary,
SUM(salary) OVER (
PARTITION BY dept -- 按部门分组,各部门独立计算
ORDER BY salary ASC -- 组内按工资升序,从低到高累计
-- 省略ROWS BETWEEN,默认范围:UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_salary
FROM employee;
计算过程(以 “技术部” 为例):
| dept | emp_name | salary | cumulative_salary(计算逻辑) |
|---|---|---|---|
| 技术部 | 张三 | 8000 | 8000(第一行,仅当前行) |
| 技术部 | 李四 | 10000 | 8000+10000=18000(前 1 行 + 当前行) |
| 技术部 | 王五 | 12000 | 8000+10000+12000=30000(前 2 行 + 当前行) |
场景 2:按月份计算 3 个月移动平均销售额
需求:从sales表中,按 “产品(product)” 分组,按 “月份(month)” 升序,计算每个月的 “近 3 个月销售额平均”(当前月 + 前 2 个月)。SQL 语句:
SELECT
product,
month,
sales_amount,
AVG(sales_amount) OVER (
PARTITION BY product
ORDER BY month
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW -- 明确范围:前2行到当前行
) AS moving_avg_3m
FROM sales;
计算过程(以 “产品 A” 为例):
| product | month | sales_amount | moving_avg_3m(计算逻辑) |
|---|---|---|---|
| 产品 A | 2024-01 | 1000 | 1000(仅当前行,前 2 行无数据) |
| 产品 A | 2024-02 | 2000 | (1000+2000)/2=1500(前 1 行 + 当前行) |
| 产品 A | 2024-03 | 3000 | (1000+2000+3000)/3=2000(前 2 行 + 当前行) |
| 产品 A | 2024-04 | 4000 | (2000+3000+4000)/3=3000(前 2 行 + 当前行) |
4. 与 GROUP BY 的核心区别(细节对比)
窗口函数常与GROUP BY混淆,二者的本质区别在 “行保留” 和 “计算范围”:
| 对比维度 | 窗口函数 | GROUP BY |
|---|---|---|
| 行保留情况 | 保留所有原始行,新增 “计算列”(如 cumulative_salary) | 合并分组行,仅保留 “分组字段 + 聚合结果”(如 dept+SUM (salary)) |
| 计算范围 | 分组内有序计算(可定义行范围,如前 2 行到当前行) | 分组内整体计算(无顺序,全组数据一次性聚合) |
| 关键字依赖 | 依赖OVER()定义窗口,PARTITION BY可选 |
依赖GROUP BY定义分组,必须指定分组字段 |
| 适用场景 | 累计值、排名、移动统计(需保留明细行) | 分组汇总(如部门总人数、总工资,无需明细行) |
三、条件函数:分支逻辑的 “灵活实现者”
条件函数用于根据不同条件返回不同结果,核心包括IF()、CASE WHEN、IFNULL()、COALESCE(),每个函数的语法细节决定了适用场景。
1. 核心函数:逐语法解析
(1)IF ():简单二分支判断
语法:IF(condition, value_if_true, value_if_false)逐参数解析:
-
condition:判断条件(返回布尔值:TRUE/FALSE); -
value_if_true:条件为 TRUE 时返回的值(可是字段、常量、表达式); -
value_if_false:条件为 FALSE 时返回的值(类型需与前者兼容);细节:仅支持 “二分支”,适合简单判断(如 “是 / 否”“大于 / 小于”)。
示例:从order_info表中,标记订单金额是否超过 1000(“高金额”/“普通金额”):
SELECT
order_id,
order_amount,
IF(order_amount > 1000, '高金额', '普通金额') AS amount_level
FROM order_info;
解析:order_amount > 1000为 TRUE 时,amount_level这个字段对应返回 “高金额”,否则返回 “普通金额”。
(2)CASE WHEN:多分支判断(推荐)
CASE WHEN支持 “等值判断” 和 “范围判断”,语法更灵活,是复杂条件的首选。
语法 1:等值判断(字段与固定值匹配):
CASE 目标字段
WHEN 固定值1 THEN 结果1
WHEN 固定值2 THEN 结果2
[ELSE 默认结果] -- 可选,无匹配时返回NULL
END AS 别名
逐关键字解析:
-
CASE 目标字段:指定要判断的字段(如 “user_level”); -
WHEN 固定值:判断 “目标字段是否等于固定值”; -
THEN 结果:匹配时返回的结果; -
ELSE:所有WHEN都不匹配时,返回默认结果(省略则返回 NULL); -
END:固定关键字,标记CASE逻辑结束。
示例:从user_info表中,按 “用户等级(user_level)” 标记等级名称:
SELECT
user_id,
user_level,
CASE user_level
WHEN 1 THEN '青铜'
WHEN 2 THEN '白银'
WHEN 3 THEN '黄金'
ELSE '钻石' -- 等级>3时返回“钻石”
END AS level_name
FROM user_info;
语法 2:范围判断(条件为表达式):
CASE
WHEN 条件表达式1 THEN 结果1
WHEN 条件表达式2 THEN 结果2
[ELSE 默认结果]
END AS 别名
细节:无需指定 “目标字段”,WHEN后直接写条件表达式(如age < 18),适合 “范围匹配”。
示例:从user_profile表中,按 “年龄(age)” 分组:
SELECT
user_id,
age,
CASE
WHEN age < 18 THEN '未成年'
WHEN age BETWEEN 18 AND 30 THEN '青年' -- 范围条件
WHEN age > 30 AND age <= 50 THEN '中年'
ELSE '老年'
END AS age_group
FROM user_profile;
(3)IFNULL ():处理单个 NULL 值
语法:IFNULL(expr1, expr2)逐参数解析:
-
expr1:要判断的表达式(如字段、函数结果); -
expr2:若expr1为 NULL,则返回expr2;否则返回expr1;细节:仅处理 “expr1 为 NULL” 的情况,非 NULL 的 “false 值”(如 0、空字符串)不触发替换。
示例:从product表中,若 “折扣价(discount_price)” 为 NULL,用 “原价(original_price)” 替代:
SELECT
product_id,
original_price,
discount_price,
IFNULL(discount_price, original_price) AS final_price
FROM product;
解析:若discount_price为 NULL(无折扣),final_price取original_price;否则取discount_price。
(4)COALESCE ():处理多个 NULL 值
语法:COALESCE(expr1, expr2, expr3, ...)逐参数解析:
- 从左到右依次判断每个表达式;
- 返回 “第一个非 NULL 的表达式的值”;
- 若所有表达式都为 NULL,返回 NULL;细节:支持多个参数(不限于 2 个),比
IFNULL()更灵活,适合 “优先取值” 场景。
示例:从user_contact表中,优先取 “手机号(phone)”,无则取 “邮箱(email)”,均无则取 “未填写”:
SELECT
user_id,
COALESCE(phone, email, '未填写') AS contact_info
FROM user_contact;
解析:
- 若
phone非 NULL,返回phone; - 若
phone为 NULL、email非 NULL,返回email; - 若
phone和email均为 NULL,返回 “未填写”。
2. 实战场景:计算商品折扣率(多函数结合)
需求:从product表中,计算 “折扣率 = 折扣价 / 原价”,若折扣价为 NULL,折扣率记为 0;若原价为 0(避免除以 0 错误),折扣率也记为 0,最终保留 2 位小数。SQL 语句:
SELECT
product_id,
original_price,
discount_price,
ROUND(
-- 先处理原价为0的情况,再处理折扣价为NULL的情况
IF(original_price = 0, 0, IFNULL(discount_price / original_price, 0)),
2 -- 保留2位小数
) AS discount_rate
FROM product
ORDER BY discount_rate DESC;
细节解析:
- 内层
IFNULL(discount_price / original_price, 0):处理折扣价为 NULL 的情况; - 外层
IF(original_price = 0, 0, ...):避免 “除以 0” 错误(若原价为 0,直接返回 0); -
ROUND(..., 2):对结果四舍五入,保留 2 位小数(符合业务中 “折扣率显示” 的需求)。
四\字符串函数:文本处理的 “实用工具”
MySQL 提供丰富的字符串函数,用于文本提取、分割、转换,核心包括SUBSTRING_INDEX()、CAST()、CONCAT(),每个函数的参数顺序和格式要求直接影响结果。
1. 核心函数:逐函数解析
(1)SUBSTRING_INDEX ():按分隔符提取子串
语法:SUBSTRING_INDEX(str, delimiter, count)逐参数解析:
-
str:要处理的原始字符串(如字段名、常量字符串); -
delimiter:分隔符(必须是单个字符,如 “-”“,”,不能是 “--”“,, ”); -
count:提取的位置(整数):- 正数:从左到右,取到第
count个分隔符左侧的子串; - 负数:从右到左,取到第
count个分隔符右侧的子串;关键细节:
- 正数:从左到右,取到第
- 若分隔符在
str中不存在,直接返回str(如SUBSTRING_INDEX('abc', ',', 1)返回'abc'); - 若
count的绝对值大于分隔符的出现次数,返回整个str(如SUBSTRING_INDEX('a,b,c', ',', 10)返回'a,b,c')。
示例:从 “用户地址(格式:省 - 市 - 区 - 详细地址)” 中提取 “市”:
SELECT
user_id,
address,
-- 第一次取“省-市”,第二次从“省-市”中取“市”
SUBSTRING_INDEX(SUBSTRING_INDEX(address, '-', 2), '-', -1) AS city
FROM user_info;
分步解析(以地址 “广东省 - 深圳市 - 南山区 - 科技园路” 为例):
- 内层
SUBSTRING_INDEX(address, '-', 2):从左到右取第 2 个 “-” 左侧的子串,结果为 “广东省 - 深圳市”; - 外层
SUBSTRING_INDEX(..., '-', -1):从右到左取第 1 个 “-” 右侧的子串,结果为 “深圳市”。
(2)CAST ():数据类型转换
语法:CAST(expr AS type)逐参数解析:
-
expr:要转换的表达式(如字段、函数结果,如age_str); -
AS:固定关键字,连接 “表达式” 和 “目标类型”; -
type:目标数据类型(MySQL 支持的类型,常用如下):目标类型 说明 示例 UNSIGNED无符号整数(非负整数,不支持负数) CAST('25' AS UNSIGNED)→ 25SIGNED有符号整数(支持正负) CAST('-18' AS SIGNED)→ -18DATE日期类型(格式必须为 YYYY-MM-DD)CAST('2024-01-01' AS DATE)→ 2024-01-01DECIMAL(n,m)小数类型(n:总位数,m:小数位数) CAST('3.1415' AS DECIMAL(4,2))→ 3.14CHAR字符串类型(可指定长度,如 CHAR(10))CAST(123 AS CHAR)→ '123'
关键细节:
- 若转换失败(如
CAST('abc' AS UNSIGNED)),MySQL 返回 0(非严格模式)或报错(严格模式); - 日期转换必须符合
YYYY-MM-DD格式(如CAST('01-01-2024' AS DATE)会返回 NULL,格式错误)。
示例:将 “字符串年龄(age_str,如 “25”“30”)” 转为整数,统计每个年龄的用户数:
SELECT
CAST(age_str AS UNSIGNED) AS age, -- 字符串转无符号整数
COUNT(user_id) AS user_count
FROM user_profile
GROUP BY age -- 按转换后的年龄分组
ORDER BY age ASC;
(3)CONCAT () 与 CONCAT_WS ():字符串拼接
CONCAT () 语法:CONCAT(str1, str2, str3, ...)逐参数解析:
- 多个
str参数:要拼接的字符串(字段、常量均可); - 拼接规则:按参数顺序拼接,若任意参数为 NULL,最终结果为 NULL;示例:拼接 “用户名(user_name)” 和 “用户等级(user_level)” 为 “用户标签”:
SELECT
user_id,
CONCAT(user_name, '-LV', user_level) AS user_tag
FROM user_info;
结果:若user_name='张三'、user_level=2,返回'张三-LV2';若user_level为 NULL,返回 NULL。
CONCAT_WS () 语法:CONCAT_WS(separator, str1, str2, ...)逐参数解析:
-
separator:固定分隔符(如 “-”“|”,会插入到每个非 NULL 参数之间); -
str1, str2:要拼接的字符串; - 核心优势:忽略 NULL 参数(仅拼接非 NULL 的字符串,分隔符不会出现在开头 / 结尾);示例:用 “-” 拼接 “姓名、年龄、城市”,忽略 NULL 值:
SELECT
user_id,
CONCAT_WS('-', user_name, age, city) AS user_info
FROM user_info;
结果:若user_name='李四'、age=28、city=NULL,返回'李四-28'(忽略 city 的 NULL,无多余分隔符)。
2. 实战场景:从日志中提取用户 ID(多函数结合)
需求:log_info表的 “日志内容(log_content)” 格式为 “用户 ID:123, 操作:登录,时间:2024-01-01”,提取 “用户 ID” 并统计每个用户的登录次数。SQL 语句:
SELECT
-- 步骤1:取“用户ID:123”;步骤2:取“123”;步骤3:转为整数
CAST(
SUBSTRING_INDEX(
SUBSTRING_INDEX(log_content, ',', 1), -- 按“,”分割,取第1部分:“用户ID:123”
':', -1 -- 按“:”分割,从右取第1部分:“123”
) AS UNSIGNED
) AS user_id,
COUNT(log_id) AS login_count -- 统计登录次数
FROM log_info
WHERE log_content LIKE '用户ID:%,操作:登录%' -- 筛选登录日志(避免其他类型日志)
GROUP BY user_id
ORDER BY login_count DESC;
细节解析:
-
LIKE '用户ID:%,操作:登录%':用通配符%筛选 “包含用户 ID 且操作是登录” 的日志,避免提取无效数据; - 多函数嵌套:
SUBSTRING_INDEX提取字符串→CAST转换类型→COUNT统计次数,实现 “提取 - 转换 - 统计” 全流程。
五、UNION 与结果集合并:多条件查询的 “汇总工具”
UNION和UNION ALL用于合并多个SELECT语句的结果集,核心区别在 “是否去重”,语法细节决定结果的完整性和性能。
1. 核心语法:逐操作符解析
(1)UNION 与 UNION ALL 的语法
-- UNION:合并并去重
SELECT 字段1, 字段2 FROM 表1 WHERE 条件1
UNION
SELECT 字段1, 字段2 FROM 表2 WHERE 条件2;
-- UNION ALL:合并不去重
SELECT 字段1, 字段2 FROM 表1 WHERE 条件1
UNION ALL
SELECT 字段1, 字段2 FROM 表2 WHERE 条件2;
逐关键字解析:
-
UNION:固定关键字,合并多个SELECT结果集,并自动删除重复行(相当于UNION ALL + DISTINCT); -
UNION ALL:固定关键字,仅合并结果集,保留所有行(包括重复行); - 关键规则:
- 多个
SELECT的 “列数必须一致”(如第一个SELECT取 2 列,后续也必须取 2 列); - 对应列的 “数据类型必须兼容”(如整数与浮点数可兼容,字符串与整数不兼容,会报错);
- 列名以 “第一个
SELECT的列名为准”(后续SELECT的列名不影响最终结果的列名); - 若需排序,
ORDER BY必须写在 “最后一个SELECT的末尾”(对合并后的整个结果集排序)。
- 多个
(2)UNION 与 UNION ALL 的核心区别
| 对比维度 | UNION | UNION ALL |
|---|---|---|
| 去重功能 | 自动去重(删除重复行) | 不去重(保留所有行,包括重复行) |
| 性能 | 较低(需额外执行去重操作) | 较高(直接合并,无额外操作) |
| 结果行数 | 小于等于所有SELECT行数之和 |
等于所有SELECT行数之和 |
| 适用场景 | 需合并且不允许重复行(如合并 “会员订单” 和 “非会员订单”,避免重复) | 无需去重或确认无重复行(如合并 “2023 年订单” 和 “2024 年订单”,无时间重叠) |
2. 实战场景:统计会员与非会员的订单量
需求:从order_info表中,分别统计 “会员用户(is_vip=1)” 和 “非会员用户(is_vip=0)” 的订单量,合并结果并按订单量降序排序。SQL 语句:
-- 统计会员订单量(第一个SELECT,列名以它为准)
SELECT
'会员' AS user_type, -- 自定义类型标识
COUNT(order_id) AS order_count
FROM order_info
WHERE is_vip = 1
UNION ALL -- 合并不去重(会员与非会员无重复,无需去重)
-- 统计非会员订单量(列数与第一个SELECT一致,数据类型兼容)
SELECT
'非会员' AS user_type,
COUNT(order_id) AS order_count
FROM order_info
WHERE is_vip = 0
ORDER BY order_count DESC; -- 对合并后的结果排序(写在最后)
结果示例:
| user_type | order_count |
|---|---|
| 会员 | 500 |
| 非会员 | 300 |
| 细节解析: |
-
'会员' AS user_type:自定义字符串作为 “用户类型” 标识,确保两个SELECT的列名和类型一致; - 用
UNION ALL而非UNION:会员(is_vip=1)和非会员(is_vip=0)是互斥条件,结果无重复行,用UNION ALL性能更高; -
ORDER BY order_count DESC:对合并后的整个结果集排序,若写在第一个SELECT后,仅对第一个结果集排序,不影响整体。
3. 常见细节误区
- 误区 1:列数不一致例如第一个
SELECT取 2 列,第二个取 3 列,MySQL 会报错 “The used SELECT statements have a different number of columns”; - 误区 2:数据类型不兼容例如第一个
SELECT的列 1 是字符串('会员'),第二个的列 1 是整数(1),MySQL 会报错 “Column 1 of UNION has in***patible types”; - 误区 3:不必要的 UNION若可通过
OR或CASE WHEN实现(如统计 “金额> 1000 或金额 < 200 的订单”),优先用单SELECT(WHERE order_amount > 1000 OR order_amount < 200),避免UNION的性能损耗。