SQLSERVER 提供 exec 与 exec sp_executesql (2005版本开始)执行动态sql。
一、EXEC 命令有两种用法
1、执行存储过程
exec 存储过程 @参数 = 值
--或
exec 存储过程 值
exec 存储过程 存储过程中的参数=参数{接受参数返回值} output
CREATE PROCEDURE [dbo].[Sp_GetStudent]
@Score FLOAT,
@Nums INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SELECT * FROM t_student WHERE Score >=@Score
SELECT @Nums=COUNT(1) FROM t_student WHERE Score >=@Score
IF(@Nums>0)
RETURN 1
ELSE
RETURN 0
END
GO
DECLARE @return_value int,
@OutNums int
EXEC @return_value = [dbo].[Sp_GetStudent] @Score = 90,
@Nums = @OutNums OUTPUT
SELECT @OutNums as N'大于90分的人数'
SELECT '返回值' = @return_value
GO
2、执行动态的SQL语句。
exec ('select * from mytable')
使用EXEC执行动态sql语句注意下面问题:
1.不能有输入参数,输出参数。
-- 如下脚本会报错
DECLARE @i AS INT;
SET @i = 10248;
DECLARE @sql AS VARCHAR(52);
SET @sql = 'SELECT * FROM dbo.Orders WHERE OrderID = @i;';
EXEC(@sql);
GO
2.圆括号内不能使用函数或case表达式
----下面的脚本是错误的:
DECLARE @schemaname AS NVARCHAR(128), @tablename AS NVARCHAR(128);
SET @schemaname = N'dbo';
SET @tablename = N'Order Details';
EXEC(N'SELECT COUNT(*) FROM '
+ QUOTENAME(@schemaname) + N'.' + QUOTENAME(@tablename) + N';');
GO
------不过把函数放在变量中是可以的:
DECLARE
@schemaname AS NVARCHAR(128),
@tablename AS NVARCHAR(128),
@sql AS NVARCHAR(539);
SET @schemaname = N'dbo';
SET @tablename = N'Order Details';
SET @sql = N'SELECT COUNT(*) FROM '
+ QUOTENAME(@schemaname) + N'.' + QUOTENAME(@tablename) + N';'
EXEC(@sql);
3.不能利用重用执行计划,所以存在性能问题。
DBCC FREEPROCCACHE -- 清空执行计划缓存
DECLARE @Sql NVARCHAR(MAX),
@ID INT;
SET @ID = 15; -- 15使用之后,换成10, 12等再次执行
SET @sql = 'SELECT * FROM Person.Person WHERE BusinessEntityID = '+CAST(@ID AS
VARCHAR(10))+' ORDER BY BusinessEntityID DESC'
EXEC(@sql);
SELECT cacheobjtype,objtype,usecounts,sql
FROM sys.syscacheobjects
WHERE sql NOT LIKE '%cach%' AND sql NOT LIKE '%sys.%'
使用exec 执行三次后,查询到的执行计划缓存如下:
通过上面的截图可以看到,执行三次生成了三次执行计划。
4、容易被sql注入,存在安全问题。
DECLARE @lastname AS NVARCHAR(40), @sql AS NVARCHAR(200);
SET @lastname = N''' DROP TABLE dbo.Employees --';
SET @sql = N'SELECT * FROM dbo.Employees WHERE LastName = '''
+ @lastname + ''';';
EXEC @sql;
GO
--实际sql SELECT * FROM dbo.Employees WHERE LastName = '' DROP TABLE dbo.Employees --';
注意
EXEC 执行拼接的SQL语句的时候,不支持内嵌参数,包括输入参数和输出参数。有的时候我们想把得到的count(*)传出来,无法直接将值传出,只能通过select 变量/insert into exec等方式看到值。
二、sp_executesql 用法
sp_executesql 后面需要直接使用表示拼接后的sql的变量或者sql常量字符串,后面不能直接使用常量+变量拼接的语句
如下面的语句会报错
declare @FName2 varchar(20) = 'Ken',
@PeronType varchar(10) = 'GC',
@sql nvarchar(1000);
exec sp_executesql 'select * from Person.Person where FirstName =''' + @FName2 + ''' and PersonType= ''' + @PeronType + ''''
这种情况下,需要先将sql拼凑后的结果放入一个变量中,然后使用 exec sp_executesql 执行;或者使用入参的方式来实现。推荐使用下面的方式:
declare @FName2 varchar(20) = 'Ken',
@PersonType varchar(10) = 'GC',
@sql nvarchar(1000);
set @sql = 'select * from Person.Person where FirstName = @FName2 and PersonType = @PersonType'
exec sp_executesql @sql, N'@FName varchar(20), @PersonType varchar(10)', @FName2, @PeronType
sp_executesql要求动态Sql和动态Sql参数列表必须是Nvarchar, 动态Sql的参数列表与外部提供值的参数列表顺序必需一致,且不能使用变量。
exec 查询不能使用sql外面定义的变量,查询的结果也不容易进行使用。而exec sp_executesql 可以使用入参和出参的方式很方便的获取或者返回内容。
sp_executesql可以建立带参数的查询字符串还可以重用执行计划。
DBCC FREEPROCCACHE
DECLARE @Sql NVARCHAR(MAX),@ID INT;
SET @ID = 17;
SET @sql = 'SELECT * FROM Person.Person WHERE BusinessEntityID = @ID ORDER BY BusinessEntityID DESC'
exec sp_executesql @sql, N'@ID int', @ID
SELECT cacheobjtype,objtype,usecounts,sql FROM sys.syscacheobjects WHERE sql NOT LIKE '%cach%' AND sql NOT LIKE '%sys.%'
执行三次之后,查询到的执行计划缓存如下:
通过上面的截图可以看到,只生成了一次执行计划。
sp_executesql可以建立带参数的查询字符串可以防止sql注入
-- 下面的SQL注入
DECLARE @Sql NVARCHAR(MAX),@FName varchar(20);
SET @FName = '''ken'' or 1=1';
SET @sql = 'SELECT * FROM Person.Person WHERE FirstName = ' + @FName + ' ORDER BY BusinessEntityID DESC'
exec sp_executesql @sql
--下面的可以防止SQL注入
DECLARE @Sql NVARCHAR(MAX),@FName varchar(20);
SET @FName = '''ken'' or 1=1';
SET @sql = 'SELECT * FROM Person.Person WHERE FirstName = @FName ORDER BY BusinessEntityID DESC'
exec sp_executesql @sql, N'@FName varchar(20)', @FName