SQL Server stored procedure optimal query method? - sql-server

I have 5 meta tables that have the same format but depend on 5 other tables. Each Meta table looks like this:
CREATE TABLE [dbo].[SiteMetas]
(
[SiteMetaId] [bigint] IDENTITY(1,1) NOT NULL,
[SiteId] [bigint] NOT NULL,
FOREIGN KEY([SiteId]) REFERENCES [dbo].[Sites] ([SiteId]),
[MetaGroup] [nvarchar] (64) NOT NULL,
[MetaName] [nvarchar] (128) NOT NULL,
[MetaType] [char] NOT NULL DEFAULT 0, -- t, i, r, d, s, b
[MetaBool] [bit] DEFAULT NULL, -- t
[MetaInteger] [bigint] DEFAULT NULL, -- i
[MetaReal] [real] DEFAULT NULL, -- r
[MetaDateTime] [datetime] DEFAULT NULL, -- d
[MetaString] [nvarchar] (MAX) DEFAULT NULL, -- s
[MetaBinary] [varbinary] (MAX) DEFAULT NULL, -- b
[MetaCreated] [datetime] NOT NULL DEFAULT (GETUTCDATE()),
[MetaExpires] [datetime] DEFAULT NULL,
[MetaUpdated] [datetime] DEFAULT NULL,
PRIMARY KEY CLUSTERED ([SiteMetaId] ASC) WITH (IGNORE_DUP_KEY = ON),
UNIQUE NONCLUSTERED ([SiteId] ASC, [MetaGroup] ASC, [MetaName] ASC) WITH (IGNORE_DUP_KEY = ON)
);
This is for Site but there's 4 more. Like Users, ...
And I want to read the Binary meta value from Site. So wrote this stored procedure:
CREATE PROCEDURE [dbo].[GetSiteMetaBinary]
#SiteId AS bigint,
#Group AS nvarchar(64),
#Name AS nvarchar(128)
AS
BEGIN
SELECT TOP 1 [MetaBinary]
FROM [dbo].[SiteMetas]
WHERE [SiteId] = #SiteId
AND [MetaGroup] = #Group
AND [MetaName] = #Name
AND [MetaType] = 'b';
END;
This stored procedure has duplicates for User too... and the rest of the tables. That just replaces Site with User in its body.
But thinking that I have too many of these I wrote this one:
CREATE PROCEDURE [dbo].[GetMeta]
#Set AS nvarchar(64),
#Id AS bigint,
#Group AS nvarchar(64),
#Name AS nvarchar(128),
#Type AS nvarchar(16)
AS
BEGIN
DECLARE #Flag nchar(1);
DECLARE #Sql nvarchar(MAX);
SET #Flag = CASE #Type
WHEN 'Bool' THEN 't'
WHEN 'Integer' THEN 'i'
WHEN 'Real' THEN 'r'
WHEN 'DateTime' THEN 'd'
WHEN 'String' THEN 's'
WHEN 'Binary' THEN 'b'
ELSE NULL
END;
SET #Sql = N'SELECT TOP 1 [Meta' + #Type + N'] FROM [dbo].[' + #Set + N'Metas]' +
N'WHERE [' + #Set + N'Id] = #Id AND [MetaGroup] = #Group AND [MetaName] = #Name AND [MetaType] = #Flag;';
-- SELECT #Sql; -- DEBUG
EXEC sp_executesql #Sql,
N' #Id AS bigint, #Group AS nvarchar(64), #Name AS nvarchar(128), #Flag AS nchar(1)',
#Id, #Group, #Name, #Flag
;
END;
which is a general use stored procedure to read any data typed stored in a column based on input arguments. I use it like this [dbo].[GetMeta] 'Site', 1, 'group', 'name', 'Binary' the difference being that the actual query is dynamically generated so it's not known before hand by SQL Server like the first specialized variant.
Which of the two choices is better from a performance point of view and friendlier to SQL Server's internals? A dedicated one for each table and column data type of a general one that internally builds a query based on fed arguments.
I can use either. I like the last as it does not pollute my stored procedure space. :) The first one is more clear and SQL Server might be able to optimize it better. Not sure...
PS: I'm quite new to SQL Server

Static procedures are usually faster because the SQL engine can cache the compiled SP's execution plan.
However, unless this SP will be called a lot or is time-critical, it probably isn't worth worrying about it because the time savings of only having to maintain one SP will make up for the very small amount of time difference spent waiting for the SP to finish.

Related

Inserts into a SQL database table that has a varbinary(max) column can be slow

We have the following stored procedure:
ALTER procedure [dbo].[spMergePPObjectBlobProperty]
(
#values dbo.udtPPObjectBlobProperty readonly
)
as
begin
begin try
declare #updatetime datetime
set #updatetime = GetUTCDate()
merge tblPPObjectBlobProperty as t
using (select * from #values) as s
on t.InsertionId = s.InsertionId and t.PropertyMapNameId = s.PropertyMapNameId
when matched then
update set UpdateTime = #updatetime, [Value] = s.BlobValue, UpdateId = s.UpdateId
when not matched then
insert (PropertyMapNameId, Value, UpdateId, UpdateTime, InsertionId)
values(s.PropertyMapNameId, s.BlobValue, s.UpdateId, #updatetime, s.InsertionId)
option (loop join);
end try
begin catch
declare #errormessage varchar(256)
-- Get the error message
select #errormessage = ERROR_MESSAGE()
-- Raise an error and return
raiserror('Error updating entries in the tblPPObjectBlobProperty Table. %s', 16, 1, #errormessage)
end catch
end
Occasionally, this sp can take a few seconds to run with only several rows to insert. Other times it is extremely fast.
We have a number of these sps and this is the only one that appears to be slow sometimes and it is the only one that inserts into a table with a varbinary(max) column.
The type used here is defined as follows:
CREATE TYPE [dbo].[udtPPObjectBlobProperty] AS TABLE(
[InsertionId] [bigint] NOT NULL,
[PropertyMapNameId] [int] NOT NULL,
[BlobValue] [varbinary](max) NULL,
[UpdateId] [bigint] NULL,
PRIMARY KEY CLUSTERED
(
[InsertionId] ASC,
[PropertyMapNameId] ASC
)WITH (IGNORE_DUP_KEY = OFF)
)
GO
Can this stored procedure be optimised in any way?

SQL Server : how to create tables in stored procedures?

Task: write a SQL script to build a table. Call the script spCreateTable(), it should accept one argument, a varchar called subjectOfTable.
subjectOfTable is a varchar containing the subject of the table. Some examples are example “Employee” or “Formula.” Your script will use that subject to build the table name, primary key, and natural key.
For example, given “Employee” as the subject, your script will build a table called tEmployee with a surrogate key called EmployeeID and a natural key called Employee. Be sure to create the necessary constraints for the keys.
CREATE PROCEDURE [dbo].[sp_CreateTable]
#subjectOfTable VARCHAR(30)
AS
BEGIN
IF EXISTS (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID (#tabName) AND type in (N'U'))
DROP TABLE [dbo].[#tabName]
CREATE TABLE #tabName
(
[ID] [INT] IDENTITY(1,1) NOT NULL,
[RankID] [INT] NOT NULL,
[SlotTime] [NVARCHAR](10) NOT NULL,
[SlotDate] [NVARCHAR](30) NOT NULL
) ON [PRIMARY]
END
This is what I have so far, any help would be great. Thanks!
You can create the table with a generic name, and then use the sp_rename stored procedure to rename it to the variable name:
CREATE TABLE IF EXISTS sp_CreateTable_TempTable (
[ID] [int] IDENTITY(1,1) NOT NULL,
[RankID] [int] NOT NULL,
[SlotTime] [nvarchar](10) NOT NULL,
[SlotDate] [nvarchar](30) NOT NULL
) ON [PRIMARY]
GO
EXEC sp_rename sp_CreateTable_TempTable, #subjectOfTable;
GO
Since I cannot comment, I'll just throw it out here
What's the real usage of this code anyway? to create table often but same with columns?
I don't know how other person will run this (in SSMS? in application?)
FYI SSMS also has Template for create table (Ctrl+Alt+T), then he/she can press Ctrl+Shift+M to fill in the parameters every time.
If the columns are fixed, you can customize first and send it to them so they only change the part
-- =========================================
-- Create table template
-- =========================================
USE <database, sysname, AdventureWorks>
GO
IF OBJECT_ID('<schema_name, sysname, dbo>.<table_name, sysname, sample_table>', 'U') IS NOT NULL
DROP TABLE <schema_name, sysname, dbo>.<table_name, sysname, sample_table>
GO
CREATE TABLE <schema_name, sysname, dbo>.<table_name, sysname, sample_table>
(
<columns_in_primary_key, , c1> <column1_datatype, , int> <column1_nullability,, NOT NULL>,
<column2_name, sysname, c2> <column2_datatype, , char(10)> <column2_nullability,, NULL>,
<column3_name, sysname, c3> <column3_datatype, , datetime> <column3_nullability,, NULL>,
CONSTRAINT <contraint_name, sysname, PK_sample_table> PRIMARY KEY (<columns_in_primary_key, , c1>)
)
GO
first: prefixing a stored procedure should be usually avoided
you can use dynamic sql like that: (but better read that before)
BEGIN TRAN;
GO
CREATE PROCEDURE [dbo].[CreateTable]
( #Tabname SYSNAME
, #SchemaName NVARCHAR(128) = NULL
) AS
BEGIN;
SET #SchemaName = ISNULL (#SchemaName, N'dbo');
IF ( SELECT object_id (#SchemaName + N'.'+#tabname ) ) IS NOT NULL
EXEC ( N'DROP TABLE ' + #SchemaName + N'.'+#tabname)
EXEC (N'CREATE TABLE ' + #SchemaName + N'.'+#tabname + N'
( [ID] [INT] IDENTITY(1,1) NOT NULL,
[RankID] [INT] NOT NULL,
[SlotTime] TIME NOT NULL,
[SlotDate] DATE NOT NULL
) ON [PRIMARY]'
);
RETURN 0;
END;
GO
SELECT OBJECT_ID (N'dbo.test') AS OBJECT_ID
EXEC [dbo].[CreateTable] #tabname = test
SELECT OBJECT_ID (N'dbo.test') AS OBJECT_ID
ROLLBACK

sql server stored procedure check if exists table in other database and rename it

Have 2 databases: MAIN and IP2LOCATION
in MAIN, I have the following stored procedure:
CREATE PROCEDURE dbo.Update_IP2Location_DB11_from_CSV
AS
BEGIN
IF NOT EXISTS (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID(N'[ip2location].[dbo].[db11_new]') AND type in (N'U'))
BEGIN
CREATE TABLE [ip2location].[dbo].[db11_new]
(
[ip_from] bigint NOT NULL,
[ip_to] bigint NOT NULL,
[country_code] nvarchar(2) NOT NULL,
[country_name] nvarchar(64) NOT NULL,
[region_name] nvarchar(128) NOT NULL,
[city_name] nvarchar(128) NOT NULL,
[latitude] float NOT NULL,
[longitude] float NOT NULL,
[zip_code] nvarchar(30) NOT NULL,
[time_zone] nvarchar(8) NOT NULL,
) ON [PRIMARY]
CREATE INDEX [ip_from] ON [ip2location].[dbo].[db11_new]([ip_from])
END
ELSE
BEGIN
DELETE FROM [ip2location].[dbo].[db11_new]
END
BULK INSERT [ip2location].[dbo].[db11_new]
FROM 'D:\IP2LOCATION-LITE-DB11.CSV'
WITH
( FORMATFILE = 'C:\inetpub\wwwroot\ws\DB11_ip4.FMT')
EXEC sp_rename N'dbo.db11', N'db11_old', 'OBJECT'
EXEC sp_rename N'ip2location.dbo.db11_new', N'db11', 'OBJECT'
END
that does not work properly:
if db11_new does not exists, it (correctly) creates it, but if it exists.. I get
There is already an object named 'db11_new' in the database.
therefore it seems there is something wrong in
IF NOT EXISTS (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID(N'[ip2location].[dbo].[db11_new]') AND type in (N'U'))
and also at the end of procedure with the 2 Rename I get (always) the following answer
Msg 15248, Level 11, State 1, Procedure sp_rename, Line 359
Either the parameter #objname is ambiguous or the claimed #objtype (OBJECT) is wrong.
it seems problem is because the sproc is not stored into ip2location DB but in another database..
can suggest a solution, considering that I would prefer to keep all sprocs in MAIN DB, since have all other there?
Thanks
therefore it seems there is something wrong in
IF NOT EXISTS (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID(N'[ip2location].[dbo].[db11_new]') AND type in (N'U'))
Your analysis is correct. The sys.objects catalog view will return objects in the current database context (MAIN). Although you could just use a 3-part name (ip2location.sys.objects), I suggest you simply check for a NULL OBJECT_ID function result:
IF OBJECT_ID(N'[ip2location].[dbo].[db11_new]', 'U') IS NULL
BEGIN
CREATE TABLE [ip2location].[dbo].[db11_new]
(
[ip_from] bigint NOT NULL,
[ip_to] bigint NOT NULL,
[country_code] nvarchar(2) NOT NULL,
[country_name] nvarchar(64) NOT NULL,
[region_name] nvarchar(128) NOT NULL,
[city_name] nvarchar(128) NOT NULL,
[latitude] float NOT NULL,
[longitude] float NOT NULL,
[zip_code] nvarchar(30) NOT NULL,
[time_zone] nvarchar(8) NOT NULL,
) ON [PRIMARY];
CREATE INDEX [ip_from] ON [ip2location].[dbo].[db11_new]([ip_from]);
END;
ELSE
BEGIN
DELETE FROM [ip2location].[dbo].[db11_new];
END;
sys.objects and sp_rename are local objects.
Try to use this:
IF NOT EXISTS (SELECT * FROM ip2location.sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[db11_new]') AND type in (N'U'))
and
EXEC ip2location.sp_rename N'dbo.db11_new', N'db11', 'OBJECT'
Maybe it helps...
Alternatively, when you wanna do things in another database than the current one, you can write you code in dynamic sql and then execute it directly in the other database.
https://msdn.microsoft.com/en-us/library/ms188001.aspx
I have tested this query (without csv upload)
At first I remove every reference to ip2location:
CREATE PROCEDURE dbo.Update_IP2Location_DB11_from_CSV
AS
BEGIN
IF NOT EXISTS (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID(N'dbo.db11_new') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[db11_new]
(
[ip_from] bigint NOT NULL,
[ip_to] bigint NOT NULL,
[country_code] nvarchar(2) NOT NULL,
[country_name] nvarchar(64) NOT NULL,
[region_name] nvarchar(128) NOT NULL,
[city_name] nvarchar(128) NOT NULL,
[latitude] float NOT NULL,
[longitude] float NOT NULL,
[zip_code] nvarchar(30) NOT NULL,
[time_zone] nvarchar(8) NOT NULL,
) ON [PRIMARY]
CREATE INDEX [ip_from] ON [dbo].[db11_new]([ip_from])
END
ELSE
BEGIN
DELETE FROM [dbo].[db11_new]
END
BULK INSERT [dbo].[db11_new]
FROM 'D:\IP2LOCATION-LITE-DB11.CSV'
WITH
( FORMATFILE = 'C:\inetpub\wwwroot\ws\DB11_ip4.FMT')
EXEC sp_rename N'dbo.db11', N'db11_old', 'OBJECT'
EXEC sp_rename N'dbo.db11_new', N'db11', 'OBJECT'
END
GO
First run:
I have no db11* tables. Execution brings me:
Msg 15248, Level 11, State 1, Procedure sp_rename, Line 401 [Batch
Start Line 2] Either the parameter #objname is ambiguous or the
claimed #objtype (OBJECT) is wrong. Caution: Changing any part of an
object name could break scripts and stored procedures.
That means that db11_new was created, and than renamed in db11, but db11_old wasn't found, so I got this error. I get db11 table in my DB.
Second run:
Caution: Changing any part of an object name could break scripts and
stored procedures. Caution: Changing any part of an object name could
break scripts and stored procedures.
That means all was created and renamed.
Third run:
Msg 15335, Level 11, State 1, Procedure sp_rename, Line 509 [Batch
Start Line 2] Error: The new name 'db11_old' is already in use as a
OBJECT name and would cause a duplicate that is not permitted. Msg
15335, Level 11, State 1, Procedure sp_rename, Line 509 [Batch Start
Line 2] Error: The new name 'db11' is already in use as a OBJECT name
and would cause a duplicate that is not permitted.
So every next re-run You will get this same errors.
My suggestion is to do something about db11_old.
Thanks to Reboon and Dan Guzman here the solution, little improved:
CREATE PROCEDURE dbo.spA_Update_IP2Location_DB11_from_CSV
AS
BEGIN
IF OBJECT_ID(N'[ip2location].[dbo].[db11_new]', 'U') IS NULL
BEGIN
CREATE TABLE [ip2location].[dbo].[db11_new](
[ip_from] bigint NOT NULL,
[ip_to] bigint NOT NULL,
[country_code] nvarchar(2) NOT NULL,
[country_name] nvarchar(64) NOT NULL,
[region_name] nvarchar(128) NOT NULL,
[city_name] nvarchar(128) NOT NULL,
[latitude] float NOT NULL,
[longitude] float NOT NULL,
[zip_code] nvarchar(30) NOT NULL,
[time_zone] nvarchar(8) NOT NULL,
) ON [PRIMARY]
CREATE INDEX [ip_from] ON [ip2location].[dbo].[db11_new]([ip_from]) ON [PRIMARY]
END
ELSE
BEGIN
delete from [ip2location].[dbo].[db11_new]
END
BULK INSERT [ip2location].[dbo].[db11_new]
FROM 'D:\IP2LOCATION-LITE-DB11.CSV'
WITH
( FORMATFILE = 'C:\inetpub\wwwroot\ws\DB11_ip4.FMT' )
BEGIN TRANSACTION
EXEC ip2location.dbo.sp_rename N'dbo.db11', N'db11_old'
EXEC ip2location.dbo.sp_rename N'dbo.db11_new', N'db11'
IF OBJECT_ID(N'[ip2location].[dbo].[db11_old]', 'U') IS NOT NULL
BEGIN
DROP TABLE ip2location.dbo.db11_old
END
COMMIT TRANSACTION
END

How can I avoid or minimize deadlocks in this situation?

I have a relatively small table (for now). It works as a fancy queue. Jobs that execute every /second/, ask this table for more work and whenever work completes, they tell the table that work is completed.
Table has ~1000 entries or so entries and long-term will hopefully have 100k+ rows
Each entry signifies a job that needs to be executed once per minute. Table is hosted in SQL Azure (S2 plan)
Job Starter executes a stored proc that requests work from this table. Basically, the proc looks at the table, sees which tasks are not in progress and are overdue, marks them as "in progress" and returns them to job starter.
When task completes, a simple update is executed to tell that task completed and will be available for another cycle of work in a minute (field called Frequency controls this)
PROBLEM: I get deadlocks quiet frequently when asking this table for more work, or trying to mark entries as completed. Looks like ROWLOCK hint is not working. Do I need an indexing structure on this table?
Here is a Stored Procedure that retrieves records (usually up to 20 at a time, governed by #count parameter
CREATE PROCEDURE [dbo].[sp_GetScheduledItems]
#activity NVARCHAR (50), #count INT, #timeout INT=300, #dataCenter NVARCHAR (50)
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
DECLARE #batchId uniqueidentifier
SELECT #batchId = NEWID()
DECLARE #result int;
DECLARE #process nvarchar(255);
BEGIN TRAN
-- Update rows
UPDATE Schedule
WITH (ROWLOCK)
SET
LastBatchId = #batchId,
LastStartedProcessingId = NEWID(),
LastStartedProcessingTime = GETUTCDATE()
WHERE
ActivityType = #activity AND
IsEnabled = 1 AND
ItemId IN (
SELECT TOP (#count) ItemId
FROM Schedule
WHERE
(LastStartedProcessingId = LastCompletedProcessingId OR LastCompletedProcessingId IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > #timeout) AND
IsEnabled = 1 AND ActivityType = #activity AND DataCenter = #dataCenter AND
(LastStartedProcessingTime IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > Frequency)
ORDER BY (DATEDIFF(SECOND, ISNULL(LastStartedProcessingTime, '1/1/2000'), GETUTCDATE()) - Frequency) DESC
)
COMMIT TRAN
-- Return the updated rows
SELECT ItemId, ParentItemId, ItemName, ParentItemName, DataCenter, LastStartedProcessingId, Frequency, LastProcessTime, ActivityType
FROM Schedule
WHERE LastBatchId = #batchId
END
GO
Here is a stored procedure that marks entries as completed (it does so one-at-a-time)
CREATE PROCEDURE [dbo].[sp_CompleteScheduledItem]
#activity NVARCHAR (50), #itemId UNIQUEIDENTIFIER, #processingId UNIQUEIDENTIFIER, #status NVARCHAR (50), #lastProcessTime DATETIME, #dataCenter NVARCHAR (50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE Schedule WITH (ROWLOCK)
SET
LastCompletedProcessingId = LastStartedProcessingId,
LastCompletedProcessingTime = GETUTCDATE(),
LastCompletedStatus = #status,
LastProcessTime = #lastProcessTime
WHERE
ItemId = #itemId AND
LastStartedProcessingId = #processingId AND
DataCenter = #dataCenter AND
ActivityType = #activity
END
GO
Here is the table itself
CREATE TABLE [dbo].[Schedule](
[ItemId] [uniqueidentifier] NOT NULL,
[ParentItemId] [uniqueidentifier] NOT NULL,
[ActivityType] [nvarchar](50) NOT NULL,
[Frequency] [int] NOT NULL,
[LastBatchId] [uniqueidentifier] NULL,
[LastStartedProcessingId] [uniqueidentifier] NULL,
[LastStartedProcessingTime] [datetime] NULL,
[LastCompletedProcessingId] [uniqueidentifier] NULL,
[LastCompletedProcessingTime] [datetime] NULL,
[LastCompletedStatus] [nvarchar](50) NULL,
[IsEnabled] [bit] NOT NULL,
[LastProcessTime] [datetime] NULL,
[DataCenter] [nvarchar](50) NOT NULL,
[ItemName] [nvarchar](255) NOT NULL,
[ParentItemName] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_Schedule] PRIMARY KEY CLUSTERED
(
[DataCenter] ASC,
[ItemId] ASC,
[ActivityType] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
This is a good question :-) As usual you can do many things but in your case I think we can simplify your query quite a bit. Note that the suggestion below doesn't use SERIALIZABLE isolation level which in your case in all likelihood is causing table level locks to prevent phantom read from occurring (and also makes all write access to your table, well, serialized. You also don't actually need to specify BEGIN & COMMIT TRAN as you are only issuing one statement within your transaction (although it doesn't hurt either in your case). In this example we leverage the fact that we can actually issue your update directly against the sub query (in this case in the shape of a CTE) and we can also remove your last SELECT as we can return the result set directly from the UPDATE statement.
HTH,
-Tobias
SQL Server Team
CREATE PROCEDURE [dbo].[sp_GetScheduledItems]
#activity NVARCHAR (50), #count INT, #timeout INT=300, #dataCenter NVARCHAR (50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #batchId uniqueidentifier
SELECT #batchId = NEWID()
DECLARE #result int;
DECLARE #process nvarchar(255);
-- Update rows
WITH a AS (
SELECT TOP (#count)
*
FROM Schedule
WHERE
(LastStartedProcessingId = LastCompletedProcessingId OR LastCompletedProcessingId IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > #timeout) AND
IsEnabled = 1 AND ActivityType = #activity AND DataCenter = #dataCenter AND
(LastStartedProcessingTime IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > Frequency)
ORDER BY (DATEDIFF(SECOND, ISNULL(LastStartedProcessingTime, '1/1/2000'), GETUTCDATE()) - Frequency) DESC
)
UPDATE a SET
LastBatchId = #batchId,
LastStartedProcessingId = NEWID(),
LastStartedProcessingTime = GETUTCDATE()
OUTPUT INSERTED.ItemId, INSERTED.ParentItemId, INSERTED.ItemName, INSERTED.ParentItemName, INSERTED.DataCenter, INSERTED.LastStartedProcessingId, INSERTED.Frequency, INSERTED.LastProcessTime, INSERTED.ActivityType
END

TRY/CATCH does not work on SQL Server Agent error?

I use sp_start_job to start a job.
The job (test2) has only one step:
select getdate()
waitfor delay '00:00:10'
The TRY/CATCH code:
begin try
EXEC msdb.dbo.sp_start_job #job_name = 'test2'
end try
begin catch
print 'error'
end catch
First run of the code:
Job 'test2' started successfully.
Second run of the code (within 10 seconds):
Msg 22022, Level 16, State 1, Line 0SQLServerAgent Error: Request to run job test2 (from User sa) refused because the job is already running from a request by User sa.
Why does TRY/CATCH not work in this scenario?
UPDATE:
I should added at first that I am working on a sql server 2005 which has linked servers (sql server 2000). I was trying to write a proc on the sql server 2005 server to see a job on all the linked servers. If the job is not running, run it. Initially, I used try - catch and hoped to catch any error when run the already running job but failed (this thread).
I finally used following code: (it won't compile, you need to substitute some variables, just gives an idea)
CREATE TABLE [dbo].[#jobInfo](
[job_id] [uniqueidentifier] NULL,
[originating_server] [nvarchar](30) ,
[name] [nvarchar](128) ,
[enabled] [tinyint] NULL,
[description] [nvarchar](512) ,
[start_step_id] [int] NULL,
[category] [nvarchar](128) ,
[owner] [nvarchar](128) ,
[notify_level_eventlog] [int] NULL,
[notify_level_email] [int] NULL,
[notify_level_netsend] [int] NULL,
[notify_level_page] [int] NULL,
[notify_email_operator] [nvarchar](128) ,
[notify_netsend_operator] [nvarchar](128) ,
[notify_page_operator] [nvarchar](128) ,
[delete_level] [int] NULL,
[date_created] [datetime] NULL,
[date_modified] [datetime] NULL,
[version_number] [int] NULL,
[last_run_date] [int] NOT NULL,
[last_run_time] [int] NOT NULL,
[last_run_outcome] [int] NOT NULL,
[next_run_date] [int] NOT NULL,
[next_run_time] [int] NOT NULL,
[next_run_schedule_id] [int] NOT NULL,
[current_execution_status] [int] NOT NULL,
[current_execution_step] [nvarchar](128) ,
[current_retry_attempt] [int] NOT NULL,
[has_step] [int] NULL,
[has_schedule] [int] NULL,
[has_target] [int] NULL,
[type] [int] NOT NULL
)
SET #sql =
'INSERT INTO #jobInfo
SELECT * FROM OPENQUERY( [' + #srvName + '],''set fmtonly off exec msdb.dbo.sp_help_job'')'
EXEC(#sql)
IF EXISTS (select * from #jobInfo WHERE [name] = #jobName AND current_execution_status IN (4,5)) -- 4: idle, 5: suspended
BEGIN
SET #sql = 'EXEC [' + #srvName + '].msdb.dbo.sp_start_job #job_name = ''' + #jobName + ''''
--print #sql
EXEC (#sql)
INSERT INTO #result (srvName ,status ) VALUES (#srvName, 'Job started.')
END ELSE BEGIN
INSERT INTO #result (srvName ,status ) VALUES (#srvName, 'Job is running already. No action taken.')
END
Not all errors can be caught by TRY/CATCH. In this case, sp_start_job actually calls external procedures, and these are outside the bounds of SQL Server's error handling. Or at least that's the story that they're sticking to:
http://connect.microsoft.com/SQLServer/feedback/details/362112/sp-start-job-error-handling
Also note that this is still a problem in SQL Server 2012 SP1 CU3. Please vote and comment if you want this bug fixed.
A tedious but viable workaround, which requires certain permissions and in this case assumes the job owner is sa:
DECLARE #x TABLE
(
a VARBINARY(32),b INT,c INT,d INT,e INT,f INT,g INT,h INT,i NVARCHAR(64),
Running BIT, -- the only important column
k INT,l INT,m INT
);
DECLARE #job_id UNIQUEIDENTIFIER;
SELECT #job_id = job_id FROM msdb.dbo.sysjobs WHERE name = N'test2';
INSERT #x EXEC master.dbo.xp_sqlagent_enum_jobs 1, N'sa', #job_id;
IF EXISTS (SELECT 1 FROM #x WHERE Running = 0)
BEGIN
EXEC msdb.dbo.sp_start_job #job_name = N'test2';
END
ELSE
BEGIN
PRINT 'error';
END
Even better might be:
DECLARE #job_id UNIQUEIDENTIFIER, #d DATETIME;
SELECT #job_id = job_id FROM msdb.dbo.sysjobs WHERE name = N'test2';
SELECT #d = stop_execution_date
FROM msdb.dbo.sysjobactivity WHERE job_id = #job_id;
IF #d IS NOT NULL
BEGIN
EXEC msdb.dbo.sp_start_job #job_name = N'test2';
END
ELSE
BEGIN
PRINT 'error';
END
In either case, it is still possible that the job has started between the check for its status and the call to start it, so this doesn't eliminate errors from sp_start_job altogether, but it makes them far less likely to occur.
You can use alerts to execute SQL Agent jobs. And you can use alerts' options to configure required response.
(this will help you to totally avoid error 22022 but you will have additional records in the errorlog)
To make an evergreen solution, we should also consider a job that has never run before.
This my be useful in a scenario where the SQL server is rebuilt and the jobs recreated.
To capture that scenario look at the last run requested date:
DECLARE #jobEnd DATETIME, #jobRun DATETIME
SELECT #jobEnd = sja.stop_execution_date , #jobRun = sja.run_requested_date
FROM msdb.dbo.sysjobactivity AS sja
INNER JOIN msdb.dbo.sysjobs AS sj
ON sja.job_id = sj.job_id
WHERE sj.name = 'PhoneListSyncMember'
IF (#jobEnd IS NOT NULL AND #jobRun IS NOT NULL)
OR (#jobEnd IS NULL AND #jobRun IS NULL) -- job is New and never run before
EXEC #iRet =msdb.dbo.sp_start_job #job_name='PhoneListSyncMember'
Here is how you can capture Job trigger failures.
Declare #returnstatus int
Exec #returnstatus msdb.dbo.sp_start_job 'Jobname'
if(#returnstaus = 1)
Print 'Success'
else
Print 'Failure'

Resources