Reputation: 4122
I'm creating CRUD procedures that duplicate a legacy program that generates a unique ID based on a 'Next ID' field in a separate table. Rather than duplicate the use of a separate table I have written a stored procedure that reads the number of rows in the table.
CREATE PROCEDURE [TLA_CreateItem]
@SiteReference varchar(50)
,@ItemID varchar(4)
,@NewUniqueID varchar(68) OUTPUT
AS
BEGIN
DECLARE @Rows varchar(12)
SET @Rows = (CONVERT(varchar(12), (SELECT Count(UniqueID) FROM [TLA_Items]) + 1))
SET @NewUniqueID = @ItemID + @SiteReference + @Rows
INSERT INTO [TLA_Items] ([ItemID], [UniqueID])
VALUES (@ItemID, @NewUniqueID)
SELECT @NewUniqueID
END
I've simplified the code above but what's not shown is that the TLA_Items
table also has an IDENTITY
column and that it needs to work with SQL Server 2008.
The UniqueID field has to match the pattern of the legacy program: ItemID + SiteReference + (integer representing number of previous records)
However when testing this I've found a flaw in my logic. If rows are deleted then it's possible to create a unique Id which matches an existing row. This doesn't happen in the legacy system as rows are rarely deleted and the separate table stores the next number in the sequence.
Other than store the next ID value in a separate table, is there a better technique, to create a unique ID that matches the legacy pattern?
Upvotes: 2
Views: 3039
Reputation: 77677
Here is another option, but please bear in mind that it would affect existing UniqueID
values.
If you can afford a slight change to the table schema, you could add a column called something like UniqueIDPrefix
:
ALTER TABLE dbo.TLA_Items
ADD UniqueIDPrefix varchar(56) NOT NULL;
and redefine the UniqueID
column to be a computed column:
ALTER TABLE dbo.TLA_Items
DROP COLUMN UniqueID;
GO
ALTER TABLE dbo.TLA_Items
ADD UniqueID AS UniqueIDPrefix + CAST(IdentiyColumn AS varchar(12));
In your stored procedure, you would then need to populate UniqueIDPrefix
instead of UniqueID
(with just the result of @ItemID + @SiteReference
)
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueIDPrefix])
VALUES (@ItemID, @ItemID + @SiteReference);
and read the value of UniqueID
using either OUTPUT or SCOPE_IDENTITY(), as in my other answer.
Upvotes: 2
Reputation: 77677
You could have your procedure store only the prefix (@ItemID + @SiteReference
) into UniqueID
and use a FOR INSERT trigger to append the IDENTITY value as the rows component immediately after the row is inserted, something like this:
CREATE TRIGGER TLA_Items_Adjust
ON dbo.TLA_Items
FOR INSERT
AS
BEGIN
UPDATE t
SET t.UniqueID = i.UniqueID + CAST(t.IdentityColumn AS varchar(10))
FROM dbo.TLA_Items AS t
INNER JOIN inserted AS i
ON t.IdentityColumn = i.IdentityColumn
;
END
To read and return the newly generated UniqueID
value as the OUTPUT parameter as well as a row, you could use a table variable and the OUTPUT clause in the INSERT statement, like this:
CREATE PROCEDURE [TLA_CreateItem]
@SiteReference varchar(50)
,@ItemID varchar(4)
,@NewUniqueID varchar(68) OUTPUT
AS
BEGIN
DECLARE @GeneratedUniqueID TABLE (UniqueID varchar(68));
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueID])
OUTPUT inserted.UniqueID INTO @GeneratedUniqueID (UniqueID)
VALUES (@ItemID, @ItemID + @SiteReference);
SELECT @NewUniqueID = UniqueID FROM @GeneratedUniqueID;
SELECT @NewUniqueID;
END
Although instead of using OUTPUT you could probably just read the value from the row matching the SCOPE_IDENTITY() result:
CREATE PROCEDURE [TLA_CreateItem]
@SiteReference varchar(50)
,@ItemID varchar(4)
,@NewUniqueID varchar(68) OUTPUT
AS
BEGIN
INSERT INTO dbo.[TLA_Items] ([ItemID], [UniqueID])
VALUES (@ItemID, @ItemID + @SiteReference);
SELECT @NewUniqueID = UniqueID
FROM dbo.TLA_Items
WHERE IdentityColumn = SCOPE_IDENTITY();
SELECT @NewUniqueID;
END
Upvotes: 2
Reputation: 4477
It sounds like you are on SQL 2008, but if you were on 2012, you could use a sequence to store an incrementing value.
How about never delete? You could add a flag to the table for logical deletes.
Upvotes: 0