-1

我有一个场景以编程方式更改少数用户定义的表类型的列。为此,我需要删除引用的存储过程。因此,我设计了我的 SQL 脚本来执行以下活动:

  1. 将存储过程从 sys.sql_modules 表备份到临时表(定义、uses_ansi_nulls 和 uses_quoted_identifier 列)。
  2. 删除存储过程。
  3. 更改用户定义的表类型。
  4. 现在,使用临时表的定义列重新创建存储过程,但我无法使用 uses_ansi_nulls 和 uses_quoted_identifier 列的值。如何在重新创建存储过程时重用 uses_ansi_nulls 和 uses_quoted_identifier 列。

我正在使用游标将暂存列的内容转换为变量,然后使用 exec() 执行存储过程的定义,如下所示:

 SET @ProcDefinition_Complete=@uses_ansi_nulls_Definition + CHAR(10)+' GO '+ CHAR(10)+@uses_quoted_identifier_Definition+ CHAR(10)+' GO '+  CHAR(10)+ @ProcDefinition
        
        EXEC (@ReferencingDefinition_Complete)

上面的语句给出了错误:

 Incorrect syntax near 'GO'. 

当我删除 GO 语句时,它会给出错误:

'CREATE/ALTER PROCEDURE' must be the first statement in a query batch.
4

2 回答 2

3

你不能做你想做的事。不是通过使用游标迭代表中的行并将定义拉出到变量中以执行和更改ansi_nullsquoted_identifier根据每一行给出的值,因为游标执行必须在一批中进行。

为什么“必须住在一批”很重要?继续阅读。

set quoted_identifier on;
select @@options & 256; -- will print 256 if qi is on, 0 if it is off

这将打印256. 到现在为止还挺好。但是这个呢?

set quoted_identifier on;
print @@options & 256;
set quoted_identifier off;
print @@options & 256;

这会打印256然后0吗?没有。它打印0,然后0再打印。奇怪的!好的,让我们确保在自己的批处理quoted_identifieron运行它,然后尝试有条件地关闭它:

set quoted_identifier on;
go -- note this additional go
if (1 = 0) set quoted_identifier off;
print @@options & 256;

当我们开始第二批(之后的位go)时,quoted_identifier设置肯定是打开的。然后我们只关闭它if 1 = 0。由于 1 不等于 0,quoted_identifier因此应保持打开状态。所以我们希望打印256.

我们实际打印什么?0. 这是怎么回事?让我们检查一下文档

对于顶级 ad-hoc 批处理解析开始使用会话的当前设置 QUOTED_IDENTIFIER。在解析批处理时,任何出现的 SET QUOTED_IDENTIFIER 都会从那时起更改解析行为,并为会话保存该设置。所以批处理解析并执行后,会话的QUOTED_IDENTIFER设置将根据批处理中最后一次出现的SET QUOTED_IDENTIFIER进行设置。

(重点补充)

您不能有条件地更改quoted_identifier批次中的设置。似乎动态 SQL 也无法拯救你,因为你需要set quoted_identifier然后create procedure在同一个动态 sql 字符串中,而你不能,因为一个动态exec是一个批处理,并且create procedure必须是批处理中的第一个语句。

但是等等,还有更多。

declare @cmdOn varchar(max) = 'exec(''set quoted_identifier on; print @@options & 256;'')';
declare @cmdOff varchar(max) = 'exec(''set quoted_identifier off; print @@options & 256;'');';

declare @both varchar(max) = concat(@cmdOn, char(10), @cmdOff);
print @both;
exec (@both);

只是为了清楚发生了什么,这是它的输出:

exec('设置quoted_identifier on;打印@@options & 256;')
exec('设置quoted_identifier off;打印@@options & 256;');
256
0

所以……耶!我们已经成功地执行了一条动态 sql(的值@both),并且我们改变了quoted_identifier里面的设置!凉爽的。我们付出了什么代价?嵌套动态调用exec().

这能救我们吗?没有。

set quoted_identifier on; -- this is the only line that matters;

declare @qi varchar(max) = 'exec(''set quoted_identifier off;'')'; -- it makes no difference what you put here
declare @def varchar(max) = 'exec(''create or alter procedure p as begin set nocount on end;'');';

declare @both varchar(max) = concat(@qi, char(10), @def);
exec (@both);

select uses_quoted_identifier from sys.sql_modules where object_name(object_id) = 'p';

-- returns 1

嵌套调用对exec我们没有帮助,因为(来自文档):

对于使用 sp_executesql 或 exec() 的嵌套批处理,解析开始使用会话的 QUOTED_IDENTIFIER 设置。如果嵌套批处理位于存储过程中,则使用存储过程的 QUOTED_IDENTIFIER 设置开始解析。在解析嵌套批处理时,任何出现的 SET QUOTED_IDENTIFIER 都会从那时起更改解析行为,但不会更新会话的 QUOTED_IDENTIFIER 设置。

你能做些什么呢?

在过程创建批处理之外运行您的“光标” 。

例如,编写一个小程序(或 powershell 脚本)来读取备份定义以及所需的设置,然后将每个设置create procedure作为自己的命令执行。

或者读取备份的内容并将其全部转储到一个文件中,包括备份表中每一行的“go”语句。将文件内容作为脚本执行。

基于程序的解决方案的伪代码:

通过 ssms/whatever 备份 sql_modules
通过 ssms/whatever 删除程序
运行程序
   打开与 sql 的连接
   从备份表中读取定义、设置
   foreach(定义,设置)
      执行 sql 命令 ("set ansi_nulls ?; setquoted_identifier ?");
      执行sql命令(定义)
   紧密连接
退出程序
 
于 2021-09-03T11:47:28.857 回答
1

在动态语句之外定义设置。例如,如果您更改外部范围的ANSI_NULLS设置,动态语句的范围也将继承该设置。

例如:

SET ANSI_NULLS OFF;
SELECT @@OPTIONS & 32; --Returns 0
EXEC sys.sp_executesql N'SELECT @@OPTIONS & 32;'; --Returns 0
GO

SET ANSI_NULLS ON;
SELECT @@OPTIONS & 32; --Returns 32
EXEC sys.sp_executesql N'SELECT @@OPTIONS & 32;'; --Returns 32

由于后两个语句都返回32,这意味着ANSI_NULLS在两个语句中都启用了。同样,0意味着它在前面的语句中被禁用。

请注意,如果您在内部(动态)范围内更改设置,则此设置更改不会传播到外部范围:

SELECT @@OPTIONS & 32; --returns 32
EXEC sys.sp_executesql N'SET ANSI_NULLS OFF; SELECT @@OPTIONS & 32;'; --returns 0
SELECT @@OPTIONS & 32; --Returns 32

至于为什么GO会出错,那是因为它不是T-SQL 运算符,因此无法识别。GO是一个实用程序语句,被 IDE 和 CLI(例如 SSMS、ADS 和sqlcmd)识别为批处理分隔符。T-SQL 编译器无法识别它,因此不应将其包含在动态语句中。

于 2021-09-03T10:49:45.337 回答