SQL Server中sp_executesql的讲解以及execute sp_executesql与execute()的区别

sp_executesql 是 SQL Server 中一个非常重要的系统存储过程,主要用于执行动态构建的 SQL 语句。以下是它的主要作用和优势:

主要作用

1. 执行动态 SQL

sql

DECLARE @SQL NVARCHAR(MAX)
DECLARE @TableName NVARCHAR(50) = 'Employees'
DECLARE @Department NVARCHAR(50) = 'IT'

SET @SQL = N'SELECT * FROM ' + @TableName + ' WHERE Department = @Dept'

EXEC sp_executesql @SQL, N'@Dept NVARCHAR(50)', @Dept = @Department

2. 参数化查询

sql

DECLARE @SQL NVARCHAR(MAX)
DECLARE @City NVARCHAR(50) = 'London'

SET @SQL = N'SELECT * FROM Customers WHERE City = @CityParam'

-- 使用参数化查询,避免 SQL 注入
EXEC sp_executesql @SQL, N'@CityParam NVARCHAR(50)', @CityParam = @City

主要优势

1. 防止 SQL 注入

sql

-- 不安全的写法(容易受 SQL 注入攻击)
DECLARE @UserInput NVARCHAR(50) = 'London; DROP TABLE Customers--'
DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Customers WHERE City = ''' + @UserInput + ''''

-- 安全的写法(使用参数化)
DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Customers WHERE City = @City'
EXEC sp_executesql @SQL, N'@City NVARCHAR(50)', @City = @UserInput

2. 执行计划重用

sql

-- 多次执行相同结构的查询,可以重用执行计划
DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Products WHERE CategoryID = @CatID'

-- 第一次执行
EXEC sp_executesql @SQL, N'@CatID INT', @CatID = 1

-- 第二次执行 - 可以重用执行计划
EXEC sp_executesql @SQL, N'@CatID INT', @CatID = 2

3. 输出参数支持

sql

DECLARE @SQL NVARCHAR(MAX)
DECLARE @Count INT

SET @SQL = N'SELECT @CountOut = COUNT(*) FROM Employees WHERE Salary > @MinSalary'

EXEC sp_executesql 
    @SQL, 
    N'@MinSalary DECIMAL(10,2), @CountOut INT OUTPUT',
    @MinSalary = 50000,
    @CountOut = @Count OUTPUT

SELECT @Count AS 'EmployeeCount'

4. 返回结果集

sql

DECLARE @SQL NVARCHAR(MAX)
DECLARE @MinAge INT = 25

SET @SQL = N'SELECT Name, Age FROM Users WHERE Age > @Age'

-- 直接返回结果集
EXEC sp_executesql @SQL, N'@Age INT', @Age = @MinAge

语法结构

sql

sp_executesql 
    @stmt = N'SQL语句',           -- 要执行的 SQL
    @params = N'参数定义',         -- 参数定义(可选)
    @param1 = '值1',              -- 参数值
    @param2 = '值2',              -- 参数值
    ...                           -- 更多参数

实际应用场景

1. 动态表名查询

sql

CREATE PROCEDURE GetTableData
    @TableName NVARCHAR(100),
    @WhereClause NVARCHAR(1000) = NULL
AS
BEGIN
    DECLARE @SQL NVARCHAR(MAX)
    
    SET @SQL = 'SELECT * FROM ' + QUOTENAME(@TableName)
    
    IF @WhereClause IS NOT NULL
        SET @SQL = @SQL + ' WHERE ' + @WhereClause
        
    EXEC sp_executesql @SQL
END

2. 动态排序

sql

DECLARE @SQL NVARCHAR(MAX)
DECLARE @SortColumn NVARCHAR(50) = 'LastName'
DECLARE @SortOrder NVARCHAR(4) = 'ASC'

SET @SQL = N'SELECT * FROM Employees ORDER BY ' + 
           QUOTENAME(@SortColumn) + ' ' + @SortOrder

EXEC sp_executesql @SQL

注意事项

  1. 使用 NVARCHAR:SQL 语句必须是 NVARCHAR 类型

  2. 参数类型匹配:确保参数定义和实际值类型一致

  3. QUOTENAME 函数:动态对象名使用 QUOTENAME() 防止注入

  4. 性能考虑:复杂的动态 SQL 可能影响性能

sp_executesql 是构建安全、高效动态 SQL 的重要工具,特别适用于需要参数化和执行计划重用的场景。


请详细讲解这行代码:

exec sp_executesql @FinalSelectSql, N'@ResultSelectDynamicSql nvarchar(max) output', @ResultSelectDynamicSqlOutput output

这行代码是使用 sp_executesql 执行动态 SQL 并获取输出参数的典型用法。让我详细分解每个部分:

代码结构分解

sql

exec sp_executesql 
    @FinalSelectSql,                           -- 第1部分:要执行的动态SQL
    N'@ResultSelectDynamicSql nvarchar(max) output',  -- 第2部分:参数定义
    @ResultSelectDynamicSqlOutput output       -- 第3部分:参数赋值和输出

1. 第1部分:动态 SQL 语句

sql

@FinalSelectSql
  • 这是一个包含实际要执行的 SQL 代码的变量

  • 例子:

sql

-- @FinalSelectSql 可能包含这样的内容:
SET @FinalSelectSql = '
    SELECT @ResultSelectDynamicSql = 
        ''SELECT [ID], [Name]'' + 
        (SELECT '',['' + ColumnName + '']'' FROM DynamicColumns FOR XML PATH(''''))
'

2. 第2部分:参数定义

sql

N'@ResultSelectDynamicSql nvarchar(max) output'
  • N'...':表示 Unicode 字符串

  • @ResultSelectDynamicSql:在动态 SQL 内部使用的参数名

  • nvarchar(max):参数的数据类型

  • output:标记这个参数是输出参数(可以从动态 SQL 返回值)

3. 第3部分:参数赋值和输出

sql

@ResultSelectDynamicSqlOutput output
  • @ResultSelectDynamicSqlOutput:外部变量,用于接收返回值

  • output:表示这个外部变量要接收动态 SQL 的输出值

完整执行流程示例

步骤1:准备数据

sql

-- 假设我们有一个动态列的表
CREATE TABLE DynamicColumns (ColumnName NVARCHAR(50))
INSERT INTO DynamicColumns VALUES ('Age'), ('Salary'), ('Department')

步骤2:构建动态 SQL

sql

DECLARE @FinalSelectSql NVARCHAR(MAX) = '
    -- 在动态SQL内部构建复杂的SELECT语句
    DECLARE @ColumnList NVARCHAR(MAX) = ''''
    SELECT @ColumnList = @ColumnList + '', ['' + ColumnName + '']''
    FROM DynamicColumns
    
    -- 将结果赋值给输出参数
    SET @ResultSelectDynamicSql = ''SELECT [ID], [Name]'' + @ColumnList
'

DECLARE @ResultSelectDynamicSqlOutput NVARCHAR(MAX)

步骤3:执行并获取结果

sql

exec sp_executesql 
    @FinalSelectSql, 
    N'@ResultSelectDynamicSql nvarchar(max) output', 
    @ResultSelectDynamicSqlOutput output

-- 查看结果
SELECT @ResultSelectDynamicSqlOutput AS '生成的SQL'

执行结果:

text

生成的SQL
-------------------------------------------
SELECT [ID], [Name], [Age], [Salary], [Department]

参数传递机制详解

内存中的变化:

text

执行前:
@ResultSelectDynamicSqlOutput = NULL

执行中(在动态SQL内部):
@ResultSelectDynamicSql = 'SELECT [ID], [Name], [Age], [Salary], [Department]'

执行后:
@ResultSelectDynamicSqlOutput = 'SELECT [ID], [Name], [Age], [Salary], [Department]'

实际应用场景

场景1:动态报表列生成

sql

-- 根据用户选择的列动态生成SQL
DECLARE @DynamicSQL NVARCHAR(MAX) = '
    SET @ResultSelectDynamicSql = ''SELECT ''
    IF @IncludeName = 1
        SET @ResultSelectDynamicSql = @ResultSelectDynamicSql + ''[Name], ''
    IF @IncludeSalary = 1  
        SET @ResultSelectDynamicSql = @ResultSelectDynamicSql + ''[Salary], ''
    -- 去除最后的逗号
    SET @ResultSelectDynamicSql = LEFT(@ResultSelectDynamicSql, LEN(@ResultSelectDynamicSql) - 1) + '' FROM Employees''
'

DECLARE @IncludeName BIT = 1, @IncludeSalary BIT = 1
DECLARE @GeneratedSQL NVARCHAR(MAX)

EXEC sp_executesql 
    @DynamicSQL, 
    N'@ResultSelectDynamicSql nvarchar(max) output, @IncludeName bit, @IncludeSalary bit',
    @ResultSelectDynamicSql = @GeneratedSQL output,
    @IncludeName = @IncludeName,
    @IncludeSalary = @IncludeSalary

SELECT @GeneratedSQL  -- 结果: 'SELECT [Name], [Salary] FROM Employees'

场景2:复杂条件构建

sql

DECLARE @ConditionBuilder NVARCHAR(MAX) = '
    DECLARE @WhereClause NVARCHAR(MAX) = ''''
    IF @Department IS NOT NULL
        SET @WhereClause = @WhereClause + '' AND Department = '''''' + @Department + '''''' ''
    IF @MinSalary IS NOT NULL
        SET @WhereClause = @WhereClause + '' AND Salary >= '' + CAST(@MinSalary AS NVARCHAR)
    
    SET @ResultSelectDynamicSql = ''SELECT * FROM Employees WHERE 1=1 '' + @WhereClause
'

DECLARE @Department NVARCHAR(50) = 'IT', @MinSalary DECIMAL = 50000
DECLARE @FinalSQL NVARCHAR(MAX)

EXEC sp_executesql 
    @ConditionBuilder,
    N'@ResultSelectDynamicSql nvarchar(max) output, @Department nvarchar(50), @MinSalary decimal',
    @ResultSelectDynamicSql = @FinalSQL output,
    @Department = @Department,
    @MinSalary = @MinSalary

SELECT @FinalSQL
-- 结果: 'SELECT * FROM Employees WHERE 1=1  AND Department = 'IT' AND Salary >= 50000'

关键要点总结

  1. 参数名对应:第二个参数中的 @ResultSelectDynamicSql 与动态 SQL 内部的参数名必须一致

  2. 数据类型匹配:定义的数据类型必须与实际使用的类型匹配

  3. OUTPUT 关键字:在定义和调用时都需要 output 关键字

  4. 作用域分离:动态 SQL 内部的变量与外部变量是独立的,通过参数传递连接

  5. Unicode 字符串:参数定义必须使用 N'...' 格式

这种机制非常适合需要在动态 SQL 中构建复杂字符串并返回结果的场景!


详细讲解以下代码的意思,以及运行机制:

declare @ResultSelectSql = 'select  [自编号], [样品名称], [样品来源]'
declare @ItemNameSQL = '
declare @item_names table (rvt_ItemName nvarchar(max))
insert into @item_names
select rvt_ItemName from ##Result_B52B7261BFCD4AB991B8C6145566F3CF group by SortNo,rvt_ItemName order by SortNo,rvt_ItemName

declare @item_sql nvarchar(max) = ''''
select @item_sql = @item_sql + '',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else ''''----''''  end) ''
from @item_names

set @ResultSelectSql = @ResultSelectSql + @item_sql
'
declare @FullSQL nvarchar(max) = '
set @ResultSelectSql = ''' 
+ @ResultSelectSql + '''' 
+ @ItemNameSQL

exec sp_executesql @FullSQL, N'@ResultSelectSql nvarchar(max) output', @ResultSelectSql output

这是一个动态构建列的复杂SQL代码。

代码整体目标

这段代码的目的是动态生成一个包含固定列和动态列的SELECT语句

第一步:初始化基础SQL

sql

declare @ResultSelectSql nvarchar(max) = 'select  [自编号], [样品名称], [样品来源]'
  • 创建一个变量 @ResultSelectSql

  • 初始值为固定的SELECT语句开头部分

  • 包含3个固定列:[自编号][样品名称][样品来源]

第二步:定义动态列构建逻辑

sql

declare @ItemNameSQL nvarchar(max) = '
declare @item_names table (rvt_ItemName nvarchar(max))
insert into @item_names
select rvt_ItemName from ##Result_B52B7261BFCD4AB991B8C6145566F3CF group by SortNo,rvt_ItemName order by SortNo,rvt_ItemName

declare @item_sql nvarchar(max) = ''''
select @item_sql = @item_sql + '',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else ''''----''''  end) ''
from @item_names

set @ResultSelectSql = @ResultSelectSql + @item_sql
'

详细分解这部分:

2.1 创建临时表并插入数据

sql

declare @item_names table (rvt_ItemName nvarchar(max))
insert into @item_names
select rvt_ItemName from ##Result_B52B7261BFCD4AB991B8C6145566F3CF 
group by SortNo, rvt_ItemName 
order by SortNo, rvt_ItemName
  • 创建一个表变量 @item_names,用于存储唯一的项目名称

  • 从全局临时表 ##Result_B52B7261BFCD4AB991B8C6145566F3CF 中获取不重复的 rvt_ItemName

  • 按 SortNo 和 rvt_ItemName 排序

2.2 构建动态列字符串

sql

declare @item_sql nvarchar(max) = ''''
select @item_sql = @item_sql + 
'',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else ''''----''''  end) ''
from @item_names

这是最复杂的部分,让我用实际例子说明:

假设表中有3个项目名称:

  • 项目A

  • 项目B

  • 项目C

生成的SQL会是:

sql

,[项目A] = max(case rvt_ItemName when '项目A' then rvt_Result else '----' end)
,[项目B] = max(case rvt_ItemName when '项目B' then rvt_Result else '----' end)  
,[项目C] = max(case rvt_ItemName when '项目C' then rvt_Result else '----' end)
2.3 拼接最终SQL

sql

set @ResultSelectSql = @ResultSelectSql + @item_sql

把动态生成的列追加到基础SELECT语句后面。

第三步:构建完整执行语句

sql

declare @FullSQL nvarchar(max) = '
set @ResultSelectSql = ''' 
+ @ResultSelectSql + '''' 
+ @ItemNameSQL

这相当于构建了这样的代码:

sql

set @ResultSelectSql = 'select  [自编号], [样品名称], [样品来源]'
-- 然后执行 @ItemNameSQL 中的动态列生成逻辑

第四步:执行动态SQL

sql

exec sp_executesql @FullSQL, N'@ResultSelectSql nvarchar(max) output', @ResultSelectSql output

完整执行流程示例

执行前:

sql

@ResultSelectSql = 'select  [自编号], [样品名称], [样品来源]'

执行后(假设有3个动态项目):

sql

@ResultSelectSql = 'select  [自编号], [样品名称], [样品来源]
,[项目A] = max(case rvt_ItemName when '项目A' then rvt_Result else '----' end)
,[项目B] = max(case rvt_ItemName when '项目B' then rvt_Result else '----' end)  
,[项目C] = max(case rvt_ItemName when '项目C' then rvt_Result else '----' end)'

实际应用场景

这种代码通常用于动态报表数据透视,比如:

原始数据格式:

text

样品名称    | 项目名称    | 检测结果
样品1      | 项目A      | 合格
样品1      | 项目B      | 不合格
样品1      | 项目C      | 合格
样品2      | 项目A      | 合格

转换为:

text

样品名称    | 项目A    | 项目B    | 项目C
样品1      | 合格    | 不合格  | 合格
样品2      | 合格    | ----    | ----

关键点总结

  1. 动态列生成:根据数据内容自动创建列

  2. 行转列:使用 CASE WHEN 实现数据透视

  3. 字符串拼接:通过循环拼接构建复杂SQL

  4. 执行计划:使用 sp_executesql 安全执行动态SQL

这种技术在构建灵活的数据报表系统时非常有用!


代码中,多处出现的@ResultSelectSql,感觉好混乱,请再详细帮忙梳理

这段代码中多处出现的 @ResultSelectSql 确实容易让人混淆。让我重新梳理这些变量的作用域和生命周期

变量作用域分析

实际上这里存在两个不同作用域的 @ResultSelectSql 变量:

1. 外层变量(主作用域)

sql

-- 这是在外层声明的变量
declare @ResultSelectSql nvarchar(max) = 'select  [自编号], [样品名称], [样品来源]'

2. 内层变量(动态SQL作用域)

sql

-- 这是在动态SQL内部使用的同名变量
-- 通过 sp_executesql 的参数传递

执行过程逐步分析

让我用颜色标记来区分:

第一步:初始化

sql

-- 🟦外层变量:初始值
declare @ResultSelectSql nvarchar(max) = 'select  [自编号], [样品名称], [样品来源]'

第二步:构建动态SQL逻辑

sql

declare @ItemNameSQL nvarchar(max) = '
declare @item_names table (rvt_ItemName nvarchar(max))
insert into @item_names
select rvt_ItemName from ##Result_B52B7261BFCD4AB991B8C6145566F3CF group by SortNo,rvt_ItemName order by SortNo,rvt_ItemName

declare @item_sql nvarchar(max) = ''''
select @item_sql = @item_sql + '',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else ''''----''''  end) ''
from @item_names

-- 🟩内层变量:在动态SQL内部使用
set @ResultSelectSql = @ResultSelectSql + @item_sql
'

第三步:拼接完整执行语句

sql

declare @FullSQL nvarchar(max) = '
-- 🟩将🟦外层变量的值赋给🟩内层变量
set @ResultSelectSql = ''' + @ResultSelectSql + '''' + @ItemNameSQL

第四步:执行并传回结果

sql

-- 🟦外层变量 ← 🟩内层变量(通过output参数)
exec sp_executesql @FullSQL, N'@ResultSelectSql nvarchar(max) output', @ResultSelectSql output

实际内存变化过程

执行前:

text

🟦外层 @ResultSelectSql = 'select  [自编号], [样品名称], [样品来源]'

执行中(在动态SQL内部):

text

🟩内层 @ResultSelectSql = 'select  [自编号], [样品名称], [样品来源]'
↓ (经过动态列拼接)
🟩内层 @ResultSelectSql = 'select  [自编号], [样品名称], [样品来源], [项目A] = max(...), [项目B] = max(...)'

执行后:

text

🟦外层 @ResultSelectSql = 'select  [自编号], [样品名称], [样品来源], [项目A] = max(...), [项目B] = max(...)'

更清晰的改写版本

为了避免混淆,可以这样改写:

sql

-- 外层变量改名为更清晰的名字
declare @BaseSelectSql nvarchar(max) = 'select  [自编号], [样品名称], [样品来源]'
declare @FinalSelectSql nvarchar(max) = ''

declare @ItemNameSQL nvarchar(max) = '
declare @item_names table (rvt_ItemName nvarchar(max))
insert into @item_names
select rvt_ItemName from ##Result_B52B7261BFCD4AB991B8C6145566F3CF group by SortNo,rvt_ItemName order by SortNo,rvt_ItemName

declare @item_sql nvarchar(max) = ''''
select @item_sql = @item_sql + '',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else ''''----''''  end) ''
from @item_names

-- 使用不同的变量名避免混淆
set @DynamicSelectSql = @BaseSelectSql + @item_sql
'

declare @FullSQL nvarchar(max) = '
set @DynamicSelectSql = ''' + @BaseSelectSql + '''' + @ItemNameSQL

-- 参数名也对应修改
exec sp_executesql @FullSQL, N'@DynamicSelectSql nvarchar(max) output', @FinalSelectSql output

关键理解点

  1. 同名但不同作用域:外层和内层的 @ResultSelectSql 是两个不同的变量

  2. 值传递:通过 sp_executesql 的参数机制,外层变量的值传递给内层变量

  3. 结果回传:内层处理完后,通过 OUTPUT 参数将结果传回外层变量

  4. 生命周期:内层变量在动态SQL执行结束后就销毁了

简单类比

可以把这个过程想象成:

  • 你(外层)写了一张纸条给助手(内层)

  • 助手在纸条上添加内容

  • 助手把修改后的纸条还给你

虽然都叫"纸条",但经过助手处理后,内容已经发生了变化。

这样理解应该就不混乱了!


sp_executesql 和 EXEC() 都是 SQL Server 中执行动态 SQL 的方法,但它们有重要的区别。让我详细对比:

1. 基本语法对比

EXEC() (或 EXECUTE())

sql

DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Employees WHERE Department = ''IT'''
EXEC(@SQL)

sp_executesql

sql

DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Employees WHERE Department = @Dept'
EXEC sp_executesql @SQL, N'@Dept NVARCHAR(50)', @Dept = 'IT'

2. 主要区别详解

2.1 参数化支持(最重要的区别)

sp_executesql - 支持参数化

sql

DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Employees WHERE Salary > @MinSalary AND Department = @Dept'
EXEC sp_executesql @SQL, 
    N'@MinSalary DECIMAL(10,2), @Dept NVARCHAR(50)', 
    @MinSalary = 50000, 
    @Dept = 'IT'

EXEC() - 不支持参数化,必须拼接字符串

sql

DECLARE @MinSalary DECIMAL(10,2) = 50000
DECLARE @Dept NVARCHAR(50) = 'IT'
DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Employees WHERE Salary > ' + CAST(@MinSalary AS NVARCHAR) + ' AND Department = ''' + @Dept + ''''
EXEC(@SQL)

2.2 SQL 注入安全性

sp_executesql - 安全

sql

-- 即使用户输入恶意代码,也不会执行
DECLARE @UserInput NVARCHAR(100) = 'IT''; DROP TABLE Employees--'
DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Employees WHERE Department = @Dept'
EXEC sp_executesql @SQL, N'@Dept NVARCHAR(100)', @Dept = @UserInput
-- 安全:只会查询,不会删除表

EXEC() - 不安全

sql

DECLARE @UserInput NVARCHAR(100) = 'IT''; DROP TABLE Employees--'
DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Employees WHERE Department = ''' + @UserInput + ''''
EXEC(@SQL)
-- 危险:会执行 DROP TABLE 语句!

2.3 执行计划重用

sp_executesql - 可以重用执行计划

sql

DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Products WHERE CategoryID = @CatID'

-- 第一次执行
EXEC sp_executesql @SQL, N'@CatID INT', @CatID = 1
-- 生成执行计划

-- 第二次执行 - 重用相同的执行计划
EXEC sp_executesql @SQL, N'@CatID INT', @CatID = 2
-- 性能更好

EXEC() - 每次都是新的执行计划

sql

-- 第一次执行
DECLARE @SQL1 NVARCHAR(MAX) = 'SELECT * FROM Products WHERE CategoryID = 1'
EXEC(@SQL1)
-- 生成执行计划1

-- 第二次执行
DECLARE @SQL2 NVARCHAR(MAX) = 'SELECT * FROM Products WHERE CategoryID = 2'
EXEC(@SQL2)
-- 生成执行计划2(不能重用)

2.4 输出参数支持

sp_executesql - 支持输出参数

sql

DECLARE @SQL NVARCHAR(MAX) = 'SELECT @Count = COUNT(*) FROM Employees'
DECLARE @EmployeeCount INT

EXEC sp_executesql @SQL, N'@Count INT OUTPUT', @Count = @EmployeeCount OUTPUT

SELECT @EmployeeCount AS '员工总数'

EXEC() - 不支持输出参数

sql

-- 无法直接获取返回值,需要借助临时表
CREATE TABLE #Temp (Count INT)
DECLARE @SQL NVARCHAR(MAX) = 'INSERT INTO #Temp SELECT COUNT(*) FROM Employees'
EXEC(@SQL)

SELECT * FROM #Temp
DROP TABLE #Temp

2.5 性能对比

场景sp_executesqlEXEC()
多次执行相同模式✅ 执行计划重用,性能好❌ 每次重新编译,性能差
处理用户输入✅ 参数化,安全高效❌ 字符串拼接,风险高
复杂动态SQL✅ 结构清晰,易于维护❌ 拼接复杂,难以调试

3. 实际应用建议

使用 sp_executesql 的场景:

sql

-- 1. 需要参数化的查询
DECLARE @SQL NVARCHAR(MAX) = 'SELECT * FROM Orders WHERE OrderDate BETWEEN @Start AND @End'
EXEC sp_executesql @SQL, N'@Start DATE, @End DATE', @Start = '2023-01-01', @End = '2023-12-31'

-- 2. 需要输出参数的场景
DECLARE @SQL NVARCHAR(MAX) = 'SELECT @MaxSalary = MAX(Salary) FROM Employees'
DECLARE @MaxSalary DECIMAL(10,2)
EXEC sp_executesql @SQL, N'@MaxSalary DECIMAL(10,2) OUTPUT', @MaxSalary OUTPUT

-- 3. 频繁执行的动态SQL
DECLARE @SQL NVARCHAR(MAX) = 'UPDATE Products SET Price = @Price WHERE ProductID = @ID'
EXEC sp_executesql @SQL, N'@Price DECIMAL(10,2), @ID INT', @Price = 19.99, @ID = 1
EXEC sp_executesql @SQL, N'@Price DECIMAL(10,2), @ID INT', @Price = 29.99, @ID = 2

使用 EXEC() 的场景:

sql

-- 1. 简单的动态表名/列名(使用 QUOTENAME 防止注入)
DECLARE @TableName NVARCHAR(100) = 'Employees'
DECLARE @SQL NVARCHAR(MAX) = 'SELECT TOP 10 * FROM ' + QUOTENAME(@TableName)
EXEC(@SQL)

-- 2. 执行存储的动态SQL(从表或变量中读取)
DECLARE @SQLCommand NVARCHAR(MAX)
SELECT @SQLCommand = SQLText FROM SavedQueries WHERE QueryID = 1
EXEC(@SQLCommand)

-- 3. 简单的一次性动态SQL
DECLARE @SQL NVARCHAR(MAX) = 'BACKUP DATABASE MyDB TO DISK = ''D:\backup.bak'''
EXEC(@SQL)

4. 总结

特性sp_executesqlEXEC()
参数化支持✅ 支持❌ 不支持
SQL注入防护✅ 安全❌ 危险
执行计划重用✅ 可以重用❌ 不能重用
输出参数✅ 支持❌ 不支持
性能✅ 更好❌ 较差
代码可读性✅ 更清晰❌ 拼接混乱
使用场景复杂、频繁、安全的动态SQL简单、一次性、无用户输入的动态SQL

推荐优先使用 sp_executesql,除非是简单的、不需要参数化的场景。


实例比对

使用 exec() 执行动态Sql

      -- ===========================================================================
      -- 并发优化【关键修改】:构建【最终查询:返回数据】Sql
      -- ===========================================================================
      declare @ResultSelectSql nvarchar(1000) 
      set @ResultSelectSql = '
      select  [自编号], [样品名称], [样品来源]
      '
      declare @ItemNameRowToColDynamicSql nvarchar(1000) 
      set @ItemNameRowToColDynamicSql = '
      declare @ItemNames table (rvt_ItemName nvarchar(200))
      insert into @ItemNames
      select rvt_ItemName from ##Result_7BEE9C3BE81946F7B3EF7E0F70F5686B group by SortNo,rvt_ItemName order by SortNo,rvt_ItemName

      declare @ItemNameSql nvarchar(max) = ''
      select @ItemNameSql = @ItemNameSql + '',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else ''''----''''  end) ''
      from @ItemNames
      '
      -- ===========================================================================
      -- 方案一:使用 exec() 执行动态Sql,通过临时表获取返回数据
      -- 1、创建临时表
      -- 2、拼接动态Sql逻辑:构建【最终查询:返回数据】Sql
      -- 3、使用 exec() 执行动态Sql
      -- 4、通过临时表获取【最终查询:返回数据】Sql
      -- 5、删除临时表
      -- ===========================================================================
      
      -- 1、创建临时表
      create table #ResultSelectSqlTempTable (sql_command nvarchar(max))

      -- 2、拼接动态Sql逻辑:构建【最终查询:返回数据】Sql
      -- 2.1、declare @ResultSelectDynamicSql nvarchar(max)								=> 定义变量 @ResultSelectDynamicSql
      -- 2.2、set @ResultSelectDynamicSql = ''' + @ResultSelectSql + ''''				=> 给变量赋值,初始值
      -- 2.3、+ @ItemNameRowToColDynamicSql + '											=> 执行这段动态sql,行转列,获取项目名称
      -- 2.4、set @ResultSelectDynamicSql = @ResultSelectDynamicSql + @ItemNameSql		=> 给变量赋值,最终内容
      -- 2.5、insert into #ResultSelectSqlTempTable values (@ResultSelectDynamicSql)	=> 将最终内容插入临时表
      declare @FinalSelectSql nvarchar(max) = '
      declare @ResultSelectDynamicSql nvarchar(max)
      set @ResultSelectDynamicSql = ''' + @ResultSelectSql + ''''
      + @ItemNameRowToColDynamicSql + '
      set @ResultSelectDynamicSql = @ResultSelectDynamicSql + @ItemNameSql
      insert into #ResultSelectSqlTempTable values (@ResultSelectDynamicSql)
      '

      -- 3、使用 exec() 执行动态Sql
      exec(@FinalSelectSql)

      -- 4、再从临时表中查询数据,从而获取返回数据
      select @ResultSelectSql = sql_command from #ResultSelectSqlTempTable

      -- 5、删除临时表
      drop table #ResultSelectSqlTempTable

使用 exec sp_executesql 执行动态Sql

      -- ===========================================================================
      -- 并发优化【关键修改】:构建【最终查询:返回数据】Sql
      -- ===========================================================================

      declare @ResultSelectSql nvarchar(1000) 
      set @ResultSelectSql = '
      select  [自编号], [样品名称], [样品来源]
      '
      declare @ItemNameRowToColDynamicSql nvarchar(1000) 
      set @ItemNameRowToColDynamicSql = '
      declare @ItemNames table (rvt_ItemName nvarchar(200))
      insert into @ItemNames
      select rvt_ItemName from ##Result_7BEE9C3BE81946F7B3EF7E0F70F5686B group by SortNo,rvt_ItemName order by SortNo,rvt_ItemName

      declare @ItemNameSql nvarchar(max) = ''
      select @ItemNameSql = @ItemNameSql + '',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else ''''----''''  end) ''
      from @ItemNames
      '

      -- ===========================================================================
      -- 方案二:使用 exec sp_executesql 执行动态Sql,通过动态Sql的输出值获取返回数据
      -- 1、拼接动态Sql逻辑:构建【最终查询:返回数据】Sql
      -- 2、使用 exec sp_executesql 执行动态Sql
      -- 3、通过动态Sql的输出值获取【最终查询:返回数据】Sql
      -- ===========================================================================
      
      -- 1、拼接动态Sql逻辑:构建【最终查询:返回数据】Sql
      -- 1.1、set @ResultSelectDynamicSql = ''' + @ResultSelectSql + ''''				=> 给参数赋值,初始值
      -- 1.2、+ @ItemNameRowToColDynamicSql + '											=> 执行这段动态sql,行转列,获取项目名称
      -- 1.3、set @ResultSelectDynamicSql = @ResultSelectDynamicSql + @ItemNameSql		=> 给参数赋值,最终内容
      declare @FinalSelectSql nvarchar(1000) = '
      set @ResultSelectDynamicSql = ''' + @ResultSelectSql + ''''
      + @ItemNameRowToColDynamicSql + '
      set @ResultSelectDynamicSql = @ResultSelectDynamicSql + @ItemNameSql
      '

      -- 2、使用 exec sp_executesql 执行动态Sql
      -- 2.1、定义动态Sql内部参数(输出参数) @ResultSelectDynamicSql(与动态Sql内部的参数名 @ResultSelectDynamicSql 必须一致)
      -- 2.2、定义外部变量 @ResultSelectDynamicSqlOutput,用于接收返回值(动态Sql的输出值)
      declare @ResultSelectDynamicSqlOutput nvarchar(1000)
      exec sp_executesql @FinalSelectSql, N'@ResultSelectDynamicSql nvarchar(1000) output', @ResultSelectDynamicSqlOutput output

      -- 3、通过动态Sql的输出值获取【最终查询:返回数据】Sql
      set @ResultSelectSql = @ResultSelectDynamicSqlOutput

代码封装

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[proc_RowToColGenerateDynamicItemNames]') and objectproperty(id, N'IsProcedure') = 1)
drop procedure [dbo].[proc_RowToColGenerateDynamicItemNames]
go

set ansi_nulls on
go
set quoted_identifier on
go
/****************************************************************************/    
/*  Author :杨广标                                                          */    
/*  Date   :2025-11-17                                                      */    
/*  version:1.0                                                             */    
/*  description:行转列生成动态项目名称(构建动态列)                        */
/****************************************************************************/
-- ===========================================================================
-- 功能说明:
-- 1. 将表(会话表)的数据行转列,构建动态的项目名称列字段
-- 2. 项目列名与检验结果格式、项目单位显示类型有关联
-- 3. 处理空值
-- 4. 输出项目名称动态列Sql
-- 5. 应用示例
-- exec proc_RowToColGenerateDynamicItemNames '##Result_F7744127A6084D77988CBF2A36B64B75', 0, 0, '---', @FieldSql output
-- 6. 输出数据样式:
-- ,[细菌总数] = max(case rvt_ItemName when '' + rvt_ItemName + '' then rvt_Result else '---' end) 
-- ,[大肠菌群] = max(case rvt_ItemName when '' + rvt_ItemName + '' then rvt_Result else '---' end) 
-- ,[金黄色葡萄球菌] = max(case rvt_ItemName when '' + rvt_ItemName + '' then rvt_Result else '---' end)
-- ===========================================================================
create procedure [dbo].[proc_RowToColGenerateDynamicItemNames]
	@SessionResultTable varchar(100),	-- 会话表
	@TableContentType int,				-- 检验结果格式
	@VerifyItemUnitType int,			-- 项目单位显示类型
	@NullMark nvarchar(50),				-- 空值显示的内容
	@ItemNameDynamiFieldsSql nvarchar(max) output	-- 项目名称动态字段Sql
as
begin
	set nocount on

	declare @ItemNameRowToColDynamicSql nvarchar(max)	-- 项目名称行转列动态Sql
	
	set @ItemNameDynamiFieldsSql = isnull(@ItemNameDynamiFieldsSql, '')
	
	-- 不需要显示标准值的情况(不出具方法、标准值和结论、出具结论)
	if @TableContentType in(0,2)
	begin
		-- 检测项目后面接单位
		if @VerifyItemUnitType in (1,5,7,8)
		begin
			set @ItemNameRowToColDynamicSql = '
			declare @ItemNames table (rvt_ItemName nvarchar(200), rvt_Unit nvarchar(100))
			insert into @ItemNames
			select rvt_ItemName,Rvt_Unit from ' + @SessionResultTable + ' group by SortNo,rvt_ItemName,Rvt_Unit order by SortNo,rvt_ItemName,Rvt_Unit

			declare @ItemNameSql nvarchar(max) = ''''
			select @ItemNameSql = @ItemNameSql + '',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else 
			'
		end
		-- 检测项目后面不接单位
		else
		begin
			set @ItemNameRowToColDynamicSql = '
			declare @ItemNames table (rvt_ItemName nvarchar(200))
			insert into @ItemNames
			select rvt_ItemName from ' + @SessionResultTable + ' group by SortNo,rvt_ItemName order by SortNo,rvt_ItemName

			declare @ItemNameSql nvarchar(max) = ''''
			select @ItemNameSql = @ItemNameSql + '',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else 
			'
		end
	end
	-- 需要显示标准值的情况
	else
	begin
		-- 检测项目后面接单位
		if @VerifyItemUnitType in (1,5,7,8)
		begin
			set @ItemNameRowToColDynamicSql = '
            declare @ItemNames table (rvt_ItemName nvarchar(200), rvt_Unit nvarchar(100), vsiID varchar(10))
            insert into @ItemNames
            select rvt_ItemName from ' + @SessionResultTable + ' group by SortNo,rvt_ItemName,Rvt_Unit,vsiID order by SortNo,rvt_ItemName,Rvt_Unit,vsiID

            declare @ItemNameSql nvarchar(max) = ''''
            select @ItemNameSql = @ItemNameSql + '',['' + rvt_ItemName + ''] = max(case vsiID when '''''''' + vsiID + '''''''' then rvt_Result else 
            '
		end
		-- 检测项目后面不接单位
		else
		begin
			set @ItemNameRowToColDynamicSql = '
            declare @ItemNames table (rvt_ItemName nvarchar(200), vsiID varchar(10))
            insert into @ItemNames
            select rvt_ItemName from ' + @SessionResultTable + ' group by SortNo,rvt_ItemName,vsiID order by SortNo,rvt_ItemName,vsiID

            declare @ItemNameSql nvarchar(max) = ''''
            select @ItemNameSql = @ItemNameSql + '',['' + rvt_ItemName + ''] = max(case vsiID when '''''''' + vsiID + '''''''' then rvt_Result else 
            '
		end
	end
	
	-- 空值显示的内容
	if @NullMark is null
        set @ItemNameRowToColDynamicSql = @ItemNameRowToColDynamicSql + 'null '
	else
		set @ItemNameRowToColDynamicSql = @ItemNameRowToColDynamicSql + '''''' + @NullMark + ''''' '
	set @ItemNameRowToColDynamicSql = @ItemNameRowToColDynamicSql + ' end) ''
	from @ItemNames
	'
	
	-- =============================================================================
	-- 构建动态列:行转列生成动态项目名称
	-- 输出数据样式:
	--	,[细菌总数] = max(case rvt_ItemName when '' + rvt_ItemName + '' then rvt_Result else '---' end) 
	--	,[大肠菌群] = max(case rvt_ItemName when '' + rvt_ItemName + '' then rvt_Result else '---' end) 
	--	,[金黄色葡萄球菌] = max(case rvt_ItemName when '' + rvt_ItemName + '' then rvt_Result else '---' end)
	-- =============================================================================
	-- 项目名称行转列动态Sql数据样式
	/*
	set @ItemNameRowToColDynamicSql = '
	declare @ItemNames table (rvt_ItemName nvarchar(200))
	insert into @ItemNames
	select rvt_ItemName from ##Result_7BEE9C3BE81946F7B3EF7E0F70F5686B group by SortNo,rvt_ItemName order by SortNo,rvt_ItemName

	declare @ItemNameSql nvarchar(max) = ''
	select @ItemNameSql = @ItemNameSql + '',['' + rvt_ItemName + ''] = max(case rvt_ItemName when '''''''' + rvt_ItemName + '''''''' then rvt_Result else ''''----''''  end) ''
	from @ItemNames
	'
	*/
	-- =============================================================================
	-- 方案一:使用 exec() 执行动态Sql,通过临时表获取返回数据
	-- 1、创建临时表
	-- 2、拼接动态Sql逻辑:构建【构建动态列:行转列生成动态项目名称】Sql
	-- 3、使用 exec() 执行动态Sql
	-- 4、通过临时表获取【构建动态列:行转列生成动态项目名称】Sql
	-- 5、删除临时表
	-- =============================================================================

	-- 1、创建临时表
	create table #temp (field_sql nvarchar(max))
	/*
	-- 2、拼接动态Sql逻辑:构建【构建动态列:行转列生成动态项目名称】Sql
	-- 2.1、declare @ItemNameFieldsSql nvarchar(max)							=> 定义变量 @ItemNameFieldsSql
	-- 2.2、set @ItemNameFieldsSql = ''' + @ItemNameDynamiFieldsSql + ''''		=> 给变量赋值,初始值
	-- 2.3、+ @ItemNameRowToColDynamicSql + '									=> 执行这段动态sql,行转列,获取项目名称
	-- 2.4、set @ItemNameFieldsSql = @ItemNameFieldsSql + @ItemNameSql			=> 给变量赋值,最终内容
	-- 2.5、insert into #temp values (@ItemNameFieldsSql)						=> 将最终内容插入临时表
	declare @FinalExecSql nvarchar(1000) = '
	declare @ItemNameFieldsSql nvarchar(max)
	set @ItemNameFieldsSql = ''' + @ItemNameDynamiFieldsSql + ''''
	+ @ItemNameRowToColDynamicSql + '
	set @ItemNameFieldsSql = @ItemNameFieldsSql + @ItemNameSql
	insert into #temp values (@ItemNameFieldsSql)
	'

	-- 3、使用 exec() 执行动态Sql
	exec(@FinalExecSql)

	-- 4、再从临时表中查询数据,从而获取返回数据
	select @ItemNameDynamiFieldsSql = field_sql from #temp

	-- 5、删除临时表
	drop table #temp
	*/
	-- =============================================================================
	-- 方案二:使用 exec sp_executesql 执行动态Sql,通过动态Sql的输出值获取返回数据
	-- 1、拼接动态Sql逻辑:构建【构建动态列:行转列生成动态项目名称】Sql
	-- 2、使用 exec sp_executesql 执行动态Sql
	-- 3、通过动态Sql的输出值获取【构建动态列:行转列生成动态项目名称】Sql
	-- =============================================================================
	
	-- 1、拼接动态Sql逻辑:构建【构建动态列:行转列生成动态项目名称】Sql
	-- 1.1、set @ItemNameFieldsSql = ''' + @ItemNameDynamiFieldsSql + ''''		=> 给参数赋值,初始值
	-- 1.2、+ @ItemNameRowToColDynamicSql + '									=> 执行这段动态sql,行转列,获取项目名称
	-- 1.3、set @ItemNameFieldsSql = @ItemNameFieldsSql + @ItemNameSql			=> 给参数赋值,最终内容
	declare @FinalExecSql nvarchar(1000) = '
	set @ItemNameFieldsSql = ''' + @ItemNameDynamiFieldsSql + ''''
	+ @ItemNameRowToColDynamicSql + '
	set @ItemNameFieldsSql = @ItemNameFieldsSql + @ItemNameSql
	'

	-- 2、使用 exec sp_executesql 执行动态Sql
	-- 2.1、定义动态Sql内部参数(输出参数) @ItemNameFieldsSql(与动态Sql内部的参数名 @ItemNameFieldsSql 必须一致)
	-- 2.2、定义外部变量 @ResultSelectDynamicSqlOutput,用于接收返回值(动态Sql的输出值)
	declare @ItemNameDynamiFieldsSqlOutput nvarchar(1000)
	exec sp_executesql @FinalExecSql, N'@ItemNameFieldsSql nvarchar(1000) output', @ItemNameDynamiFieldsSqlOutput output

	-- 3、通过动态Sql的输出值获取【构建动态列:行转列生成动态项目名称】Sql
	set @ItemNameDynamiFieldsSql = @ItemNameDynamiFieldsSqlOutput
	
	set nocount off
end
go

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值