【转载】SQL SERVER 存储过程中执行动态Sql语句
MSSQL为我们提供了两种动态执行SQL语句的命令,分别是EXEC和sp_executesql;通常,sp_executesql则更具有优势,它提供了输入输出接口,而EXEC没有。还有一个最大的好处就是利用sp_executesql,能够重用执行计划,这就大大提供了执行性能,还可以编写更安全的代码。EXEC在某些情况下会更灵活。除非您有令人信服的理由使用EXEC,否侧尽量使用sp_executesql.
1.EXEC的使用
EXEC命令有两种用法,一种是执行一个存储过程,另一种是执行一个动态的批处理。以下所讲的都是第二种用法。
下面先使用EXEC演示一个例子,代码1
DECLARE @TableName VARCHAR(50),@Sql NVARCHAR (MAX),@OrderID INT; SET @TableName ='Orders'; SET @OrderID =10251; SET @sql =
'SELECT * FROM '+QUOTENAME(@TableName) +'WHERE OrderID = '+
CAST(@OrderID AS VARCHAR(10))+' ORDER BY ORDERID DESC' EXEC(@sql);
注:这里的EXEC括号中只允许包含一个字符串变量,但是可以串联多个变量,如果我们这样写EXEC:
EXEC('SELECT TOP('+ CAST(@TopCount AS VARCHAR(10)) +')* FROM '+
QUOTENAME(@TableName) +' ORDER BY ORDERID DESC');
SQL编译器就会报错,编译不通过,而如果我们这样:
EXEC(@sql+@sql2+@sql3);
编译器就会通过;
所以最佳的做法是把代码构造到一个变量中,然后再把该变量作为EXEC命令的输入参数,这样就不会受限制了。
EXEC的缺点是不提供接口,这里的接口是指,它不能执行一个包含一个带变量符的批处理,如下
DECLARE @TableName VARCHAR(50),@Sql NVARCHAR(MAX),@OrderID INT; SET @TableName ='Orders'; SET @OrderID =10251; SET @sql ='SELECT * FROM '+QUOTENAME(@TableName) +
'WHERE OrderID = @OrderID ORDER BY ORDERID DESC' EXEC(@sql);
关键就在SET @sql这一句话中,如果我们运行这个批处理,编译器就会产生一下错误
Msg 137, Level 15, State 2, Line 1
必须声明标量变量 "@OrderID"。
使用EXEC时,如果您想访问变量,必须把变量内容串联到动态构建的代码字符串中,如:
SET @sql ='SELECT * FROM '+QUOTENAME(@TableName) +
'WHERE OrderID = '+CAST(@OrderID AS VARCHAR(10))+' ORDER BY ORDERID DESC'
串联变量的内容也存在性能方面的弊端。SQL Server为每一个的查询字符串创建新的执行计划,即使查询模式相同也是这样。为演示这一点,先清空缓存中的执行计划
DBCC FREEPROCCACHE (这个不是本文所涉及的内容,您可以查看MS的MSDN)
将代码1运行3次,分别对@OrderID 赋予下面3个值,10251,10252,10253。然后使用下面的代码查询
SELECT cacheobjtype,objtype,usecounts,sql FROM sys.syscacheobjects WHERE sql NOT LIKE '%cach%' AND sql NOT LIKE '%sys.%'
点击F5运行,我们可以看到,每执行一次都要产生一次的编译,执行计划没有得到充分重用。
EXEC除了不支持动态批处理中的输入参数外,他也不支持输出参数。默认情况下,EXEC把查询的输出返回给调用者。例如下面代码返回Orders表中所有的记录数
DECLARE @sql NVARCHAR(MAX)
SET @sql ='SELECT COUNT(ORDERID) FROM Orders';
EXEC(@sql);
然而,如果你要把输出返回给调用批处理中的变量,事情就没有那么简单了。为此,你必须使用INSERT EXEC语法把输出插入到一个目标表中,然后从这表中获取值后赋给该变量,就像这样:
DECLARE @sql NVARCHAR(MAX),@RecordCount INT
SET @sql ='SELECT COUNT(ORDERID) FROM Orders';
CREATE TABLE #T(TID INT);
INSERT INTO #T EXEC(@sql);
SET @RecordCount = (SELECT TID FROM #T)
SELECT @RecordCount
DROP TABLE #T2
2.sp_executesql的使用
sp_executesql命令在SQL Server中引入的比EXEC命令晚一些,它主要为重用执行计划提供更好的支持。
为了和EXEC作一个鲜明的对比,我们看看如果用代码1的代码,把EXEC换成sp_executesql,看看是否得到我们所期望的结果
DECLARE @TableName VARCHAR(50),@sql NVARCHAR(MAX),@OrderID INT ,@sql2 NVARCHAR(MAX);
SET @TableName ='Orders ';
SET @OrderID =10251;
SET @sql ='SELECT * FROM '+QUOTENAME(@TableName) +' WHERE OrderID = '+CAST(@OrderID AS VARCHAR(50)) +' ORDER BY ORDERID DESC'
EXEC sp_executesql @sql
注意最后一行;事实证明可以运行;
sp_executesql提供接口
sp_executesql命令比EXEC命令更灵活,因为它提供一个接口,该接口及支持输入参数也支持输出参数。这功能使你可以创建带参数的查询字符串,这样就可以比EXEC更好的重用执行计划,sp_executesql的构成与存储过程非常相似,不同之处在于你是动态构建代码。它的构成包括:代码快,参数声明部分,参数赋值部分。说了这么多,还是看看它的语法:
EXEC sp_executesql
@stmt=<statement>,--类似存储过程主体
@params =<params>, --类似存储过程参数部分,声明参数类型
<params assignment>--类似存储过程调用,为参数赋值,参数值要和参数顺序要一一对应,也可以通过为参数指明参数值的方式为其赋值
@stmt参数是输入的动态批处理,它可以引入输入参数或输出参数,和存储过程的主体语句一样,只不过它是动态的,而存储过程是静态的,不过你也可以在存储过程中使用sp_executesql;
@params参数与定义输入/输出参数的存储过程头类似,实际上和存储过程头的语法完全一样;
@<params assignment> 与调用存储过程的EXEC部分类似。
其实@stmt,@params可以省略,那么exec sp_executesql的语法就可以简写成如下格式:
EXEC sp_executesql
<statement>,
<params>,
<params assignment>
为了说明sp_executesql对执行计划的管理优于EXEC,我将使用前面讨论EXEC时用到的代码。
DECLARE @TableName VARCHAR(50),@sql NVARCHAR(MAX),@OrderID INT;
SET @TableName ='Orders ';
SET @OrderID =10251;
SET @sql ='SELECT * FROM '+@TableName +' WHERE OrderID = @OID ORDER BY ORDERID DESC'
--注意当要对动态sql语句的表名实行参数化时,不可以如下表示:
--set @sql='select * from @TableName where OrderID=@OID ORDER BY Orderid desc',
--如果这样会提示必须声明标量变量@TableName,只可以如上面所写的一样,将表名@TableName作为变量名进行拼接 EXEC sp_executesql
@sql,
N'@OID int ',
@OID = @OrderID
下面我们看看exec sp_executesql的执行效率,在调用该代码和检查它生成的执行计划前,先清空缓存中的执行计划;
DBCC FREEPROCCACHE
将上面的动态代码执行3次,每次执行都赋予@OrderID 不同的值,然后查询sys.syscacheobjects表,并注意它的输出,优化器只创建了一个备用计划,而且该计划被重用的3次
SELECT cacheobjtype,objtype,usecounts,sql FROM sys.syscacheobjects
WHERE sql NOT LIKE '%cache%' AND sql NOT LIKE '%sys.%' AND sql NOT LIKE '%sp_executesql%'
点击F5运行。
sq_executesql的另一个与其接口有关的强大功能是,你可以使用输出参数为调用批处理中的变量返回值。利用该功能可以避免用临时表返回数据,从而得到更高效的代码和更少的重新编译。定义和使用输出参数的语法与存储过程类似。也就是说,你需要在声明参数时指定OUTPUT子句。例如,下面的静态代码简单的演示了如何从动态批处理中利用输出参数@p把值返回到外部批处理中的变量@i.
EXEC sp_executesql
@sql,
N'@p AS INT OUTPUT',
@p = @i OUTPUT
SELECT @i --该代码返回输出10
以字母 N 为前缀标识 Unicode 字符串常量
总结以下几点:
一.使用exce sp_executesql效率比exec要高,同一类型的语句,只需编译一次即可,而exec执行几次就需要编译几次。
二.构造动态sql的where子句,也就是条件子句时,exec无法使用变量来进行站位,需要将变量转换成字符串,然后和动态sql进行拼接,这就可能引起Sql注入问题,如下:
而若使用exec sp_executesql则可以使用变量来进行站位,以后再给这个参数传值的放式构造动态sql,就避免的Sql注入的问题,如下:
SET @sql ='SELECT * FROM '+@TableName +' WHERE OrderID = @OID ORDER BY ORDERID DESC'
三.无论是Exec还是Exec sp_executesql,如果想要将表名和列名进行动态参数化,不可以使用表名参数和列名参数来进行站位,而且表名参数和列名参数需要使用存储过程的参数.对 于exec sp_executesql来说,不可以将表名参数和列名参数在指定为在exec sp_executesql参数声明部分声明的参数,如:
@colName nvarchar(10),
@Name nvarchar(10)
AS
BEGIN
declare @sql nvarchar(50);
set @sql='select '+ @colName+' from '+@tbName+' where name=@whereName';
--注意此句不可以写成如下:
-- set @sql='select @colName from @tbName where name=@whereName';
exec sp_executesql
@sql,
N'@whereName nvarchar(10)',
@Name
END
也就是说exec sp_executesql语句的参数声明部分只能声明动态sql的where子句的参数。
【转载】SQL SERVER 存储过程中执行动态Sql语句的更多相关文章
- 存储过程中执行动态Sql语句
MSSQL为我们提供了两种动态执行SQL语句的命令,分别是EXEC和sp_executesql;通常,sp_executesql则更具有优势,它提供了输入输出接口,而EXEC没有.还有一个最大的好处就 ...
- 怎样SQL存储过程中执行动态SQL语句
MSSQL为我们提供了两种动态执行SQL语句的命令,分别是EXEC和sp_executesql;通常,sp_executesql则更具有优势,它提供了输入输出接口,而EXEC没有.还有一个最大的好处就 ...
- 在Oracle中执行动态SQL的几种方法
转载:在Oracle中执行动态SQL的几种方法 以下为内容留存: 在Oracle中执行动态SQL的几种方法 在一般的sql操作中,sql语句基本上都是固定的,如:SELECT t.empno,t.en ...
- mysql 存储过程中使用动态sql语句
Mysql 5.0 以后,支持了动态sql语句,我们可以通过传递不同的参数得到我们想要的值 这里介绍两种在存储过程中的动态sql: set sql = (预处理的sql语句,可以是用concat拼接的 ...
- Sql Server 存储过程中查询数据无法使用 Union(All)
原文:Sql Server 存储过程中查询数据无法使用 Union(All) 微软Sql Server数据库中,书写存储过程时,关于查询数据,无法使用Union(All)关联多个查询. 1.先看一段正 ...
- SQL Server存储过程中使用表值作为输入参数示例
这篇文章主要介绍了SQL Server存储过程中使用表值作为输入参数示例,使用表值参数,可以不必创建临时表或许多参数,即可向 Transact-SQL 语句或例程(如存储过程或函数)发送多行数据,这样 ...
- SQL Server-聚焦sp_executesql执行动态SQL查询性能真的比exec好?
前言 之前我们已经讨论过动态SQL查询呢?这里为何再来探讨一番呢?因为其中还是存在一定问题,如标题所言,很多面试题也好或者有些博客也好都在说在执行动态SQL查询时sp_executesql的性能比ex ...
- SQL Server 存储过程中处理多个查询条件的几种常见写法分析,我们该用那种写法
本文出处: http://www.cnblogs.com/wy123/p/5958047.html 最近发现还有不少做开发的小伙伴,在写存储过程的时候,在参考已有的不同的写法时,往往很迷茫,不知道各种 ...
- 在SQL Server 2008中执行透明数据加密
问题 安全是任何公司的一个主要考量.数据库备份容易被偷并被恢复到另一个SQL Server实例上.当我们浏览SQL Server 2008的新特性时,我们对一个叫做透明数据加密的特性很感兴趣,我们可以 ...
随机推荐
- SpringBoot-JavaMailSender接口实战
相信使用过Spring的众多开发者都知道Spring提供了非常好用的JavaMailSender接口实现邮件发送,在Spring Boot的Starter模块中也为此提供了自动化配置. 下面通过实例来 ...
- 华为设备配置Stelnet命令
配置SSH server:rsa local-key-pair create 生成本地RSA主机密钥对 display rsa local-key-pair public 查看本地密钥对中的公钥信息 ...
- 【C++】spdlog光速入门,C++logger最简单最快的库
参考文档:https://spdlog.docsforge.com/master/ spdlog简介 Very fast, header only, C++ logging library. 一个he ...
- 9.MongoDB系列之创建副本集(二)
1. 如何设计副本集 大多数:选取主节点时需要由大多数决定,主节点只有在得到大多数支持时才能继续作为主节点,写操作被复制到大多数成员时就是安全的写操作.这里的大多数定义为"副本集中一半以上的 ...
- 几个实用 shell 脚本
1. Dos攻击防范(自动屏蔽攻击 IP) #!/bin/bash DATE=$(date +%d/%b/%Y:%H:%M) LOG_FILE=/usr/local/nginx/logs/demo2. ...
- 剑指offer20题表示数值的字符串:这题实在是太优雅了
目录 前言 一.憨憨初解 1.思路 2.代码 3.战绩 4.反思 二.看懂再解 1.思路 2.代码 3.C++版战绩 总结 前言 题目来源:https://leetcode.cn/problems/b ...
- 论文笔记 - RETRIEVE: Coreset Selection for Efficient and Robust Semi-Supervised Learning
Motivation 虽然半监督学习减少了大量数据标注的成本,但是对计算资源的要求依然很高(无论是在训练中还是超参搜索过程中),因此提出想法:由于计算量主要集中在大量未标注的数据上,能否从未标注的数据 ...
- 【题解】CF45I TCMCF+++
题面传送门 题目描述 有 \(n\) 个数 \(a_i\) 请你从中至少选出一个数,使它们的乘积最大 解决思路 对于正数,对答案一定有贡献(正数越乘越大),所以输入正数时直接输出即可. 对于负数,如果 ...
- Day10:for循环结构的使用详解
for循环 将0~100内的奇.偶数分别求和 思路 第一步先将0~100以内的奇.偶数分成两队,第二步使奇数累加.ou'shu public class ForCirculate{ public st ...
- Kubernetes核心技术Pod
Kubernetes核心技术Pod Pod概述 Pod是K8S系统中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型,也是在K8S上运行容器化应用的资源对象,其它的资源对象 ...