动态SQL的核心是使用EXEC或sp_executesql执行字符串形式的SQL语句。基本语法:EXEC('SELECT * FROM ' + @TableName); 对于参数化查询,推荐sp_executesql 'SELECT * FROM Users WHERE Id = @Id', N'@Id int', @Id=1; 这能防止SQL注入并提升性能。
实战策略一:构建动态表名查询
DECLARE @TableName NVARCHAR(128) = 'Orders'; DECLARE @SQL NVARCHAR(MAX) = 'SELECT COUNT(*) FROM ' + QUOTENAME(@TableName); EXEC sp_executesql @SQL; 使用QUOTENAME防止注入,确保表名安全。
实战策略二:动态条件过滤
DECLARE @WhereClause NVARCHAR(500) = ''; IF @Status IS NOT NULL SET @WhereClause = 'WHERE Status = @Status'; SET @SQL = 'SELECT * FROM Orders ' + @WhereClause; EXEC sp_executesql @SQL, N'@Status INT', @Status;
动态SQL性能优化
sp_executesql比EXEC快,因为它能缓存执行计划。总是使用参数化:避免硬编码值。示例:EXEC sp_executesql N'SELECT * FROM Products WHERE CategoryId = @CatId AND Price > @Price', N'@CatId INT, @Price DECIMAL(10,2)', @CatId=1, @Price=10.00;
防范SQL注入的最佳实践
永远不要直接拼接用户输入:BAD: 'SELECT * FROM Users WHERE Name=''' + @UserInput + ''''; GOOD: 使用sp_executesql参数。验证输入长度和类型,使用QUOTENAME/QUOTESTRING函数。
高级应用:动态PIVOT
DECLARE @Columns NVARCHAR(MAX) = ''; SELECT @Columns = STRING_AGG(QUOTENAME(Col), ',') FROM (VALUES('Sales'),('Profit')) t(Col); SET @SQL = 'SELECT * FROM (SELECT * FROM Data) p PIVOT(SUM(Value) FOR Metric IN (' + @Columns + ')) AS pvt'; EXEC sp_executesql @SQL;
错误处理与调试
使用TRY...CATCH包围动态SQL:BEGIN TRY EXEC sp_executesql @SQL; END TRY BEGIN CATCH PRINT ERROR_MESSAGE(); END CATCH; 调试时PRINT @SQL查看生成的语句。
何时避免动态SQL
静态SQL更高效,除非必要(如动态列、表)。JSON函数或表值参数常可替代动态SQL。优先考虑视图、存储过程参数化。
Q: 动态SQL和静态SQL的性能差异?
A: sp_executesql参数化能重用计划,性能接近静态;纯EXEC拼接每次新计划,性能差。
Q: 如何完全避免SQL注入?
A: 全部用sp_executesql参数,不拼接用户输入到SQL,用QUOTENAME处理标识符。
Q: 动态SQL支持事务吗?
A: 支持,但嵌套动态SQL事务需小心,优先在外部事务中执行。
Q: sp_executesql vs EXEC?
A: sp_executesql支持参数,计划缓存更好;EXEC简单但无参数安全。