The error in the EXEC
part of the INSERT-EXEC
statement is leaving your transaction in a doomed state.
If you PRINT
out XACT_STATE()
in the CATCH
block it is set to -1
.
Not all errors will set the state to this. The following check constraint error goes through to the catch block and the INSERT
succeeds.
ALTER PROCEDURE test -- or create
AS
BEGIN try
DECLARE @retval INT;
DECLARE @t TABLE(x INT CHECK (x = 0))
INSERT INTO @t
VALUES (1)
SET @retval = 0;
SELECT @retval;
RETURN( @retval );
END try
BEGIN catch
PRINT XACT_STATE()
PRINT ERROR_MESSAGE();
SET @retval = -1;
SELECT @retval;
RETURN( @retval );
END catch;
Adding this to the CATCH
block
IF (XACT_STATE()) = -1
BEGIN
ROLLBACK TRANSACTION;
END;
Doesn't help. It gives the error
Cannot use the ROLLBACK statement within an INSERT-EXEC statement.
I don't think there is any way of recovering from such an error once it has happened. For your specific use case you don't need INSERT ... EXEC
anyway though. You can assign the return value to a scalar variable then insert that in a separate statement.
DECLARE @RC INT;
EXEC sp_executesql
N'EXEC @RC = test',
N'@RC INT OUTPUT',
@RC = @RC OUTPUT;
INSERT INTO @t
VALUES (@RC)
Or of course you could restructure the called stored procedure so that it doesn't raise that error at all.
DECLARE @RetVal INT = -1
IF OBJECT_ID('PrintMax', 'P') IS NULL
BEGIN
EXEC('create procedure PrintMax as begin print ''hello world'' end;')
SET @RetVal = 0
END
SELECT @RetVal;
RETURN( @RetVal );
I'd say the easiest possible way to do this is through PowerShell and SMO. Take the following code for instance:
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") |
Out-Null
$SqlServerNames = "Server1", "Server2"
$SqlJobName = "YourJob"
foreach ($SqlServerName in $SqlServerNames) {
$SqlServer = New-Object Microsoft.SqlServer.Management.Smo.Server($SqlServerName)
$SqlServer.JobServer.Jobs[$SqlJobName].Start()
}
All this does it loop through a list of servers (explicitly stated at as string array variable, but can easily be obtained also from a SQL Server database table, a text file, etc.), connects to the current server, and then starts the job with the job named whatever you set the $SqlJobName
variable to.
It's also worth noting that the Start()
function will not wait for the job to complete before continuing code execution.
As you can see, in very few lines of PowerShell code you are able to accomplish this task. But I'd take it a step further, such as error handling. You don't want this to bomb out and not know what failed, when it failed, and what servers it did/didn't run on.
foreach ($SqlServerName in $SqlServerNames) {
try {
$SqlServer = New-Object Microsoft.SqlServer.Management.Smo.Server($SqlServerName)
$SqlServer.JobServer.Jobs[$SqlJobName].Start()
}
catch {
# add your error handling code here
## for instance, write the error to a text file
}
}
And, as always with any code, test this out in a non-production environment to ensure that it does what you think it will and should do. Re-work it for your needs/environment.
As per RoKa's comment:
Can this be edited to check for whether the job is currently running, error handling?
Great point, and definitely something to check for (as well as if the job actually exists by checking for a non-null value):
foreach ($SqlServerName in $SqlServerNames) {
try {
$SqlServer = New-Object Microsoft.SqlServer.Management.Smo.Server($SqlServerName)
$SqlJob = $SqlServer.JobServer.Jobs[$SqlJobName]
if (($SqlJob) -and ($SqlJob.CurrentRunStatus -eq [Microsoft.SqlServer.Management.Smo.Agent.JobExecutionStatus]::Idle)) {
$SqlJob.Start()
}
else {
# handle/log accordingly if the job doesn't exist or it's not idle
}
}
catch {
# add your error handling code here
## for instance, write the error to a text file
}
}
EDIT: While re-reading your question, I see SQL Server 2000. Are those going to be the target instances? If that is the case, and you do decide to go with the PowerShell/SMO answer I provided, then definitely definitely definitely test and ensure it is going to do what you want. Test this out very far from production and be 100% sure it does what you think it will.
EDIT: It looks like SMO versions before SQL Server 2012 (i.e. 2005, 2008, and 2008 R2) support managing SQL Server 2000 instances. I'm finding this information through this BOL reference.
Best Answer
Three things worth mentioning:
Always use
PRINT
to view the resulting dynamic SQL whenever you work with dynamic SQL. You will see that the SQL variable is actually holdingNULL
.The reason because the dynamic SQL is
NULL
is because you are concatenating a NULL value which is the@variable1
contents. I believe you wanted to write down the text'@variable1'
as literal:@variable1
since it's not declare anywhere. If we execute the dynamic SQL:The way you can set variables values inside a dynamic execution and be able to read them from the outside is by supplying parameters via the
OUTPUT
option. This will require to use the SP sp_executesql rathen than a directEXEC
:Note that I changed data types to
NVARCHAR
sincesp_executesql
works with unicode inputs.Another example with more parameters:
If you don't have to read back results from variables, you can build your dynamic SQL by "hard-coding" the variables values directly into the script. Make sure to correctly use data type conversions inside the script and also escape
NULL
and literal values:Printed:
Result: