sp_executesql (Transact-SQL)

适用于: SQL Server Azure SQL 数据库 Azure SQL 托管实例 Azure Synapse Analytics 分析平台系统 (PDW) Microsoft Fabric 中的 SQL 分析端点 Microsoft Fabric 中的仓库

执行可以多次重复使用的 Transact-SQL 语句或批处理,或动态生成的语句。 Transact-SQL 语句或批处理可以包含嵌入参数。

注意

运行时编译的 Transact-SQL 语句可能会向恶意攻击公开应用程序。 使用 sp_executesql时应参数化查询。 有关详细信息,请参阅 SQL 注入

Transact-SQL 语法约定

语法

SQL Server、Azure SQL 数据库、Azure SQL 托管实例、Azure Synapse Analytics 和 Analytics Platform System(PDW)的语法。

sp_executesql [ @stmt = ] N'statement'
[
    [ , [ @params = ] N'@parameter_name data_type [ { OUT | OUTPUT } ] [ , ...n ]' ]
    [ , [ @param1 = ] 'value1' [ , ...n ] ]
]

本文中的 Transact-SQL 代码示例使用 AdventureWorks2022AdventureWorksDW2022 示例数据库,可从 Microsoft SQL Server 示例和社区项目主页下载它。

参数

[ @stmt = ] N'statement'

包含 Transact-SQL 语句或批处理的 Unicode 字符串。 @stmt 必须是 Unicode 常量或 Unicode 变量。 不允许使用更复杂的 Unicode 表达式(例如使用 + 运算符连接两个字符串)。 不允许使用字符常量。 Unicode 常量必须带有 N前缀 。 例如,Unicode 常量 N'sp_who' 有效,但字符常量 'sp_who' 无效。 字符串的大小仅受可用数据库服务器内存限制。 在 64 位服务器中,字符串大小限制为 2 GB,即 nvarchar(max) 的最大大小。

@stmt 可以包含与变量名称具有相同格式的参数。 例如:

N'SELECT * FROM HumanResources.Employee WHERE EmployeeID = @IDParameter';

@stmt 中包含的每个参数在 @params 参数定义列表和参数值列表中均必须有对应项。

[ @params = ] N'@parameter_name data_type [ , ...n ]'

一个字符串,其中包含嵌入 @stmt中的所有参数的定义。字符串必须是 Unicode 常量或 Unicode 变量。 每个参数定义由参数名称和数据类型组成。 n 是表示附加参数定义的占位符。 必须在 @params 中定义在 @stmt 中指定的每个参数。 如果@stmt中的 Transact-SQL 语句或批处理不包含参数,则不需要@params 此参数的默认值为 NULL

[ @param1 = ] 'value1'

参数字符串中定义的第一个参数的值。 该值可以是 Unicode 常量,也可以是 Unicode 变量。 必须为 @stmt 中包含的每个参数提供参数值。当 @stmt 中的 Transact-SQL 语句或批处理没有参数时,就不需要这些值。

{ OUT |OUTPUT }

指示参数是输出参数。 文本ntext图像 参数可用作 OUTPUT 参数,除非该过程是公共语言运行时 (CLR) 过程。 使用关键字的 OUTPUT 输出参数可以是游标占位符,除非过程是 CLR 过程。

[ ...n ]

附加参数值的占位符。 这些值只能为常量或变量, 不能是很复杂的表达式(例如函数)或使用运算符生成的表达式。

返回代码值

0(成功)或非零(失败)。

结果集

从生成 SQL 字符串的所有 SQL 语句中返回结果集。

注解

sp_executesql 参数必须按本文前面“ 语法 ”部分中所述的特定顺序输入。 如果这些参数的输入顺序不正确,则会显示一条错误消息。

sp_executesql 与批处理、名称和数据库上下文具有相同的行为 EXECUTE 。 在执行语句之前sp_executesql,不会编译 @stmt 参数中的 sp_executesql Transact-SQL 语句或批处理。 然后,将编译并执行@stmt的内容,作为执行计划与调用sp_executesql的批处理的执行计划分开。 批处理 sp_executesql 不能引用在调用 sp_executesql的批处理中声明的变量。 批处理中的 sp_executesql 本地游标或变量对调用 sp_executesql的批处理不可见。 数据库上下文中的更改只持续到 sp_executesql 语句的结尾。

sp_executesql 当对语句的参数值更改是唯一的变体时,可以使用存储过程而不是存储过程来执行 Transact-SQL 语句。 因为 Transact-SQL 语句本身保持不变,仅参数值发生变化,所以 SQL Server 查询优化器可能重复使用首次执行时所生成的执行计划。 在此方案中,性能相当于存储过程的性能。

注意

若要提高性能,请使用语句字符串中的完全限定对象名称。

sp_executesql 支持将参数值与 Transact-SQL 字符串分开设置,如以下示例所示。

DECLARE @IntVariable AS INT;
DECLARE @SQLString AS NVARCHAR (500);
DECLARE @ParmDefinition AS NVARCHAR (500);

/* Build the SQL string once */
SET @SQLString = N'SELECT BusinessEntityID, NationalIDNumber, JobTitle, LoginID
       FROM AdventureWorks2022.HumanResources.Employee
       WHERE BusinessEntityID = @BusinessEntityID';

SET @ParmDefinition = N'@BusinessEntityID tinyint';

/* Execute the string with the first parameter value. */
SET @IntVariable = 197;

EXECUTE sp_executesql
    @SQLString,
    @ParmDefinition,
    @BusinessEntityID = @IntVariable;

/* Execute the same string with the second parameter value. */
SET @IntVariable = 109;

EXECUTE sp_executesql
    @SQLString,
    @ParmDefinition,
    @BusinessEntityID = @IntVariable;

输出参数也可用于 sp_executesql。 以下示例从示例数据库中的HumanResources.EmployeeAdventureWorks2022表中检索作业标题,并在输出参数@max_title中返回它。

DECLARE @IntVariable AS INT;

DECLARE @SQLString AS NVARCHAR (500);

DECLARE @ParmDefinition AS NVARCHAR (500);

DECLARE @max_title AS VARCHAR (30);

SET @IntVariable = 197;

SET @SQLString = N'SELECT @max_titleOUT = max(JobTitle)
   FROM AdventureWorks2022.HumanResources.Employee
   WHERE BusinessEntityID = @level';

SET @ParmDefinition = N'@level TINYINT, @max_titleOUT VARCHAR(30) OUTPUT';

EXECUTE sp_executesql
    @SQLString,
    @ParmDefinition,
    @level = @IntVariable,
    @max_titleOUT = @max_title OUTPUT;

SELECT @max_title;

能够替换参数 sp_executesql 比使用 EXECUTE 语句执行字符串具有以下优势:

  • 由于字符串中 sp_executesql Transact-SQL 语句的实际文本不会在执行之间更改,因此查询优化器可能与第二次执行中的 Transact-SQL 语句与为第一次执行生成的执行计划匹配。 因此,SQL Server 不必编译第二个语句。

  • Transact-SQL 字符串仅生成一次。

  • 整数参数按其本身格式指定。 不需要强制转换为 Unicode。

OPTIMIZED_SP_EXECUTESQL

适用于: Azure SQL 数据库

启用OPTIMIZED_SP_EXECUTESQL 数据库范围配置 时,使用的 sp_executesql 批处理的编译行为与当前使用的存储过程和触发器等对象的序列化编译行为相同。

当批处理相同(不包括任何参数差异)时, OPTIMIZED_SP_EXECUTESQL 该选项会尝试获取编译锁作为强制机制,以确保编译过程被序列化。 此锁可确保如果多个会话同时调用 sp_executesql ,则在第一个会话启动编译过程后,这些会话会在尝试获取独占编译锁时等待。 首次执行 sp_executesql 编译并将其编译的计划插入计划缓存中。 其他会话中止等待编译锁,并在计划可用后重复使用该计划。

OPTIMIZED_SP_EXECUTESQL如果没有此选项,则通过sp_executesql并行编译执行的多个相同批处理的多个调用,并将编译计划的副本放入计划缓存中,在某些情况下替换或复制计划缓存条目。

注意

启用OPTIMIZED_SP_EXECUTESQL数据库范围配置之前,如果启用了自动更新统计信息,还应使用ASYNC_STATS_UPDATE_WAIT_AT_LOW_PRIORITY数据库范围的配置选项启用自动更新统计信息异步选项。 启用这两个选项可以显著减少与长时间编译时间相关的性能问题以及过多的锁管理器排他锁(LCK_M_X)和 WAIT_ON_SYNC_STATISTICS_REFRESH 等待的可能性。

默认情况下,将关闭 OPTIMIZED_SP_EXECUTESQL。 若要在数据库级别启用 OPTIMIZED_SP_EXECUTESQL ,请使用以下 Transact-SQL 语句:

ALTER DATABASE SCOPED CONFIGURATION
SET OPTIMIZED_SP_EXECUTESQL = ON;

权限

要求 公共 角色具有成员身份。

示例

A. 执行 SELECT 语句

以下示例创建并执行包含名为 <a0SELECT/> 的嵌入参数的语句。

EXECUTE sp_executesql
    N'SELECT * FROM AdventureWorks2022.HumanResources.Employee
    WHERE BusinessEntityID = @level',
    N'@level TINYINT',
    @level = 109;

B. 执行动态生成的字符串

以下示例显示使用 sp_executesql 执行动态生成的字符串。 该示例中的存储过程用于向一组表中插入数据,这些表用于划分一年的销售数据。 每年每个月都有一个表,格式如下:

CREATE TABLE May1998Sales
(
    OrderID INT PRIMARY KEY,
    CustomerID INT NOT NULL,
    OrderDate DATETIME NULL CHECK (DATEPART(yy, OrderDate) = 1998),
    OrderMonth INT CHECK (OrderMonth = 5),
    DeliveryDate DATETIME NULL,
    CHECK (DATEPART(mm, OrderDate) = OrderMonth)
);

此示例存储过程将动态生成并执行 INSERT 语句,以便向正确的表中插入新订单。 此示例使用订货日期生成应包含数据的表的名称,然后将此名称并入 INSERT 语句中。

注意

这是一个基本的示例 sp_executesql。 该示例不包含错误检查,不包括对业务规则的检查,例如保证在表之间不复制订单号。

CREATE PROCEDURE InsertSales @PrmOrderID INT,
    @PrmCustomerID INT,
    @PrmOrderDate DATETIME,
    @PrmDeliveryDate DATETIME
AS
DECLARE @InsertString AS NVARCHAR (500);
DECLARE @OrderMonth AS INT;
-- Build the INSERT statement.
SET @InsertString = 'INSERT INTO ' +
    /* Build the name of the table. */
    SUBSTRING(DATENAME(mm, @PrmOrderDate), 1, 3) +
    CAST(DATEPART(yy, @PrmOrderDate) AS CHAR(4)) + 'Sales' +
    /* Build a VALUES clause. */
    ' VALUES (@InsOrderID, @InsCustID, @InsOrdDate,' +
    ' @InsOrdMonth, @InsDelDate)';

/* Set the value to use for the order month because
   functions are not allowed in the sp_executesql parameter
   list. */
SET @OrderMonth = DATEPART(mm, @PrmOrderDate);

EXEC sp_executesql @InsertString,
    N'@InsOrderID INT, @InsCustID INT, @InsOrdDate DATETIME,
       @InsOrdMonth INT, @InsDelDate DATETIME',
    @PrmOrderID,
    @PrmCustomerID,
    @PrmOrderDate,
    @OrderMonth,
    @PrmDeliveryDate;
GO

sp_executesql使用此过程比使用EXECUTE动态生成的字符串更有效,因为它允许使用参数标记。 参数标记使得数据库引擎更可能重复使用生成的查询计划,这有助于避免其他查询编译。 因此 EXECUTE,每个 INSERT 字符串都是唯一的,因为参数值不同,并且会追加到动态生成的字符串的末尾。 执行时,不会以鼓励计划重用的方式对查询进行参数化,并且必须在执行每个 INSERT 语句之前进行编译,这将在计划缓存中添加查询的单独缓存条目。

°C 使用 OUTPUT 参数

以下示例使用参数 OUTPUT 将语句生成的 SELECT 结果集存储在参数中 @SQLString 。 然后执行两 SELECT 个语句,以使用参数的值 OUTPUT

USE AdventureWorks2022;
GO

DECLARE @SQLString AS NVARCHAR (500);
DECLARE @ParmDefinition AS NVARCHAR (500);
DECLARE @SalesOrderNumber AS NVARCHAR (25);
DECLARE @IntVariable AS INT;

SET @SQLString = N'SELECT @SalesOrderOUT = MAX(SalesOrderNumber)
    FROM Sales.SalesOrderHeader
    WHERE CustomerID = @CustomerID';

SET @ParmDefinition = N'@CustomerID INT,
    @SalesOrderOUT NVARCHAR(25) OUTPUT';

SET @IntVariable = 22276;

EXECUTE sp_executesql
    @SQLString,
    @ParmDefinition,
    @CustomerID = @IntVariable,
    @SalesOrderOUT = @SalesOrderNumber OUTPUT;

-- This SELECT statement returns the value of the OUTPUT parameter.
SELECT @SalesOrderNumber;

-- This SELECT statement uses the value of the OUTPUT parameter in
-- the WHERE clause.
SELECT OrderDate,
       TotalDue
FROM Sales.SalesOrderHeader
WHERE SalesOrderNumber = @SalesOrderNumber;

示例:Azure Synapse Analytics 和 Analytics Platform System (PDW)

D. 执行 SELECT 语句

以下示例创建并执行包含名为 <a0SELECT/> 的嵌入参数的语句。

EXECUTE sp_executesql
    N'SELECT * FROM AdventureWorksPDW2012.dbo.DimEmployee
    WHERE EmployeeKey = @level',
    N'@level TINYINT',
    @level = 109;