Home > Enterprise >  TSQL IF appears to randomly execute code from wrong logic branch
TSQL IF appears to randomly execute code from wrong logic branch

Time:07-06

I have an idempotent script that brings different database deployments up to the latest revision. The column referenced in the code is actually deleted in a future release, so in order to preserve the script's validity I check if the column exists before executing the statement. I noticed some unexpected behaviour whereby the IF logic branches seem to intermittently execute when the condition is false.

Basic example

IF (1 = 0)
    BEGIN
        PRINT 'Adding data'; -- Never printed
    END
GO

Testing the logic branch

IF (1 = 0)
    BEGIN
        PRINT 'Adding data';                       -- Never printed
        RAISERROR('One never equals zero', 16, 1); -- Never raised
    END
GO

Added data modification code

IF (1 = 0)
    BEGIN
        PRINT 'Adding data';                                            -- Never printed
        RAISERROR('One never equals zero', 16, 1);                      -- Never raised
        INSERT INTO [tbl_Users] ( NonExistentColumn ) VALUES ( 'Bar' ); -- Msg 207, Level 16, State 1, Line 6; Invalid column name 'NonExistentColumn'.
    END
GO

Why does this happen? Particularly, why are the PRINT and RAISERROR lines correctly ignored but the INSERT still executes, even though the entire logic branch should be ignored?

CodePudding user response:

None of your statements are run, it isn't skipping the PRINT and RAISERROR and going to the INSERT INTO, instead the entire batch fails due to a compilation error.

Despite some beliefs, T-SQL is parsed and validated before it is executed, and if the statement isn't valid then the entire batch won't be run; this is why when you have a syntax error, for example, none of the statements are run.

Here you have an invalid column reference, the column NonExistentColumn in the table [YourDefaultSchema].tbl_Users doesn't exist. Although SQL Server does have some deferred object validation, columns against objects that exist are not one of them. As such, when you ask for your batch to be executed, the parser checks to see table [YourDefaultSchema].tbl_Users has a column NonExistentColumn, and when it finds it doesn't, it raises an "Invalid Column Name" error.

If the table itself did not exist, then this error would not occur. For example, take the following batches:

CREATE TABLE dbo.SomeTable (SomeColumn int);
GO
PRINT N'First Batch Starts';    
IF 1 = 0 BEGIN
    PRINT N'Entered first IF.';
    INSERT INTO dbo.SomeTable (AnotherColumn)
    VALUES(1);
END;
PRINT N'First Batch Ends';    
GO
PRINT N'Second Batch Starts';    
IF 1 = 0 BEGIN
    PRINT N'Entered second IF.';
    INSERT INTO dbo.AnotherTable (AnotherColumn)
    VALUES(1);
END;
PRINT N'Second Batch Ends';
GO

DROP TABLE dbo.SomeTable;

In the messages pane of SSMS you will get the following:

Msg 207, Level 16, State 1, Line 6
Invalid column name 'AnotherColumn'.
Second Batch Starts
Second Batch Ends

Notice that none of the PRINT statements from the first batch print, as the batch isn't run, however, in the second batch they are, as the validation of the column is deferred till that statement would be run (and it's never run, so no error occurs).

If you wanted to reference a potentially non-existent column on an existing object, defer the statement's validation using sys.sp_executesql:

CREATE TABLE dbo.SomeTable (SomeColumn int);
GO
PRINT N'Third Batch Starts';    
IF 1 = 0 BEGIN
    PRINT N'Entered first IF.';
    EXEC sys.sp_executesql N'INSERT INTO dbo.SomeTable (AnotherColumn) VALUES(1);';
END;
PRINT N'Third Batch Ends';    
GO

DROP TABLE dbo.SomeTable;

This simply prints the following:

Third Batch Starts
Third Batch Ends

db<>fiddle

  • Related