不能直接拼接where条件,因sql server编译时将变量视为字面量而非逻辑;须用sp_executesql动态执行并参数化,严禁字符串拼接用户输入。

为什么不能直接在存储过程中拼接 WHERE 条件用普通 SQL?
因为 SQL Server(及其他主流数据库)在编译存储过程时会预解析整个语句结构,WHERE 后面如果写成 @where_clause 这种变量,会被当作字面量处理,而不是可执行逻辑。你看到的错误通常是 Must declare the scalar variable "@xxx" 或 Incorrect syntax near '@' —— 这不是语法错,是作用域和解析阶段不匹配。
真正能绕过这个限制的方式只有一种:把整条查询构建成字符串,再用 sp_executesql 执行。注意,不是 EXEC(@sql),后者无法参数化,极易引发注入且无法复用执行计划。
-
sp_executesql支持传入参数定义(N'@p1 int, @p2 nvarchar(50)')和参数值(@p1, @p2),让动态部分和安全参数分离 - 所有用户输入必须走参数化,绝不能拼进 SQL 字符串里(比如
+' AND name = ''' + @name + ''''是高危写法) - 嵌套查询本身可以是子查询、CTE 或派生表,只要最终拼出来的字符串是合法 SQL 即可
怎么安全拼接带条件的嵌套查询?
典型场景:外层按时间范围查订单,内层按客户标签聚合统计,但“客户标签”列表由应用传入,长度不定。这时不能硬写 IN ('A','B','C'),得动态生成。
关键点在于:把“变化的部分”提取为参数,把“结构固定的部分”留在模板字符串中。
DECLARE @sql NVARCHAR(MAX) = N'
SELECT o.order_id, o.amount,
(SELECT COUNT(*) FROM customer_tags ct
WHERE ct.customer_id = o.customer_id
AND ct.tag IN (SELECT value FROM STRING_SPLIT(@tags, '','')))
FROM orders o
WHERE o.created_at >= @start_time AND o.created_at
<p>注意:<code>STRING_SPLIT</code> 在 SQL Server 2016+ 可用;若版本更低,需改用自定义拆分函数或临时表。</p>
-
@tags是纯参数,不参与字符串拼接,避免注入 - 嵌套的
SELECT ... FROM STRING_SPLIT(...)是静态写死在字符串里的,SQL 引擎能识别其结构 - 外层
WHERE条件也全部参数化,不拼接日期字符串
CTE 能放进动态 SQL 吗?要注意什么?
能,但 CTE 必须紧跟在 WITH 后且作为第一个语句,不能被注释、空行或变量声明隔开。常见翻车点是:在动态 SQL 字符串里漏了换行或多了空格,导致 WITH cte AS (...) SELECT ... 被解析成两段。
正确写法要确保 CTE 定义和后续查询之间无干扰:
SET @sql = N' WITH filtered_orders AS ( SELECT order_id, customer_id, amount FROM orders WHERE status = @status ), tagged_customers AS ( SELECT customer_id FROM customer_tags WHERE tag IN (SELECT value FROM STRING_SPLIT(@tags, '','')) ) SELECT fo.order_id, fo.amount FROM filtered_orders fo INNER JOIN tagged_customers tc ON fo.customer_id = tc.customer_id';
- 所有 CTE 名、列名、关键字都写在字符串里,不能用变量替换(如
WITH @cte_name AS...会报错) - CTE 中仍可使用参数(
@status,@tags),只要它们在sp_executesql的参数列表里声明过 - 如果 CTE 需要引用临时表,注意作用域:#temp 表在
sp_executesql内不可见,得改用全局临时表##temp或表变量
动态 SQL 嵌套太深时性能掉得厉害,怎么办?
不是嵌套逻辑本身慢,而是每次执行都触发重新编译 + 参数嗅探失效。尤其当 @tags 长度差异极大(有时 2 个值,有时 200 个),SQL Server 可能为“2 值”场景生成的执行计划,被复用到“200 值”上,导致索引扫描变全表扫描。
- 加
OPTION (RECOMPILE)强制每次重编译(适合低频调用或参数组合极少的场景) - 对高频变动字段(如标签列表),考虑预计算中间结果到物理表或索引视图,把动态过滤转为静态关联
- 避免在动态 SQL 里反复调用标量函数(如
fn_parse_tag),改用内联表值函数或STRING_SPLIT这类可优化的形式 - 用
sys.dm_exec_query_stats查看该动态语句的execution_count和avg_logical_reads,确认是否真因计划缓存导致
最常被忽略的是:以为“用了参数就一定安全高效”,其实参数类型不匹配(比如用 nvarchar(max) 传短字符串)、未加 OPTION (RECOMPILE) 且数据分布倾斜,都会让动态 SQL 比静态版还慢一截。










