Setup:
-- For the demo
USE tempdb;
GO
IF OBJECT_ID(N'dbo.WorkQueue', N'U')
IS NOT NULL
DROP TABLE dbo.WorkQueue;
IF OBJECT_ID(N'dbo.GetWorkItem', N'P')
IS NOT NULL
DROP PROCEDURE dbo.GetWorkItem;
IF OBJECT_ID(N'dbo.SetWorkItemComplete', N'P')
IS NOT NULL
DROP PROCEDURE dbo.SetWorkItemComplete;
-- Create table
CREATE TABLE dbo.WorkQueue
(
work_queue_id INTEGER IDENTITY(1,1) NOT NULL
PRIMARY KEY,
name VARCHAR(255) NOT NULL,
status_id TINYINT NOT NULL
CHECK (status_id IN (0, 1, 2)),
status_desc AS
ISNULL
(
CASE status_id
WHEN 0 THEN 'Unprocessed'
WHEN 1 THEN 'In progress'
WHEN 2 THEN 'Processed'
ELSE NULL
END,
'')
);
-- Add data to table
INSERT dbo.WorkQueue
(name, status_id)
SELECT 'A', 0 UNION ALL
SELECT 'B', 0 UNION ALL
SELECT 'C', 0 UNION ALL
SELECT 'D', 0 UNION ALL
SELECT 'E', 0 UNION ALL
SELECT 'F', 0 UNION ALL
SELECT 'G', 0 UNION ALL
SELECT 'H', 0 UNION ALL
SELECT 'I', 0 UNION ALL
SELECT 'J', 0 UNION ALL
SELECT 'K', 0;
GO
-- Index to help find unprocessed items
CREATE NONCLUSTERED INDEX nc1
ON dbo.WorkQueue (status_id);
Procedures:
CREATE PROCEDURE dbo.GetWorkItem
@WorkQueueID INTEGER OUTPUT
AS
BEGIN
SET NOCOUNT ON;
-- Allocate a work item and set it to in-progress
-- TODO: Add lock timeout and error handler
UPDATE Q
SET status_id = 1,
@WorkQueueID = work_queue_id
FROM (
SELECT TOP (1)
Q2.work_queue_id,
Q2.status_id
FROM dbo.WorkQueue Q2 WITH (UPDLOCK, READPAST, ROWLOCK)
WHERE Q2.status_id = 0
ORDER BY
Q2.work_queue_id ASC
) Q;
RETURN CASE WHEN @WorkQueueID IS NULL THEN 999 ELSE 0 END
END;
GO
CREATE PROCEDURE dbo.SetWorkItemComplete
@WorkQueueID INTEGER
AS
BEGIN
SET NOCOUNT ON;
-- Set the work item's status to complete
-- TODO: Add lock timeout and error handler
UPDATE Q
SET status_id = 2
FROM WorkQueue Q
WHERE Q.work_queue_id = @WorkQueueID
AND Q.status_id = 1;
RETURN CASE WHEN @@ROWCOUNT = 0 THEN 999 ELSE 0 END;
END;
GO
Test:
DECLARE @WorkItemID INTEGER,
@ReturnCode INTEGER;
-- Get a work item
EXECUTE @ReturnCode =
dbo.GetWorkItem @WorkItemID OUTPUT;
-- Show the record
SELECT rc = @ReturnCode,
*
FROM dbo.WorkQueue
WHERE work_queue_id = @WorkItemID;
-- Simulate processing delay
WAITFOR DELAY '00:00:03';
-- Set the work item to complete
EXECUTE @ReturnCode =
dbo.SetWorkItemComplete @WorkItemID;
-- Show the record
SELECT rc = @ReturnCode,
*
FROM dbo.WorkQueue
WHERE work_queue_id = @WorkItemID;
GO
DROP TABLE dbo.WorkQueue;
DROP PROCEDURE dbo.GetWorkItem;
DROP PROCEDURE dbo.SetWorkItemComplete;
Results:
rc work_queue_id name status_id status_desc
0 1 A 1 In progress
...
0 1 A 2 Processed
Allocation routine query plan: (compute scalars removed)
Paul White
SQLPerformance.com
SQLkiwi blog
@SQL_Kiwi