在使用 sqlcmd 调用的脚本中使用 SET NOEXEC ON 时出现意外行为
unexpected behavior when using SET NOEXEC ON in a script called using sqlcmd
下面是一组简化脚本的示例,它准确地再现了为 Prod 编写的更复杂脚本中存在的问题。
当 simulation.bat
在 sandbox
数据库存在之前 运行 时,它工作正常 - 数据库与填充的 table 和一个视图一起创建。这是终端输出 -
但是,在初始执行批处理文件后,即使在 if
块中使用了 set NOEXEC on;
,后续执行也会导致出现数据库错误消息。它似乎在视图创建时窒息,因为 table 不存在。虽然 table 不存在是有道理的,但为什么在 set NOEXEC on
已设置时它仍然试图创建视图?如果数据库已经存在,如何修改逻辑以确保它不会尝试创建视图?
4 个文件 -
simulation.bat
@echo off
sqlcmd -S TheServerName -E -d master -i .\Simulation.sql -v db_name=sandbox
pause
Simulation.sql
print 'database name $(db_name)';
--if database already exists then print a message and exit script, otherwise create database
if exists (select 1 from sys.databases where [name] = '$(db_name)')
begin
print '--- Database $(db_name) already exists - script execution aborted ---';
set NOEXEC on; --prevent execution of statements in this batch and batches that follow it
end;
if not exists (select 1 from sys.databases where [name] = '$(db_name)')
begin
create database $(db_name);
print '--- $(db_name) database created ---';
end;
go
use $(db_name);
go
:r .\Tally.sql
go
print 'Table creation is complete.';
go
:r .\vw_TallyRows.sql
go
print 'View creation is complete.';
go
set NOEXEC off; --allow execution of statements that follow
go
print 'Reached end of script.';
go
Tally.sql
create table Tally (n int not null primary key);
insert into Tally values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
vw_TallyRows.sql
create view vw_TallyRows as
select rows = count(1) from dbo.Tally;
在某些时候,我突然想到,如果数据库存在(如第二张屏幕截图中打印到终端的行所示),那么 dbo.Tally
table 也应该存在。基于此,出现无效的对象名称错误消息是没有意义的。根本原因:在 Simulation.sql use $(db_name);
之后 检查数据库是否存在的代码块
因此,解决方案是重新排列 Simulation.sql 脚本开头的语句顺序,并添加另一个存在性检查 -
print 'database name $(db_name)';
if not exists (select 1 from sys.databases where [name] = '$(db_name)')
begin
create database $(db_name);
print '--- $(db_name) database created ---';
end;
go
use $(db_name);
go
if exists (select 1 from sys.databases where [name] = '$(db_name)') and exists (select distinct 1 from sys.all_objects where is_ms_shipped = 0)
begin
print '--- Database $(db_name) already exists - script execution aborted ---';
set NOEXEC on; --prevent execution of statements in this batch and batches that follow it
end;
go
--no change to remaining logic in script
当sandbox
数据库还不存在时的终端输出:
sandbox
数据库存在时的终端输出:
下面是一组简化脚本的示例,它准确地再现了为 Prod 编写的更复杂脚本中存在的问题。
当 simulation.bat
在 sandbox
数据库存在之前 运行 时,它工作正常 - 数据库与填充的 table 和一个视图一起创建。这是终端输出 -
但是,在初始执行批处理文件后,即使在 if
块中使用了 set NOEXEC on;
,后续执行也会导致出现数据库错误消息。它似乎在视图创建时窒息,因为 table 不存在。虽然 table 不存在是有道理的,但为什么在 set NOEXEC on
已设置时它仍然试图创建视图?如果数据库已经存在,如何修改逻辑以确保它不会尝试创建视图?
4 个文件 -
simulation.bat
@echo off
sqlcmd -S TheServerName -E -d master -i .\Simulation.sql -v db_name=sandbox
pause
Simulation.sql
print 'database name $(db_name)';
--if database already exists then print a message and exit script, otherwise create database
if exists (select 1 from sys.databases where [name] = '$(db_name)')
begin
print '--- Database $(db_name) already exists - script execution aborted ---';
set NOEXEC on; --prevent execution of statements in this batch and batches that follow it
end;
if not exists (select 1 from sys.databases where [name] = '$(db_name)')
begin
create database $(db_name);
print '--- $(db_name) database created ---';
end;
go
use $(db_name);
go
:r .\Tally.sql
go
print 'Table creation is complete.';
go
:r .\vw_TallyRows.sql
go
print 'View creation is complete.';
go
set NOEXEC off; --allow execution of statements that follow
go
print 'Reached end of script.';
go
Tally.sql
create table Tally (n int not null primary key);
insert into Tally values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
vw_TallyRows.sql
create view vw_TallyRows as
select rows = count(1) from dbo.Tally;
在某些时候,我突然想到,如果数据库存在(如第二张屏幕截图中打印到终端的行所示),那么 dbo.Tally
table 也应该存在。基于此,出现无效的对象名称错误消息是没有意义的。根本原因:在 Simulation.sql use $(db_name);
之后 检查数据库是否存在的代码块
因此,解决方案是重新排列 Simulation.sql 脚本开头的语句顺序,并添加另一个存在性检查 -
print 'database name $(db_name)';
if not exists (select 1 from sys.databases where [name] = '$(db_name)')
begin
create database $(db_name);
print '--- $(db_name) database created ---';
end;
go
use $(db_name);
go
if exists (select 1 from sys.databases where [name] = '$(db_name)') and exists (select distinct 1 from sys.all_objects where is_ms_shipped = 0)
begin
print '--- Database $(db_name) already exists - script execution aborted ---';
set NOEXEC on; --prevent execution of statements in this batch and batches that follow it
end;
go
--no change to remaining logic in script
当sandbox
数据库还不存在时的终端输出:
sandbox
数据库存在时的终端输出: