【MySQL】9.吃透关键SQL语法:从正则表达式、窗口函数、条件函数到结果集合并的实战拆解

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 WHENIFNULL()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_priceoriginal_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
  • phoneemail均为 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;

分步解析(以地址 “广东省 - 深圳市 - 南山区 - 科技园路” 为例):

  1. 内层SUBSTRING_INDEX(address, '-', 2):从左到右取第 2 个 “-” 左侧的子串,结果为 “广东省 - 深圳市”;
  2. 外层SUBSTRING_INDEX(..., '-', -1):从右到左取第 1 个 “-” 右侧的子串,结果为 “深圳市”。

(2)CAST ():数据类型转换

语法CAST(expr AS type)逐参数解析

  • expr:要转换的表达式(如字段、函数结果,如age_str);
  • AS:固定关键字,连接 “表达式” 和 “目标类型”;
  • type:目标数据类型(MySQL 支持的类型,常用如下):
    目标类型 说明 示例
    UNSIGNED 无符号整数(非负整数,不支持负数) CAST('25' AS UNSIGNED) → 25
    SIGNED 有符号整数(支持正负) CAST('-18' AS SIGNED) → -18
    DATE 日期类型(格式必须为YYYY-MM-DD CAST('2024-01-01' AS DATE) → 2024-01-01
    DECIMAL(n,m) 小数类型(n:总位数,m:小数位数) CAST('3.1415' AS DECIMAL(4,2)) → 3.14
    CHAR 字符串类型(可指定长度,如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=28city=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 与结果集合并:多条件查询的 “汇总工具”

UNIONUNION 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:固定关键字,仅合并结果集,保留所有行(包括重复行)
  • 关键规则:
    1. 多个SELECT的 “列数必须一致”(如第一个SELECT取 2 列,后续也必须取 2 列);
    2. 对应列的 “数据类型必须兼容”(如整数与浮点数可兼容,字符串与整数不兼容,会报错);
    3. 列名以 “第一个SELECT的列名为准”(后续SELECT的列名不影响最终结果的列名);
    4. 若需排序,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若可通过ORCASE WHEN实现(如统计 “金额> 1000 或金额 < 200 的订单”),优先用单SELECTWHERE order_amount > 1000 OR order_amount < 200),避免UNION的性能损耗。

 

 

转载请说明出处内容投诉
CSS教程网 » 【MySQL】9.吃透关键SQL语法:从正则表达式、窗口函数、条件函数到结果集合并的实战拆解

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买