最省事安全的方式是在where中用(@param is null or column = @param)实现参数可选,sql server能识别并走索引seek;字符串需额外判空,数值日期一般只判is null;复杂场景须动态sql但必须用sp_executesql传参;排序和视图参数需特殊处理。

WHERE里用@param IS NULL OR column = @param最省事也最安全
多数场景下,根本不用拼SQL字符串。直接在WHERE里写(@status IS NULL OR status = @status)就能让参数“可选”。SQL Server优化器能识别这种模式,只要字段有索引,且参数非NULL,通常仍能走Seek。
- 括号不能少:漏掉会导致
AND优先级高于OR,逻辑错乱 - 字符串要额外判空:
(@name IS NULL OR @name = '' OR t.Name = @name),否则传空串会查不出数据 - 数值/日期类型一般只判
IS NULL,除非业务明确允许用0或'1900-01-01'作占位符 - 前端传来的空字符串建议在存储过程开头统一转成
NULL,避免每个条件都重复判断
动态拼接WHERE时必须用sp_executesql传参,不能EXEC(@sql)
一旦参数太多、过滤逻辑太复杂(比如含BETWEEN、LIKE、多值IN),硬套OR结构可能让执行计划退化成Scan。这时得动态拼SQL,但绝不能用EXEC(@sql)——它等于把SQL注入和执行计划失效一起打包送上门。
-
@sql只拼骨架:IF @start_date IS NOT NULL SET @sql += ' AND created_at >= @start_param' - 参数定义字符串必须完整列出所有可能用到的参数,哪怕某次没拼进去:
N'@start_param DATETIME, @status_param VARCHAR(20)' - 调用
sp_executesql时,所有声明过的参数都得传值,缺一个就报Must declare the scalar variable - 别信
QUOTENAME()能防住所有问题:它只处理标识符,对值无效;且每次执行都重新编译,缓存不了执行计划
排序字段和方向必须白名单+CASE,不能拼字符串
用@order_by控制排序字段,如果直接拼进ORDER BY字符串,不仅被注入,还会让SQL Server拒绝缓存执行计划。正确做法是把字段名硬编码进CASE分支里,参数只决定走哪条路。
-
@order_by类型用VARCHAR(32),值限定为'name'、'created_at'等预设字段 - 每个字段对应一个
CASE分支,类型要一致(比如都转成VARCHAR),否则报Conversion failed - 升降序用
@sort_dir BIT控制:ORDER BY CASE WHEN @sort_dir = 0 THEN name ELSE NULL END ASC, CASE WHEN @sort_dir = 1 THEN name ELSE NULL END DESC,或者更简洁地用正负号:ORDER BY CASE @sort_dir WHEN 0 THEN 1 ELSE -1 END * CAST(name AS INT)(需确保类型可转)
视图不能传参,想“带参视图”就用内联表值函数
试图在视图定义里写WHERE status = @p_status必然报错Must declare the scalar variable "@p_status"。视图是编译时静态结构,不支持运行时填参。
- 替代方案是内联表值函数(ITVF):
CREATE FUNCTION dbo.fn_orders(@status VARCHAR(20)) RETURNS TABLE AS RETURN (SELECT * FROM orders WHERE status = @status) - 它支持参数、可JOIN、WHERE能下推,执行计划干净,性能接近视图
- 千万别用多语句表值函数(MTVF):它返回“黑盒”,JOIN时容易触发嵌套循环,大数据量下明显变慢
- 函数体只能是单个
SELECT,不能有BEGIN...END、变量赋值,也不能调用GETDATE()这类非确定性函数
真正难的不是怎么写,而是判断该用OR结构还是该切到动态SQL——前者简单但可能索引失效,后者灵活但容易踩坑。参数基数、字段选择性、查询频次,这些才是决策依据,不是语法本身。











