Sunday, March 25, 2012

HOW: Auotincrement without using autoincrement

I have a master table which I have been updating in multiple queries to
create a new row. It has a primary key, call it WorkID. I can't make it
autoincrement. And it is more complicated because...
WorkID can also appear in two other tables, as references to the master
record, but I need to provide for the master record being gone, and as
such the records in the other two tables are orphaned. That is fine.
To create a new master record I select MAX(WorkID) unioned on all 3
tables, and increment the result, and this is my new WorkID. However,
even if in a transaction, this query work can result in duplicate WorkID
numbers when new masters are created in extremely rapid succession. I
see the flaw here. Don't think this approach can be salvaged when the
MAX and INSERT queries are done separately.
I have been racking my brain to find a way to deal with this WITHOUT
resorting to auto-increment on the master table. I can think of no
expression for an INSERT value which will take the MAX+1 of WorkID on
the three tables, but it seems there must be a way to do this in a
single statement. Something like:
INSERT WORK (WorkID) VALUES (SELECT MAX(WorkID)+1 FROM ... UNION ... )
Maybe I need a new single row, single column table that arbitrates new
WorkID values? Or maybe someone has a favorite trick?
Thx - Lee>> I have a master table .. <<
I have not heard that term since I moved from navigational DBMS to
RDBMS in thw 1970's. Let's get back to the basics of an RDBMS. Rows
are not records; fields are not columns; tables are not files. The
differences are VITAL!!!
Of course not! An exposed physical locator created inside the hardware
cannot be a relational key by definition.
Well, you need a new brain :) What is the natrual, relational key in
the SPECS YOU NEVER POSTED -- repeated SPECS YOU NEVER POSTED? I
assume that you are not soooooo ignorant that you want to have a
"magical one-size-fits-all" auto numbering.|||Wow.
----
Louis Davidson - http://spaces.msn.com/members/drsql/
SQL Server MVP
"--CELKO--" <jcelko212@.earthlink.net> wrote in message
news:1123641244.396767.117310@.g14g2000cwa.googlegroups.com...
> I have not heard that term since I moved from navigational DBMS to
> RDBMS in thw 1970's. Let's get back to the basics of an RDBMS. Rows
> are not records; fields are not columns; tables are not files. The
> differences are VITAL!!!
>
> Of course not! An exposed physical locator created inside the hardware
> cannot be a relational key by definition.
>
> Well, you need a new brain :) What is the natrual, relational key in
> the SPECS YOU NEVER POSTED -- repeated SPECS YOU NEVER POSTED? I
> assume that you are not soooooo ignorant that you want to have a
> "magical one-size-fits-all" auto numbering.
>|||On Tue, 09 Aug 2005 12:18:38 -0700, Lee Gillie wrote:

> I have been racking my brain to find a way to deal with this WITHOUT
> resorting to auto-increment on the master table. I can think of no
> expression for an INSERT value which will take the MAX+1 of WorkID on
> the three tables, but it seems there must be a way to do this in a
> single statement. Something like:
> INSERT WORK (WorkID) VALUES (SELECT MAX(WorkID)+1 FROM ... UNION ... )
> Maybe I need a new single row, single column table that arbitrates new
> WorkID values? Or maybe someone has a favorite trick?
> Thx - Lee
As Celko said, you could probably do well with a full redesign that uses a
natural key. Failing that, how about this:
INSERT WORK (WorkID)
SELECT 1+MAX(WorkID)
FROM (
SELECT MAX(WorkID) WorkID FROM WORK
UNION ALL
SELECT MAX(WorkID) WorkID FROM OtherTable1
UNION ALL
SELECT MAX(WorkID) WorkID FROM OtherTable2
)
A better way might be to keep your available unused workIDs in yet another
table. First gather all used WorkIDs in the table:
CREATE TABLE WorkIDs (WorkID int, Used bit)
INSERT INTO WorkIDs (WorkID, Used)
SELECT WorkID, 1 FROM WORK
UNION
SELECT WorkID, 1 FROM OtherTable1
UNION
SELECT WorkID, 1 FROM OtherTable2
Now add some new ones for future use:
DECLARE @.maxworkID int
DECLARE @.x int
SELECT @.maxworkID = MAX(WorkID) FROM WorkIDs
SET @.x = @.maxworkID
WHILE @.x < @.maxworkID + 10000
BEGIN
INSERT INTO WorkIDs (WorkID, Used) VALUES (@.x, 0)
SET @.x = @.x + 1
END
Now here's your procedure for creating a new workID:
BEGIN TRANSACTION
DECLARE @.newWorkID int
SELECT @.newWorkID = MIN(WorkID) FROM WorkIDs WHERE Used=0
INSERT INTO WORK (WorkID) VALUES (@.newWorkID)
UPDATE WorkIDs SET Used=1 WHERE WorkID=@.newWorkID
COMMIT TRANSACTION|||> BEGIN TRANSACTION
> DECLARE @.newWorkID int
> SELECT @.newWorkID = MIN(WorkID) FROM WorkIDs WHERE Used=0
> INSERT INTO WORK (WorkID) VALUES (@.newWorkID)
> UPDATE WorkIDs SET Used=1 WHERE WorkID=@.newWorkID
> COMMIT TRANSACTION
Actually this is the worst case scenario for a busy system unless you use
exclusive locks. Say two users start the select at the same time. Boom,
deadlock. They both have a shared read lock on the workId's, the both
insert into work and then the UPDATE tries to WorkIDs to the same value
requiring exclusive locks. (unless the Unique Key fails them in the Work
table.)

> INSERT WORK (WorkID)
> SELECT 1+MAX(WorkID)
> FROM (
> SELECT MAX(WorkID) WorkID FROM WORK
> UNION ALL
> SELECT MAX(WorkID) WorkID FROM OtherTable1
> UNION ALL
> SELECT MAX(WorkID) WorkID FROM OtherTable2
> )
This would have to single thread over ALL tables! The SELECT by default
would only take shared locks, so any two users could be getting the max
values at the same time.
This gets even worse if this transaction gets called in another transaction.
There is not a good way to do this without single threading.
----
Louis Davidson - http://spaces.msn.com/members/drsql/
SQL Server MVP
"Ross Presser" <rpresser@.NOSPAMgmail.com.invalid> wrote in message
news:85kyr6seugm6$.dlg@.rosspresser.dyndns.org...
> On Tue, 09 Aug 2005 12:18:38 -0700, Lee Gillie wrote:
>
> As Celko said, you could probably do well with a full redesign that uses a
> natural key. Failing that, how about this:
> INSERT WORK (WorkID)
> SELECT 1+MAX(WorkID)
> FROM (
> SELECT MAX(WorkID) WorkID FROM WORK
> UNION ALL
> SELECT MAX(WorkID) WorkID FROM OtherTable1
> UNION ALL
> SELECT MAX(WorkID) WorkID FROM OtherTable2
> )
> A better way might be to keep your available unused workIDs in yet another
> table. First gather all used WorkIDs in the table:
> CREATE TABLE WorkIDs (WorkID int, Used bit)
> INSERT INTO WorkIDs (WorkID, Used)
> SELECT WorkID, 1 FROM WORK
> UNION
> SELECT WorkID, 1 FROM OtherTable1
> UNION
> SELECT WorkID, 1 FROM OtherTable2
> Now add some new ones for future use:
> DECLARE @.maxworkID int
> DECLARE @.x int
> SELECT @.maxworkID = MAX(WorkID) FROM WorkIDs
> SET @.x = @.maxworkID
> WHILE @.x < @.maxworkID + 10000
> BEGIN
> INSERT INTO WorkIDs (WorkID, Used) VALUES (@.x, 0)
> SET @.x = @.x + 1
> END
> Now here's your procedure for creating a new workID:
> BEGIN TRANSACTION
> DECLARE @.newWorkID int
> SELECT @.newWorkID = MIN(WorkID) FROM WorkIDs WHERE Used=0
> INSERT INTO WORK (WorkID) VALUES (@.newWorkID)
> UPDATE WorkIDs SET Used=1 WHERE WorkID=@.newWorkID
> COMMIT TRANSACTION|||On Thu, 11 Aug 2005 21:39:02 -0500, Louis Davidson wrote:
[snip]

> This gets even worse if this transaction gets called in another transactio
n.
> There is not a good way to do this without single threading.
Thanks for your comments. So, is there any decent answer to the problem of
generating new WorkIDs without using IDENTITY?

No comments:

Post a Comment