做MySQL开发这些年,凡是涉及到条件判断、空值处理、报表统计的SQL,几乎没有不用到逻辑函数的。很多新手朋友写SQL查询,一遇到“某个字段为空就取另一个值”“满足条件打标签”“防止除零报错”这些需求,第一反应就是去翻文档,或者直接写一堆IF嵌套,代码又长又难读。其实MySQL的逻辑函数体系非常成熟,用好了能大幅简化SQL,还能避开不少隐性的坑。这篇文章就把我实际项目中常用的逻辑函数、运算符、踩过的坑,一次性说清楚,顺便给点能直接抄作业的实战示例,适合做数据开发、后端开发、数据分析的朋友参考。
1. 先搞清楚MySQL逻辑函数到底解决什么问题
1.1 逻辑函数的核心价值
逻辑函数本质上是SQL语言里的“判断器”和“分流器”。你在写SQL时,大多数查询不是简单的“查出来就完事”,而是需要根据字段值动态决定输出内容。比如一个订单表,状态字段是数字,1代表待付款、2代表已付款、3代表已取消,你总不能每次查完数据后在Java、Python里自己再转换一遍吧?用逻辑函数直接在SQL层面就把状态“翻译”成人类可读的文本,后端代码立马清爽很多。
再比如做数据清洗时,很多表里会有陈年历史数据,字段里存了NULL、空字符串、甚至“null”这种字符串,逻辑函数就是你第一道防线,可以统一把这些“脏值”归一成默认值。数据统计场景更不用说了,求和之前先判断字段是否合法,做分组统计时按条件打标分类,全是逻辑函数的主场。
而学习逻辑函数的真正门槛,不在于记住语法,而在于理解MySQL与众不同的“三值逻辑”。只要把三值逻辑这个点弄明白,再用任何逻辑函数都会顺手很多。
1.2 三值逻辑:MySQL和别的语言不一样的地方
接触过Java或者Python的朋友都知道,布尔类型基本就是true和false二选一,写完if (a > b),结果要么是true,要么是false,不会有第三种情况。但MySQL不一样,SQL标准里引入了NULL这个概念,任何一个比较表达式的结果,除了TRUE和FALSE之外,还有可能是NULL。
举一个最经典的例子:
sql复制SELECT NULL > 5, NULL = NULL, NULL AND TRUE;
三个表达式的结果,分别是NULL、NULL、NULL。注意第二列,NULL = NULL的结果依然是NULL,而不是1。这个点让无数人踩坑,因为直觉上NULL应该等于NULL。实际上NULL代表“未知”,两个“未知”之间没法比较是否相等,所以结果仍然是未知。
所以你在用逻辑函数之前,脑子里必须有这么一张表:
| 表达式 | 结果 | 解释 |
|---|---|---|
TRUE AND NULL |
NULL | 真和未知,结果不确定 |
FALSE AND NULL |
FALSE | 假和未知,整体必然为假 |
TRUE OR NULL |
TRUE | 真和未知,整体必然为真 |
FALSE OR NULL |
NULL | 假和未知,结果不确定 |
NOT NULL |
NULL | 对未知取反,依然未知 |
这四条规则是逻辑函数和逻辑运算符所有行为的根基。比如下面这段SQL,如果你不理解三值逻辑,很容易写出“看起来没问题但结果永远不对”的查询:
sql复制SELECT * FROM users WHERE name != 'admin';
如果name字段里有NULL值,这一行的name != 'admin'结果是NULL,在WHERE条件里NULL相当于“不成立”,所以这一行永远查不出来。很多新手排查半天,发现数据明明在表里,就是查不出来,八成就是NULL在作怪。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 五个最常用的逻辑函数逐个拆解
2.1 IF():最简单的条件分支
IF()函数的语法非常直白:
sql复制IF(expr1, expr2, expr3)
如果expr1为TRUE,返回expr2,否则返回expr3。看起来和三元运算符一模一样。
我整理一个线上真实用过的例子。订单表里有一个payment_status字段,1代表已支付,0代表未支付。查询时要把这个数字转成文字,最简单的写法就是:
sql复制SELECT
order_id,
IF(payment_status = 1, '已支付', '未支付') AS payment_status_text
FROM orders;
IF()乍一看很简单,但有几个细节非常容易忽略。
第一,expr1的结果会被当成条件判断,而MySQL会做隐式类型转换。如果expr1是数字,0当作FALSE,非0当作TRUE;如果expr1是字符串,空字符串当作FALSE,其他字符串当作TRUE。所以IF('0', 'yes', 'no')返回的是no,因为字符串'0'会被转换为数字0。这个场景我还真踩过,业务接口传了个字符串'0'进来,直接用IF判断就翻车了。
第二,IF()嵌套不要超过三层。嵌套过多,SQL可读性会急剧下降。一旦分支逻辑超过三个判断,我建议直接换成CASE WHEN,代码会清晰得多。
第三,IF()在WHERE子句里要慎用。很多人喜欢写WHERE IF(a > 1, 1, 0) = 1,这会让MySQL对每行数据都做一次函数计算,相当于放弃了索引优化空间。后文我会专门讲这个问题。
2.2 IFNULL():空值处理的头号选择
IFNULL的语法是:
sql复制IFNULL(expr1, expr2)
如果expr1不是NULL,返回expr1,否则返回expr2。它和IF()的区别在于,IFNULL只针对NULL做判断,不会去判断空字符串、0这些“假值”,它只认准“是否为NULL”这一件事。
这个函数用得最多的场景,就是“查出来的数据可能为空,要给它一个兜底值”。比如用户表里nickname允许为空,查询时要保证接口一定返回字符串,不能返回NULL给前端:
sql复制SELECT
user_id,
IFNULL(nickname, '匿名用户') AS nickname
FROM users;
空值处理有个容易混淆的点:空字符串和NULL根本不是一回事。IFNULL('', 'default')返回的是空字符串,不是default。因为空字符串不是NULL。如果是历史脏数据里有空字符串,你得IFNULL(NULLIF(TRIM(nickname), ''), '匿名用户')三件套联合使用才行。这个组合拳后面讲NULLIF函数时会展开说。
2.3 NULLIF():防止除零和特殊比较
NULLIF函数的语法是:
sql复制NULLIF(expr1, expr2)
当expr1等于expr2时返回NULL,否则返回expr1。它本质上是一个“比较后做特殊标记”的函数。
最常见的实战场景是防止除零错误。比如我们要计算商品的折扣率,数据库表里original_price是原价,actual_price是实际售价,折扣率就是实际售价除原价。但有些商品的original_price可能是0,直接相除会报ERROR 1365: Division by 0。用NULLIF就可以优雅解决:
sql复制SELECT
product_id,
actual_price / NULLIF(original_price, 0) AS discount_rate
FROM products;
当original_price为0时,NULLIF返回NULL,整个除法表达式的结果也是NULL。虽然结果还是NULL,但至少不会报错,而且你可以继续用IFNULL把NULL转成0或者空字符串,表示“该商品无处计算折扣率”。
2.4 COALESCE():多候选值的兜底方案
COALESCE函数接受多个参数:
sql复制COALESCE(expr1, expr2, expr3, ...)
它从左到右依次判断,返回第一个非NULL的值。如果参数全是NULL,就返回NULL。
这个函数的大杀器在于,它可以同时处理多个字段的“备选回退”逻辑。比如一个客户联系方式表,字段有mobile、email、wechat,现在要导出一份客户联系方式清单,优先取手机号,手机号为空取邮箱,邮箱为空取微信:
sql复制SELECT
customer_id,
COALESCE(mobile, email, wechat) AS contact
FROM customers;
一行SQL解决,干净利落。如果用IF嵌套,你得写两三个IF才能搞定,代码混乱且容易出错。
COALESCE还有一个特性容易被人忽略:它的参数并不要求是简单字段,可以嵌套函数、子查询等。比如我做过一个需求,用户表里level字段空了,要去另一张等级表里查默认等级,代码大概是:
sql复制SELECT
COALESCE(level, (SELECT default_level FROM user_level_config WHERE id = 1))
FROM users;
这种写法能极大提高SQL的表达能力。但要注意子查询的返回结果,如果子查询本身返回多行会报错,所以更稳妥的做法是配合LIMIT 1使用。
2.5 CASE WHEN:复杂逻辑的终极武器
说句实在话,我工作里写的最多的逻辑函数其实是CASE WHEN。它有两种语法,各有适用场景。
第一种是简单搜索:
sql复制CASE expression
WHEN value1 THEN result1
WHEN value2 THEN result2
ELSE resultN
END
适合判断一个表达式等于某个值的情况。比如把订单状态数字转成文本:
sql复制SELECT
order_id,
CASE payment_status
WHEN 1 THEN '待支付'
WHEN 2 THEN '已支付'
WHEN 3 THEN '已取消'
ELSE '未知状态'
END AS status_text
FROM orders;
第二种是条件搜索:
sql复制CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE resultN
END
适合复杂条件判断。比如用户等级划分:
sql复制SELECT
user_id,
CASE
WHEN total_amount >= 10000 THEN 'VIP客户'
WHEN total_amount >= 5000 THEN '重点客户'
WHEN total_amount >= 1000 THEN '普通客户'
ELSE '新客户'
END AS user_level
FROM users;
CASE WHEN不仅仅是查询数据时用,UPDATE语句里也能发挥奇效。我做过一个批量更新操作,需求是把所有订单的运费统一调整,基础运费8元,订单金额超过100元的免运费,超过50元减3元。一个UPDATE加CASE WHEN就解决了:
sql复制UPDATE orders
SET shipping_fee = CASE
WHEN order_amount >= 100 THEN 0
WHEN order_amount >= 50 THEN 5
ELSE 8
END;
如果用代码去逐条更新,要写一堆循环,效率低了不知道多少倍。
CASE WHEN还有一个显著优点:它属于SQL标准表达式,可读性远高于多层IF嵌套。团队协作时,别人看你写的SQL,基本上能一目了然地明白业务分支,所以我强烈建议,超过两个条件分支就用CASE WHEN,别硬堆IF。
3. 逻辑运算符和三值逻辑的相爱相杀
3.1 AND、OR、NOT、XOR到底怎么算
除了函数,MySQL还支持逻辑运算符:AND(&&)、OR(||)、NOT(!)、XOR。函数是显式判断,运算符则可以直接连在WHERE条件里。它们和三值逻辑结合时,行为必须烂熟于心。
我直接给一张完整真值表:
| A | B | A AND B | A OR B | A XOR B | NOT A |
|---|---|---|---|---|---|
| TRUE | TRUE | TRUE | TRUE | FALSE | FALSE |
| TRUE | FALSE | FALSE | TRUE | TRUE | FALSE |
| TRUE | NULL | NULL | TRUE | NULL | FALSE |
| FALSE | TRUE | FALSE | TRUE | TRUE | TRUE |
| FALSE | FALSE | FALSE | FALSE | FALSE | TRUE |
| FALSE | NULL | FALSE | NULL | NULL | TRUE |
| NULL | TRUE | NULL | TRUE | NULL | NULL |
| NULL | FALSE | FALSE | NULL | NULL | NULL |
| NULL | NULL | NULL | NULL | NULL | NULL |
这个表不用死记,理解两个核心直觉就行:
第一,AND很严格,只要有一个FALSE,结果就是FALSE;只要没有FALSE但有NULL,结果就是NULL。
第二,OR很宽松,只要有一个TRUE,结果就是TRUE;只要没有TRUE但有NULL,结果就是NULL。
至于XOR(异或),它的含义是“两个条件恰好一个为真”,NULL参与时基本都会变成NULL。
实战中最容易翻车的写法,就是把NULL值直接拼进逻辑表达式。比如查所有“不是VIP”的用户:
sql复制SELECT * FROM users WHERE NOT is_vip = 1;
如果is_vip为NULL,NOT NULL依然是NULL,所以在WHERE里不成立,这条数据就被过滤掉了。正确写法是:
sql复制SELECT * FROM users WHERE is_vip != 1 OR is_vip IS NULL;
这个坑我在排查线上数据不一致问题时遇到过好几次,每次都是这种逻辑遗漏导致的。说到底还是对三值逻辑的理解不够。
3.2 短路求值:MySQL也会偷懒
还有一个容易被忽略的特性:MySQL的AND和OR是支持短路求值的。也就是说,如果一个表达式的结果已经能确定整个逻辑表达式的结果,MySQL就不会继续计算后面的部分。
举个例子:
sql复制SELECT 1 OR 100 / 0;
这个查询返回1,不会报除零错误。因为1 OR 任何东西的结果必定是TRUE,MySQL根本不去计算100 / 0。同样:
sql复制SELECT 0 AND 100 / 0;
返回0,也不会报除零错误。
这个特性可以当成一个小技巧来用。比如你想判断一个字符串是否满足条件,但是只有在字符串非空时才调用函数:
sql复制SELECT * FROM users WHERE name IS NOT NULL AND LENGTH(name) > 5;
如果name为NULL,第一个条件已经是FALSE,LENGTH(name)根本不会执行,也就不会产生NULL比较的副作用。虽然这个例子看似无害,但在复杂查询里,善用短路求值可以避免很多不必要的函数计算,对性能也有正面作用。
3.3 优先级的问题
逻辑运算符的优先级也非常重要。MySQL中优先级从高到低大致是NOT > AND > OR。很多人写条件时不自加括号,结果逻辑和预期完全相反。
举例来说:
sql复制SELECT * FROM products WHERE is_active = 1 OR is_stock = 1 AND is_deleted = 0;
你以为的逻辑是(is_active = 1 OR is_stock = 1) AND is_deleted = 0,但MySQL的实际逻辑是is_active = 1 OR (is_stock = 1 AND is_deleted = 0)。一个条件没加括号,查询结果可能完全不一样。
我的个人习惯是:只要WHERE里同时出现AND和OR,就无条件给OR两边的条件都加上括号。宁可多写一对括号,也不能让优先级坑了自己。这是写SQL的基本修养,避免线上事故的重要一条。
4. 综合实操:从订单表到用户画像的经典案例
4.1 业务场景设计
光讲函数语法没意思,我把它们放到一个综合业务场景里串一遍。假设我们有一个电商平台,订单表orders结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| order_id | INT | 订单ID |
| user_id | INT | 用户ID |
| order_amount | DECIMAL(10,2) | 订单金额 |
| payment_status | TINYINT | 1待支付,2已支付,3已取消 |
| coupon_id | INT | 使用的优惠券ID,可为NULL |
| shipping_fee | DECIMAL(10,2) | 运费 |
| paid_at | DATETIME | 支付时间,可为NULL |
现在有三个报表需求:
第一个,输出每个订单的支付状态文字、是否使用了优惠券、实付金额(订单金额加运费)。
第二个,按用户维度统计“有效订单数”和“有效订单总额”,其中有效订单指已支付且未取消的订单。
第三个,为用户打标签:日均消费超过200元且至少完成3笔有效订单的,打上“高价值用户”。
4.2 逐层实现SQL
第一个需求,直接用IF、IFNULL、CASE WHEN联合实现:
sql复制SELECT
order_id,
CASE payment_status
WHEN 1 THEN '待支付'
WHEN 2 THEN '已支付'
WHEN 3 THEN '已取消'
ELSE '未知状态'
END AS order_status,
IF(coupon_id IS NOT NULL, '有优惠券', '无优惠券') AS coupon_flag,
IFNULL(order_amount, 0) + IFNULL(shipping_fee, 0) AS actual_amount
FROM orders;
这里的IFNULL很重要,订单金额和运费在历史数据里可能有NULL,直接相加会让结果变成NULL,加起来等于没用。先转成0再相加,才能保证报表数据的健壮性。
第二个需求,统计用户的有效订单数,需要用到CASE WHEN配合聚合函数。注意一个常见错误,很多人会直接写COUNT(CASE WHEN payment_status = 2 THEN 1 ELSE 0 END),这样会统计出所有行,因为COUNT函数会数所有非NULL值,而ELSE分支把0也算进去了。正确写法有两种:
sql复制SELECT
user_id,
COUNT(CASE WHEN payment_status = 2 THEN 1 END) AS valid_order_count,
SUM(CASE WHEN payment_status = 2 THEN order_amount ELSE 0 END) AS valid_order_amount
FROM orders
GROUP BY user_id;
注意第一种写法里,CASE WHEN没有写ELSE,当分支不匹配时返回NULL,COUNT就会自动忽略NULL值,从而只统计匹配的订单。这个细节特别容易踩坑,我见过不少同事在这里翻车,统计出来的数量莫名其妙地多了一倍。
第三个需求,用户打标签。基于第二个需求的结果继续嵌套。这里可以把上一步的统计结果作为子查询,再在外层做判断:
sql复制SELECT
user_id,
valid_order_count,
valid_order_amount,
CASE
WHEN valid_order_count >= 3
AND valid_order_amount / 30 >= 200 THEN '高价值用户'
WHEN valid_order_count >= 1 THEN '普通用户'
ELSE '沉睡用户'
END AS user_label
FROM (
SELECT
user_id,
COUNT(CASE WHEN payment_status = 2 THEN 1 END) AS valid_order_count,
SUM(CASE WHEN payment_status = 2 THEN order_amount ELSE 0 END) AS valid_order_amount
FROM orders
GROUP BY user_id
) t;
这里我假设统计周期为30天,所以用valid_order_amount / 30作为日均消费估算。实际项目中周期判断可以用WHERE paid_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)在子查询里提前过滤,逻辑更准确,我这里为了演示函数用法简化了。
其实这套组合逻辑,在工作中非常典型:先聚合,后打标。而条件打标的最佳实现,就是CASE WHEN配合聚合函数嵌套。你会发现整个SQL里没有一句暴力的IF嵌套,全部是清晰的条件表达式。
4.3 在存储过程中使用逻辑函数
逻辑函数在存储过程中也极其常用。比如我们要写一个存储过程,根据订单ID计算实付金额,并同步更新订单表:
sql复制DELIMITER $$
CREATE PROCEDURE update_order_actual_amount(IN p_order_id INT)
BEGIN
DECLARE v_amount DECIMAL(10,2);
DECLARE v_fee DECIMAL(10,2);
SELECT order_amount, shipping_fee
INTO v_amount, v_fee
FROM orders
WHERE order_id = p_order_id;
UPDATE orders
SET actual_amount =
IFNULL(v_amount, 0) + IFNULL(v_fee, 0),
update_time = NOW()
WHERE order_id = p_order_id;
END$$
DELIMITER ;
这里IFNULL再次发挥作用:如果表中的字段为NULL,直接相加结果为NULL,写入后会把原本正常的actual_amount字段也污染掉。对这种数据质量问题,宁可在存储过程里多写几个判断,也不能让NULL值在业务层泛滥。
5. 常见问题与排查技巧实录
5.1 NULL导致的行“莫名其妙消失”
排查过很多线上SQL问题后,我可以负责任地说,逻辑相关的报错大多不是语法错误,而是NULL导致的“数据不符预期”。最典型的案例就是:
sql复制SELECT COUNT(*) FROM orders WHERE coupon_id != 1;
你预期的是统计所有没有使用ID为1优惠券的订单,但实际上,coupon_id为NULL的订单根本不会被统计进去。因为NULL != 1的结果是NULL,在WHERE条件里永远不为TRUE。
正确的做法是:
sql复制SELECT COUNT(*) FROM orders WHERE coupon_id != 1 OR coupon_id IS NULL;
或者反过来用IFNULL提前转换:
sql复制SELECT COUNT(*) FROM orders WHERE IFNULL(coupon_id, -1) != 1;
这种“字段为NULL导致行消失”的问题,在聚合、JOIN、子查询中都会出现。排查思路就一条:一旦发现查询结果比预期少,第一时间检查WHERE条件涉及的字段是否允许NULL,把可能的NULL值全部考虑进去。
5.2 IF()函数在WHERE子句中使用会拖慢查询
我前面提过,WHERE子句里使用IF()函数,通常会导致索引失效。比如:
sql复制SELECT * FROM orders WHERE IF(status = 1, 1, 0) = 1;
这等同于对每一行做一次函数运算,MySQL无法直接利用status字段上的索引。你应该改成:
sql复制SELECT * FROM orders WHERE status = 1;
也就是把函数计算从WHERE条件里挪到SELECT列表中,让WHERE只保留可索引的比较条件。如果在SELECT列表里仍然需要IF做转换,那不影响索引。这是SQL优化里的一个基本常识,但在逻辑函数里特别容易踩到,因为IF()用起来实在太顺手了。
5.3 隐式类型转换会改变判断结果
MySQL在比较不同类型时会做隐式转换。当字符串和数字比较时,字符串会被转换成数字。比如:
sql复制SELECT IF('abc' = 0, 'equal', 'not equal');
结果是equal。因为'abc'转换为数字时是0,0等于0,所以返回equal。这个结果很反直觉,但凡是做过几年MySQL的人基本都踩过这种坑。
所以在写逻辑函数和逻辑表达式时,要特别留意字段类型。尤其是从接口或者外部系统同步过来的数据,字段类型往往是VARCHAR,但存的内容是数字。这种表在做逻辑判断时,稍不留神就会触发隐式转换,导致结果和预期不一致。我的建议是,在关键判断里显式加上类型转换:
sql复制SELECT IF(CAST(mobile AS CHAR) = '13800138000', 'match', 'not match');
虽然多写了一个CAST,但可读性和准确性都得到了保障。
5.4 函数嵌套导致可读性灾难
有人喜欢把函数嵌套到极致,写出一行几百字符的SQL,比如:
sql复制SELECT IFNULL(IFNULL(a, IFNULL(b, 'default')), 'default');
说实话,这种写法能跑,但别人维护起来想骂人。COALESCE函数就是专门解决多层IFNULL嵌套问题的:
sql复制SELECT COALESCE(a, b, 'default');
理解了吗?当参数数量变多时,COALESCE的可读性和可维护性完胜嵌套的IFNULL。同理,多个IF嵌套完全可以用CASE WHEN替代。这不仅仅是代码风格问题,也直接关系到后续排查和交接的效率。
我还碰到过一个情况,同事写了个嵌套IF判断三天不同区域的销售指标,缩进错位导致谁都读不懂。最后我花半小时把整个逻辑换成了CASE WHEN,分支一目了然。从那以后,我们团队定了一条规范:超过两层的IF嵌套必须重写为CASE WHEN,超过三层的IFNULL嵌套必须改用COALESCE。
5.5 逻辑函数的返回值类型容易忽略
IF()、CASE WHEN这类函数,不同分支返回的数据类型可能不一致。MySQL虽然会自动做类型转换,但结果可能出乎意料。比如:
sql复制SELECT IF(1, '100', 100);
看起来一个是字符串'100',一个是数字100,拼接结果会如何?MySQL会以第一个分支的类型为主,返回字符串'100'。听起来没问题,但如果你把数字分支放在第一位:
sql复制SELECT IF(0, 100, '100');
返回的是数字100,因为第一分支是数字。这种隐式类型规则在使用时很容易忽略,尤其是当分支返回值后续还要参与计算时,类型不一致可能导致运算结果异常。
我的建议是,在写逻辑函数分支时,尽量保持所有分支的数据类型一致。如果确实无法一致,就显式使用CAST做转换,避免MySQL的隐式转换规则坑你一把。
6. 几个容易被忽略的高级用法
6.1 用逻辑函数构造维度列做透视报表
在报表SQL中,逻辑函数经常用来构造维度列。比如按时间段统计订单:
sql复制SELECT
CASE
WHEN HOUR(paid_at) < 6 THEN '凌晨'
WHEN HOUR(paid_at) < 12 THEN '上午'
WHEN HOUR(paid_at) < 18 THEN '下午'
ELSE '晚上'
END AS time_slot,
COUNT(*) AS order_count,
SUM(order_amount) AS total_amount
FROM orders
WHERE payment_status = 2
GROUP BY time_slot;
这种SQL在管理后台的报表功能里几乎天天用。核心思路就是先构造一个逻辑维度列,再用这个维度列做GROUP BY。注意,MySQL的GROUP BY可以用SELECT列表里的别名,所以我可以直接写GROUP BY time_slot,而不需要重复CASE WHEN。这个特性在不同版本的MySQL中都支持,但在一些老版本或严格模式下会有差异,建议实际测试确认。
6.2 逻辑拼接实现动态条件查询
有时候我们在写搜索接口时,需要根据传入参数动态拼SQL。用逻辑函数也能做到一定的动态效果。比如:
sql复制SELECT *
FROM products
WHERE (p_keyword = '' OR name LIKE CONCAT('%', p_keyword, '%'))
AND (p_category = 0 OR category_id = p_category);
当p_keyword为空字符串时,第一个条件恒为TRUE,name LIKE判断被跳过;当p_category为0时,第二个条件恒为TRUE,分类过滤被跳过。这种写法可以避免在业务代码里动态拼接SQL字符串,减少注入风险,也让SQL的可维护性更强。
但要注意,这种写法并不能在所有场景下都用到索引。数据量大的时候,更推荐在应用层做条件拼接,或者使用存储过程和PREPARE动态SQL。我这个经验是在几百万数据的表上验证过的,OR条件一旦存在,优化器可能放弃索引,查询性能会受到明显影响。数据量小时无所谓,上了千万级要认真分析执行计划。
写到最后的一点个人体会
做MySQL开发越久,越觉得逻辑函数是“小功能大智慧”的代表。语法就那么几个,真要写好写对,背后是对三值逻辑、隐式转换、函数执行时机这些细节的透彻理解。我现在每次写SQL前,都会先问自己三个问题:这个字段有没有可能为NULL?这个条件的判断结果有没有可能是NULL?这个运算会不会触发隐式类型转换?这三个问题想清楚,SQL基本不会出大错。
如果你刚开始接触MySQL逻辑函数,我建议别急着背语法,拿一张真实业务表,把IF、IFNULL、NULLIF、COALESCE、CASE WHEN逐个跑一遍,再故意往表里插入几条NULL值的数据看输出变化,比看十篇文章都管用。等遇到那种“数据明明在,怎么查不出来”的问题时,你回顾一下这篇文章里提到的三值逻辑,会豁然开朗。SQL优化和排错这事,说到底就是经验和细节的积累,我现在分享的这些坑,几乎都是从线上环境里一个个踩出来的,希望能帮你少走几次弯路。
