Search code examples
t-sqlsqlcmd

unexpected behavior when using SET NOEXEC ON in a script called using sqlcmd


Below is an example of a simplified set of scripts that accurately reproduces an issue the exists in a more complex scripts being written for Prod.

When simulation.bat is run before the sandbox database exists, it works fine - the database is created along with the one populated table and one view. Here is the terminal output from that - enter image description here

However, after the initial execution of the batch file, subsequent executions of it cause a database error message to surface even though set NOEXEC on; was used in the if block. It appears to choke on the view creation because the table doesn't exist. While it makes sense that the table doesn't exist, why is it trying to create the view at all when set NOEXEC on has been set? How can the logic be modified to ensure that it does not try to create the view if the database already exists? enter image description here

4 files -
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;

Solution

  • At some point, it occurred to me that if the database existed (as indicated by the line printed to the terminal in the 2nd screenshot) then the dbo.Tally table should also exist. Based on that it didn't make sense that an invalid object name error message was appearing. The root cause: in Simulation.sql use $(db_name); came after the block of code that checks whether or not the database exists.

    Therefore, the solution was to rearrange the order of the statements at the beginning of the Simulation.sql script and add another existence check -

    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
    

    Terminal output when sandbox database does not exist yet: enter image description here

    Terminal output when sandbox database does exist: enter image description here