views:

58

answers:

3

I have a table that is used to store an incrementing numeric ID (of type INT). It contains a single row. The ID is incremented using a query:

UPDATE TOP(1) MyTable
WITH(TABLOCKX)
SET NextID = NextID + 1

I would like to move this into a stored procedure that returns the value that was in the NextID column before it was incremented, but am unsure how to do this using OUTPUT parameters. Any help would be appreciated.

+2  A: 

You might want to add in some error checking, etc. I also assumed that the table is a one-row table.

CREATE PROCEDURE dbo.Get_New_My_Table_ID
    @current_id INT = NULL OUTPUT    -- Declared OUTPUT parameter
AS
BEGIN
    BEGIN TRANSACTION

    SELECT
        @current_id = COALESCE(next_id, 0)
    FROM
        dbo.My_Table WITH (TABLOCK, HOLDLOCK)

    UPDATE dbo.My_Table
    SET next_id = @current_id + 1

    COMMIT TRANSACTION
END
GO

To use the stored procedure:

DECLARE @my_id INT

EXEC dbo.Get_New_My_Table_ID @current_id = @my_id OUTPUT
Tom H.
@Tom he wants the *before increment* value returned.
Martin Smith
Thanks Tom, that works for me. The idea of using an initial select before the update had occurred to me, but I wrongly thought that HOLDLOCK couldn't be used with TABLOCKX, so I couldn't see a way to keep the lock. Looks like it can. Also you've introduced me to the COALESCE statement, which I wasn't aware of!
Andy Johnson
@Martin True, but I can easily adapt his solution.
Andy Johnson
Edited to return the current value
Tom H.
far more complicated than it needs to be, you can do this in a single UPDATE statement, see my answer.
KM
@KM I've accepted your answer, but to me (as a someone who isn't a sql expert) Tom's answer was easier for me to understand.
Andy Johnson
+3  A: 

In SQL2005 or later the previous values for updated values can be retrieved using the OUTPUT clause.

Given that only one value will be returned I don't know how this compares with Tom's approach. I suspect that Tom's will be faster as there is an extra read but no overhead of creating a table variable. (Edit: and of course mine still has to read the table variable as well!)

CREATE PROC blah
@OldId int OUTPUT
AS

DECLARE @OldIds table( NextID int);


UPDATE TOP(1) MyTable
WITH(TABLOCKX)
SET NextID = NextID + 1
OUTPUT DELETED.NextID INTO @OldIds

SELECT @OldId = NextID
FROM @OldIds
Martin Smith
Thanks Martin. +1 for that. I decided to accept Tom's solution because it avoids the need for a temporary table. Seemed slightly simpler.
Andy Johnson
@Andy - Yep I agree. It's a shame that `@OldId = DELETED.NextID` doesn't appear to work.
Martin Smith
The `OUTPUT` clause is the way to go, but you ruined it by using the temp table. `OUTPUT` doesn't need to go into a table, it can be a result set, see my answer.
KM
@KM Was working off the requirement that it go into an OUTPUT parameter but I see that wasn't such a requirement in the end :-)
Martin Smith
+2  A: 

for SQL Server 2005+, try:

CREATE PROCEDURE dbo.Get_New_My_Table_ID
    @current_id INT = NULL OUTPUT    -- Declared OUTPUT parameter
AS
BEGIN TRANSACTION

UPDATE TOP(1) MyTable WITH(TABLOCKX)
    SET NextID = NextID + 1
    OUTPUT DELETED.NextID

COMMIT TRANSACTION

RETURN 0
GO

the results of OUTPUT don't need to go into an actual table, it can be a result set.

test it out:

declare @MyTable table (NextID int)
INSERT INTO @MyTable VALUES (1234)


SELECT 'BEFORE',* FROM @MyTable

PRINT '------------<<<<UPDATE>>>>---------'
UPDATE TOP(1) @MyTable
    SET NextID = NextID + 1
    OUTPUT DELETED.NextID
PRINT '------------<<<<UPDATE>>>>---------'

SELECT 'AFTER',* FROM @MyTable

OUTPUT:

(1 row(s) affected)
       NextID
------ -----------
BEFORE 1234

(1 row(s) affected)

------------<<<<UPDATE>>>>---------
NextID
-----------
1234

(1 row(s) affected)

------------<<<<UPDATE>>>>---------
      NextID
----- -----------
AFTER 1235

(1 row(s) affected)
KM
Thanks KM. That works for me. Since the query is simpler (and presumably more efficient) than Tom's, I've accepted your answer.
Andy Johnson