您的位置:首页 > 数据库

在SQL Server中生成动态SQL语句

2010-10-26 08:24 417 查看

在SQL Server中生成动态SQL语句

在需要解决某个棘手的数据库问题时,生成SQL语句可作为一种强大的工具,虽然我们在使用它时必须十分小心。本文将探讨如何用这种功能来轻松地生成SQL语句。

  动态SQL语句

  一个动态的SQL语句是在执行时创建的,不同的条件生成不同的SQL语句。在我们需要决定运行时有哪些字段从SELECT语句返回时,在决定查询的不同标准时,动态地创建这些语句是很有用处的。

   这些SQL字符串不是为了语法分析以便于查找错误,因为它们是在运行时生成的,而且它们有可能将安全漏洞引入到你的数据库中。此外,SQL字符串有可能 成为一个调试上恶梦,这就是为什么笔者并非动态生成SQL语句的一个痴迷者的原因。但在有些情况下,这种功能却是很不错的。

  一个动态的例子

  笔者经常回答的一个问题是“我如何将我的WHERE语句传递给一个存储过程?”,而且经常看到类似于下面的情况,其TSQL语法是非法的。

  DECLARE@WhereClauseNVARCHAR(2000)

  SET@WhereClause='Prouct=''Computer'''

  SELECT*FROMSalesHistoryWHERE@WhereClause


   但情况并不如此简单,有时,需要额外的标准,而且随着数据表的逐渐增大,就需要越来越多的标准。这通常可以通过为不同的标准编写不同的存储过程而解决,不过有时每次执行的这种标准是如此迥然不同,以至于在一个存储过程中包含所有的可能性可能成为一个沉重的负担。虽然这些存储过程可以用于考虑每一个可能的 WHERE语句,(当然这要依赖于不同的参数) ,这通常会引起性能上的降低,因为在WHERE子句中有太多的条件。

  让我们看看如何创建一个简单的动态查询。首先,我们需要一个表和一些查询的数据。下面的脚本创建了SalesHistory表并将数据装载到其中。

CREATETABLE[dbo].[SalesHistory]

  (

  [SaleID][int]IDENTITY(1,1),

  [Product][varchar](10)NULL,

  [SaleDate][datetime]NULL,

  [SalePrice][money]NULL

  )

  GO

  SETNOCOUNTON

  DECLARE@iINT

  SET@i=1

  WHILE(@i<=5000)

  BEGIN

  INSERTINTO[SalesHistory](Product,SaleDate,SalePrice)

  VALUES('Computer',DATEADD(ww,@i,'3/11/1919'),

  DATEPART(ms,GETDATE())+(@i+57))

  INSERTINTO[SalesHistory](Product,SaleDate,SalePrice)

  VALUES('BigScreen',DATEADD(ww,@i,'3/11/1927'),

  DATEPART(ms,GETDATE())+(@i+13))

  INSERTINTO[SalesHistory](Product,SaleDate,SalePrice)

  VALUES('PoolTable',DATEADD(ww,@i,'3/11/1908'),

  DATEPART(ms,GETDATE())+(@i+29))

  SET@i=@i+1

  END


  下面我们创建一个可以接受WHERE子句的存储过程。为了达成这个例子的目的,笔者将假定WHERE子句是从调用客户应用程序中动态生成的。

  CREATEPROCEDUREusp_GetSalesHistory

  (

  @WhereClauseNVARCHAR(2000)=NULL

  )

  AS

  BEGIN

  DECLARE@SelectStatementNVARCHAR(2000)

  DECLARE@FullStatementNVARCHAR(4000)

  SET@SelectStatement='SELECTTOP5 * FROMSalesHistory'

  SET@FullStatement=@SelectStatement+ISNULL(@WhereClause,'')

  PRINT@FullStatement

  EXECUTE sp_executesql@FullStatement

  /*

  --也可用EXECUTE()执行相同的语句

  EXECUTE(@FullStatement)

  */

  END


笔者在此设置@WhereClause允许NULL值,因为我们可能并不总是想为@WhereClause传递一个值。

  对这个存 储过程的每次执行而言,每一个字段都从SalesHistory中返回前五行。如果为@WhereClause参数传递了一个值,执行语句将把此字符串添 加到@SelectStatement字符串中。然后笔者使用了存储过程sp_executesql执行动态生成的SQL字符串。

  sp_executesql或 EXECUTE()

  在SQL Server中有两种方法执行动态SQL语句,一是使用sp_executesql系统存储过程,二是使用EXECUTE()。有时这两种方法可以产同样的结果,不过在其如何运行上却有着一些不同点。

   系统存储过程sp_executesql允许参数可被传递进入或传出动态的SQL语句,而EXECUTE()则不然。因为SQL语句是作为一个参数被传 递给sp_executesql存储过程中的,与EXECUTE()相比,它不易受到SQL注入式攻击。因为sp_executesql是一个存储过程, 所以将SQL字符串传递给它可以使SQL字符串有更多的机会被放置在高速缓存中。以笔者的观点,sp_executesql可以生成清晰而且容易阅读和维护的代码。这就是笔者为什么更喜欢用sp_executesql来执行动态SQL语句的原因。

  在笔者前面的例子中,我们看了如何通过将一个WHERE子句传递给一个存储过程而生成一个简单的SQL语句。不过,如果我们想从动态生成的SQL语句中得到参数值的列表该怎么办?笔者将使用sp_executesql,因为它准许我们输入和输出参数。

  我们要稍微修改一下最初的存储过程,这就可以将从SQL语句中返回的记录总数分配给一个输出参数。

  DROPPROCEDUREusp_GetSalesHistory

  GO

  CREATEPROCEDUREusp_GetSalesHistory

  (

  @WhereClauseNVARCHAR(2000)=NULL,

  @TotalRowsReturnedINTOUTPUT

  )

  AS

  BEGIN

  DECLARE@SelectStatementNVARCHAR(2000)

  DECLARE@FullStatementNVARCHAR(4000)

  DECLARE@ParameterListNVARCHAR(500)

  SET@ParameterList='@TotalRowsReturnedINTOUTPUT'

  SET@SelectStatement='SELECT@TotalRowsReturned=COUNT(*)FROMSalesHistory'

  SET@FullStatement=@SelectStatement+ISNULL(@WhereClause,'')

  PRINT@FullStatement

  EXECUTEsp_executesql@FullStatement,@ParameterList,@TotalRowsReturned=@TotalRowsReturnedOUTPUT

  END

  GO


   在上面过程中,笔者需要声明一个参数列表,以传递给sp_executesql存储过程,因为在运行时将一个值分配给了变量。对 sp_executesql调用的唯一一个变化是在usp_GetSalesHistory存储过程中,笔者将从调用中得到的输出参数分配给了本地的 @TotalRowsReturned参数。

  我们还可以用与以前类似的方式调用usp_GetSalesHistory存储过程,不过在此增加了一个输出参数,用以指明返回的行。

  DECLARE@WhereClauseNVARCHAR(2000),@TotalRowsReturnedINT

  SET@WhereClause='WHEREProduct=''Computer'''

  EXECUTEusp_GetSalesHistory

  @WhereClause=@WhereClause,

  @TotalRowsReturned=@TotalRowsReturnedOUTPUT

  SELECT@TotalRowsReturned


  小心为妙

  虽然笔者并不极力推荐动态SQL语句,但它确实是一个有用的工具。如果你决定要将动态SQL语句集成到实际的代码中,需谨慎对待。因为这种代码可以将一些潜在的漏洞引入到你的系统中;如果你认真对待了,这种代码可以灵活为你地解决一些问题。

SQL Server中执行动态SQL两种正确方式

【IT168 技术文档】动态SQL:code that is executeddynamically。它一般是根据用户输入或外部条件动态组合的SQL语句块。动态SQL能灵活的发挥SQL强大的功能、方便的解决一些其它方法难以 解决的问题。相信使用过动态SQL的人都能体会到它带来的便利,然而动态SQL有时候在执行性能(效率)上面不如静态SQL,而且使用不恰当,往往会在安全方面存在隐患(SQL 注入式攻击)。

  动态SQL可以通过EXECUTE 或SP_EXECUTESQL这两种方式来执行。

  EXECUTE

  执行 Transact-SQL 批中的命令字符串、字符串或执行下列模块之一:系统存储过程、用户定义存储过程、标量值用户定义函数或扩展存储过程。SQL Server 2005 扩展了 EXECUTE 语句,以使其可用于向链接服务器发送传递命令。此外,还可以显式设置执行字符串或命令的上下文

  SP_EXECUTESQL

   执行可以多次重复使用或动态生成的 Transact-SQL 语句或批处理。Transact-SQL 语句或批处理可以包含嵌入参数。在批处理、名称作用域和数据库上下文方面,SP_EXECUTESQL 与 EXECUTE 的行为相同。SP_EXECUTESQL stmt 参数中的 Transact-SQL 语句或批处理在执行 SP_EXECUTESQL 语句时才编译。随后,将编译 stmt 中的内容,并将其作为执行计划运行。该执行计划独立于名为 SP_EXECUTESQL 的批处理的执行计划。SP_EXECUTESQL 批处理不能引用调用 SP_EXECUTESQL 的批处理中声明的变量。SP_EXECUTESQL 批处理中的本地游标或变量对调用 SP_EXECUTESQL 的批处理是不可见的。对数据库上下文所作的更改只在SP_EXECUTESQL 语句结束前有效。

  如果只更改了语句中的参数值,则 sp_executesql 可用来代替存储过程多次执行 Transact-SQL 语句。因为 Transact-SQL 语句本身保持不变,仅参数值发生变化,所以 SQLServer 查询优化器可能重复使用首次执行时所生成的执行计划。

  一般来说,我们推荐、优先使用SP_EXECUTESQL来执行动态SQL,一方 面它更加灵活、可以有输入输出参数、另外一方面,查询优化器更有可能重复使用执行计划,提高执行效率。还有就是使用SP_EXECUTESQL能提高安全 性;当然也不是说要完全摈弃EXECUTE,在特定场合下,EXECUTE比SP_EXECUTESQL更方便些,比如动态SQL字符串是VARCHAR 类型、不是NVARCHAR类型。SP_EXECUTESQL 只能执行是Unicode的字符串或是可以隐式转换为ntext的常量或变量、而EXECUTE则两种类型的字符串都能执行。

  下面我们来对比看看EXECUTE 和SP_EXECUTESQL的一些细节地方。

EXECUTE (N'SELECT * FROM Groups') --执行成功
EXECUTE ('SELECT * FROM Groups') --执行成功

SP_EXECUTESQL N'SELECT * FROMGroups'; --执行成功
SP_EXECUTESQL 'SELECT * FROM Groups' --执行出错

  Summary:EXECUTE 可以执行非Unicode或Unicode类型的字符串常量、变量。而SP_EXECUTESQL只能执行Unicode或可以隐式转换为ntext的字符串常量、变量。

DECLARE @GroupName VARCHAR(50);

SET @GroupName = 'SuperAdmin';

EXECUTE ('SELECT* FROM Groups WHERE GroupName=''' + SUBSTRING(@GroupName, 1,5) + '''');--'SUBSTRING' 附近有语法错误。

DECLARE @Sql VARCHAR(200);
DECLARE @GroupName VARCHAR(50);

SET @GroupName = 'SuperAdmin';
SET @Sql = 'SELECT* FROM Groups WHERE GroupName=''' + SUBSTRING(@GroupName, 1,5) + ''''
--PRINT @Sql;
EXECUTE (@Sql);

  Summary:EXECUTE括号里面只能是字符串变量、字符串常量、或它们的连接组合,不能调用其它一些函数、存储过程等。 如果要使用,则使用变量组合,如上所示。

DECLARE @Sql VARCHAR(200);
DECLARE @GroupName VARCHAR(50);

SET @GroupName = 'SuperAdmin';
SET @Sql = 'SELECT* FROM Groups WHERE GroupName=@GroupName'
--PRINT @Sql;
EXECUTE (@Sql); --出错:必须声明标量变量"@GroupName"。

SET @Sql = 'SELECT * FROM Groups WHERE GroupName=' +QUOTENAME(@GroupName, '''')
EXECUTE (@Sql); --正确:

DECLARE @Sql NVARCHAR(200);
DECLARE @GroupName NVARCHAR(50);

SET @GroupName = 'SuperAdmin';
SET @Sql = 'SELECT* FROM Groups WHERE GroupName=@GroupName'
PRINT @Sql;
EXEC SP_EXECUTESQL @Sql, N'@GroupNameNVARCHAR',@GroupName
查询出来没有结果,没有声明参数长度。

DECLARE @Sql NVARCHAR(200);
DECLARE @GroupName NVARCHAR(50);

SET @GroupName = 'SuperAdmin';
SET @Sql = 'SELECT* FROM Groups WHERE GroupName=@GroupName'
PRINT @Sql;
EXEC SP_EXECUTESQL @Sql, N'@GroupNameNVARCHAR(50)',@GroupName

  Summary:动态批处理不能访问定义在批处理里的局部变量。 SP_EXECUTESQL 可以有输入输出参数,比EXECUTE灵活。  

下面我们来看看EXECUTE , SP_EXECUTESQL的执行效率,首先把缓存清除执行计划,然后改变用@GroupName值SuperAdmin、CommonUser、CommonAdmin分别执行三次。然后看看其使用缓存的信息

DBCC FREEPROCCACHE;

DECLARE @Sql VARCHAR(200);
DECLARE @GroupName VARCHAR(50);

SET @GroupName = 'SuperAdmin'; --'CommonUser', 'CommonAdmin'
SET @Sql = 'SELECT* FROM Groups WHERE GroupName=' + QUOTENAME(@GroupName, '''')
EXECUTE (@Sql);

SELECT cacheobjtype, objtype, usecounts, sql
FROM sys.syscacheobjects
WHERE sql NOT LIKE '%cache%'
AND sql NOT LIKE '%sys.%';

  如下图所示:

  依葫芦画瓢,接着我们看看SP_EXECUTESQL的执行效率.

DBCC FREEPROCCACHE;

DECLARE @Sql NVARCHAR(200);
DECLARE @GroupName NVARCHAR(50);

SET @GroupName = 'SuperAdmin'; --'CommonUser', 'CommonAdmin'
SET @Sql = 'SELECT* FROM Groups WHERE GroupName=@GroupName'
EXECUTE SP_EXECUTESQL @Sql, N'@GroupName NVARCHAR(50)', @GroupName;

SELECT cacheobjtype, objtype, usecounts, sql
FROM sys.syscacheobjects
WHERE sql NOT LIKE '%cache%'
AND sql NOT LIKE '%sys.%';

  执行结果如下图所示:

  Summary:EXEC 生成了三个独立的 ad hoc 执行计划,而用SP_EXECUTESQL只生成了一次执行计划,重复使用了三次,试想如果一个库里面,有许多这样类似的动态SQL,而且频繁执行,如果采用SP_EXECUTESQL就能提高性能。

1. --动态语句语法

2. /******************************************************************************************************************************************************

3. 动态语句语法:exec\sp_executesql语法

4.

5. 整理人:中国风(Roy)

6.

7. 日期:2008.06.06

8. ******************************************************************************************************************************************************/

9. 动态语句语法:

10.

11.--方法1查询表改为动态

12.select * from sysobjects

13.exec('select ID,Name from sysobjects')

14.exec sp_executesql N'select ID,Name from sysobjects'--多了一个N为unicode

15.

16.--方法2:字段名,表名,数据库名之类作为变量时,用动态SQL

17.declare @FName varchar(20)

18.set @FName='ID'

19.exec('select '+@FName+' from sysobjects where '+@FName+'=5' )

20.

21.

22.declare @s varchar(1000)

23.set @s=N'select '+@FName+' from sysobjects where '+@FName+'=5'

24.exec sp_executesql @s--会报错

25.

26.

27.declare @s nvarchar(1000)--改为nvarchar

28.set @s=N'select '+@FName+' from sysobjects where '+@FName+'=5'

29.exec sp_executesql @s--成功

30.

31.

32.--方法3:输入参数

33.

34.declare @i int,@s nvarchar(1000)

35.set @i=5

36.exec('select ID,Name from sysobjects where ID='+@i)

37.

38.set @s='select ID,Name from sysobjects where ID=@i'

39.exec sp_executesql @s,N'@i int',@i--此处输入参数要加上N

40.

41.--方法4:输出参数

42.

43.declare @i int,@s nvarchar(1000)

44.set @s='select @i=count(1) from sysobjects'

45.

46.--用exec

47.exec('declare @i int '+@s+' select @i')--把整个语句用字符串加起来执行

48.

49.--用sp_executesql

50.exec sp_executesql @s,N'@i int output',@i output--此处输出参数要加上N

51.select @i

52.

53.

54.--方法5:输入输出

55.

56.--用sp_executesql

57.declare @i int,@con int,@s nvarchar(1000)

58.set @i=5

59.select @s='select @con=count(1) from sysobjects where ID>@i'

60.exec sp_executesql @s,N'@con int output,@i int',@con output ,@i

61.select @con

62.

63.--用exec

64.declare @i int,@s nvarchar(1000)

65.set @i=5

66.select @s='declare @con int select @con=count(1) from sysobjects where ID>'+rtrim(@i)+' select @con'

67.exec(@s)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: