TSQL - How to use GO inside of a BEGIN .. END block?
I had the same problem and finally managed to solve it using SET NOEXEC.
IF not whatever
BEGIN
SET NOEXEC ON;
END
ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever
SET NOEXEC OFF;
Use of Begin / End Blocks and the Go keyword in SQL Server?
GO is like the end of a script.
You could have multiple CREATE TABLE statements, separated by GO. It's a way of isolating one part of the script from another, but submitting it all in one block.
BEGIN and END are just like { and } in C/++/#, Java, etc.
They bound a logical block of code. I tend to use BEGIN and END at the start and end of a stored procedure, but it's not strictly necessary there. Where it IS necessary is for loops, and IF statements, etc, where you need more then one step...
IF EXISTS (SELECT * FROM my_table WHERE id = @id)
BEGIN
INSERT INTO Log SELECT @id, 'deleted'
DELETE my_table WHERE id = @id
END
Multiple statements inside one BEGIN ... END block
The column doesn't exist
error is due to validation that occurs on existing objects. Since the table already exists, the parser / compiler will verify that the table contains all of the referenced columns.
In order to get around such timing issues with object verification, you can enclose the statement in an EXEC
which will not be verified until run-time:
BEGIN
ALTER TABLE [dbo].[UserProfiles]
ADD [AllCheckboxesChecked] [bit]
CONSTRAINT [DF_UserProfiles_AllCheckboxesChecked] DEFAULT 0
NOT NULL;
EXEC(N'UPDATE [dbo].[UserProfiles]
SET [AllCheckboxesChecked]=1
WHERE [CheckedBoxes] LIKE ''%#ALL#%''');
END;
How to ignore the GO statements in IF EXISTS block?
As I mention in the comment, GO
is not a Transact-SQL operator, it's interpreted by your IDE/CLI as a batch separator.SQL Server Utilities Statements - GO:
SQL Server provides commands that are not Transact-SQL statements, but are recognized by the sqlcmd and osql utilities and SQL Server Management Studio Code Editor. These commands can be used to facilitate the readability and execution of batches and scripts.
GO signals the end of a batch of Transact-SQL statements to the SQL Server utilities.
The answer is simply, remove the GO
s. There is no need for them here; it's because they are there that you are getting the error because separating the statements into different batches makes no sense here. What you have would be equivalent to having the following 3 "files" and trying to run them independently:
File 1:
IF NOT EXISTS(SELECT 1 FROM [dbo].[UPGRADEHISTORY] WHERE SCRIPTNAME='001-MarkSubmitted.sql' AND RELEASENUMBER= '1')
BEGIN
IF NOT EXISTS(SELECT 1 FROM [dbo].[Action] WHERE Name='mark As Submitted')
BEGIN
SET IDENTITY_INSERT [dbo].[Action] ON
INSERT INTO [dbo].[Action](Id,Name,CreatedBy,CreatedOn) VALUES (6,'mark As Submitted',1,getdate())
SET IDENTITY_INSERT [dbo].[Action] OFF
END
Would error due to a BEGIN
with out END
.
File 2:
INSERT INTO [dbo].[StatusActionMapping](ArtifactType,StatusId,ActionId,RoleId) VALUES ('Report',11,6,1)
This would run fine.
File 3:
INSERT INTO [dbo].[UpgradeHistory] ([ReleaseNumber],[ScriptNumber],[ScriptName],[ExecutionDate]) VALUES (1, (SELECT FORMAT(MAX(SCRIPTNUMBER) + 1, 'd3') FROM UpgradeHistory WHERE ReleaseNumber= 1),'001-MarkSubmitted.sql',GETDATE());
END
Would error due to an END
without a BEGIN
.
There's nothing in your query that will causing a parsing error like you're adding a new column to an existing table and reference it later in the batch, so there's no need to separate the batches. Just remove the GO
s in the middle of your BEGIN...END
s and this works as you require:
IF NOT EXISTS (SELECT 1
FROM [dbo].[UPGRADEHISTORY]
WHERE SCRIPTNAME = '001-MarkSubmitted.sql'
AND RELEASENUMBER = '1')
BEGIN
IF NOT EXISTS (SELECT 1
FROM [dbo].[Action]
WHERE Name = 'mark As Submitted')
BEGIN
SET IDENTITY_INSERT [dbo].[Action] ON;
INSERT INTO [dbo].[Action] (Id, Name, CreatedBy, CreatedOn)
VALUES (6, 'mark As Submitted', 1, GETDATE());
SET IDENTITY_INSERT [dbo].[Action] OFF;
END;
INSERT INTO [dbo].[StatusActionMapping] (ArtifactType, StatusId, ActionId, RoleId)
VALUES ('Report', 11, 6, 1);
INSERT INTO [dbo].[UpgradeHistory] ([ReleaseNumber], [ScriptNumber], [ScriptName], [ExecutionDate])
VALUES (1, (SELECT FORMAT(MAX(SCRIPTNUMBER) + 1, 'd3') --This is a REALLY bad idea. Use an IDENTITY or SEQUENCE
FROM UpgradeHistory
WHERE ReleaseNumber = 1), '001-MarkSubmitted.sql', GETDATE());
END;
GO
Also note my point on your final INSERT
. FORMAT(MAX(SCRIPTNUMBER) + 1, 'd3')
is going to end up with race conditions. Use an IDENTITY
or SEQUENCE
.
TSQL: Executing a stored procedure inside a BEGIN...END block
From MSDN's EXECUTE
Article:
You do not have to specify the EXECUTE keyword when executing stored
procedures if the statement is the first one in a batch.
Therefore, your second example throws the error because the stored procedure call is not the first statement in the batch.
Many inserts inside a begin-end block
To counter the huge else block, you could use any one the the following 3 strategies.
STRATEGY 1
Use Bulk insert from SQL Server. Just dump all your data into a csv file and use the following statement in your else block. Store the csv file on your computer and give it's UNC path after FROM in statement below. This way you will only have a single line in your else block.
BULK INSERT dbo.MyTable
FROM '\\share\somepath\myTableInsertData.csv'
WITH (FORMAT = 'CSV');
STRATEGY 2
Write a stored procedure that inserts only x
rows at a time. You could then call this stored procedure in a While loop
and your else block would be very small. You would call this stored procedure repeatedly in a while loop from your original SQL and then the else block would end being just a few lines of t-sql code.
Note that you can control how many rows are inserted by the stored procedure at a time by using an appropriate value for the variable @numberOfRowsAtaTime
. I have used 1000 so in a single call of stored procedure 1000 rows get inserted.
Of course, based on your business rules you can script the stored procedure's insert statements. If you have a pattern in your INSERTS then you could script that pattern into the stored procedure logic below.
Stored Procedure
CREATE PROCEDURE dbo.insertXRows
@startIndex INT,
@numberOfRows INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @counter INT;
SET @counter = @startIndex;
WHILE @counter < (@startIndex + @numberOfRows -1)
BEGIN
-- Insert statements for rows goes here
--INSERT FOR @counter -- @counter would be different for each iteration
SET @counter = @counter + 1;
END
END
GO
Your SQL
IF EXISTS (SELECT * FROM [MyTable])
BEGIN
PRINT 'No need to insert data'
--Stop executing script
END
ELSE
BEGIN
declare @insertRowCounter int;
set @insertRowCounter = 1;
declare @numberOfRowsAtaTime int;
set @numberOfRowsAtaTime = 1000;
WHILE @insertRowCounter <= 150000
EXEC dbo.insertXRows @insertRowCounter, @numberOfRowsAtaTime -- insert 1000 rows at a time
SET @insertRowCounter = @insertRowCounter + @numberOfRowsAtaTime;
END
END
STRATEGY 3
Come up with 10 stored procedures so that each stored procedure has 15000 INSERTS. Then simply call these 10 stored procedures from your else block.
10 stored procedures
CREATE PROCEDURE dbo.insertProc1
AS
BEGIN
SET NOCOUNT ON;
--INSERT1
--INSERT2
--INSERT15000
END
END
GO
CREATE PROCEDURE dbo.insertProc2
AS
BEGIN
SET NOCOUNT ON;
--INSERT15001
--INSERT15002
--INSERT3000
END
END
GO
Your SQL
IF EXISTS (SELECT * FROM [MyTable])
BEGIN
PRINT 'No need to insert data'
--Stop executing script
END
ELSE
BEGIN
EXEC insertProc1
EXEC insertProc2
EXEC insertProc3
EXEC insertProc4
EXEC insertProc5
EXEC insertProc6
EXEC insertProc7
EXEC insertProc8
EXEC insertProc9
EXEC insertProc10
END
BEGIN...END block in SQL Server
BEGIN
/ END
delimit program statements.
Encloses a series of Transact-SQL statements so that a group of Transact-SQL statements can be executed.
CASE
blocks accept expressions.
Evaluates a list of conditions and returns one of multiple possible result expressions.
So you are trying to fit a square peg in a round hole.
Why can't I create a view inside of a BEGIN ... END block
It's because CREATE VIEW must be the first statement in a batch as described in this MSDN reference.
Instead, you could do:
e.g.
.....
BEGIN
EXECUTE('CREATE VIEW [dbo].[dummy] AS SELECT 1 AS Dummy')
END
can you have multiple GO's inside of an BEGIN TRY CATCH?
GO is a batch separator. The first GO inside a stored procedure ends the procedure's definition, IOW everything after the GO is not considered a part of the procedure. You cannot have a GO inside a procedure.
So, whatever you're trying to do, it's not the proper way.
Any harm in using BEGIN / END to organize SQL code?
You can do this. It's a good idea to structure code like that because it's integrated with the language and the IDE in a way that comments are not. In C languages I sometimes use {}
blocks for that in case it is necessary to have larger methods.
I found the col/line style easier to read for me
If that's easier for you then it makes sense. But maybe it also makes sense to train your eyes to accept a style where many columns are in one line. This saves a tremendous amount of vertical space. More fits on the same screen and clarity increases.
Related Topics
Difference Between Filtering Queries in Join and Where
How to Execute a Native SQL Script in JPA/Hibernate
Postgresql Where Count Condition
Getting "Comma-Separated List Near 'Xx.Yy' Invalid" with Dbms_Utility.Comma_To_Table
Access Substitute for Except Clause
How to Change Db Schema to Dbo
What Does a (+) Sign Mean in an Oracle SQL Where Clause
Prevent Duplicate Values in Left Join
Copy Data from One Column to Other Column (Which Is in a Different Table)
Oracle SQL: Update If Exists Else Insert
Execute a Stored Procedure in Another Stored Procedure in SQL Server
"Select Distinct" Ignores Different Cases
Ssis Best Practice to Load N Tables from Source to Target Server
SQL . the Sp or Function Should Calculate the Next Date for Friday