Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2008 Forums
 Transact-SQL (2008)
 Help me rid myself of these meddlesome loops!

Author  Topic 

Curt Blood
Starting Member

23 Posts

Posted - 2012-07-16 : 16:34:29
First off, this isn't "urgent". I have a working solution, but my solution grates at me. Every time I do something like this, it's like fingernails on chalkboard.

The problem: The app I maintain the database for is all proprietary ad-hoc queries, so I cannot change the way the system does things, or even tune the queries.

For all tables, the system uses an ID field as the primary key (ASSETID, VENDORID, etc.) There is also a sequence table that holds the next available number for each table. When a new record is inserted, the system looks at the sequence table for the new ID and increments the sequence table.

A problem arises when a developer goes into SSMS and inserts a record or two into the table with a dummy ID. When the system gets to that ID, it throws up with a "primary key" error.

So, I have to go through the tables and update the sequence table with the max(ID) + 1 from the base table.

Here is the abbreviated DDL for the tables in question

Sequence table:

CREATE TABLE [dbo].[sequence](
[tbname] [varchar](60) NOT NULL,
[name] [varchar](60) NOT NULL,
[reserved] [bigint] NOT NULL,
[maxvalue] [bigint] NULL
) ON [PRIMARY]


Sample tables:

CREATE TABLE [dbo].[asset](
[assetnum] [varchar](60) NOT NULL,
[assetid] [bigint] NOT NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[vendor](
[vendornum] [varchar](60) NOT NULL,
[vendorid] [bigint] NOT NULL
) ON [PRIMARY]



Sample data:


INSERT INTO sequence(tbname, name, reserved, maxvalue)
SELECT asset, assetid, 50, 40 UNION ALL
SELECT vendor, vendorid, 50, 40

insert into asset (assetnum, assetid)
select 'Asset1', 38 union all
select 'Asset2', 39 union all
select 'Asset3', 40

insert into vendor (vendornum, vendorid)
select 'Vendor1', 38 union all
select 'Vendor2', 39 union all
select 'Vendor3', 40 union all
select 'Vendor4', 55



So as you can see, a developer went in (despite the decapatation threats) and added Vendor4 with a rogue vendorid. What I need to do is leave the "asset" record alone on the sequence table but update the "reserved" value on the "Vendor" record to 56.

What I have -- and this works fine, I would just love to learn a more elegant solution:


--create temp table
create table #sequence
(tbname varchar(50),
colname varchar(50),
reserved numeric,
value numeric default 0,
sql varchar(300),
seq numeric)

--populate basic info into temp table
insert into #sequence (tbname, colname, reserved) select tbname, name, reserved from sequence

--populate sequence numbers
declare @seq numeric = 0
update #sequence set @seq = seq = @seq + 1

--build preliminary dynamic sql
update #sequence set sql = 'select max(' + colname + ') from '+ tbname

--populate value column with max value from the tables
declare @count numeric = 0
declare @sqlstmt varchar(300)
declare @maxvalue numeric
while @count <= (select max(seq) from #sequence)
begin
set @sqlstmt = 'update #sequence set value = (' + (select SQL from #sequence where seq = @count) + ') where seq = ' + cast(@count as varchar(10))
exec (@sqlstmt)
set @count = @count + 1
end

--Build and execute dynamic sql to update sequence
declare @tblname varchar(50)
declare @colname varchar(50)
set @count = 1

delete from #sequence where value is null
delete from #sequence where value < reserved


while @count <= (select max(seq) from #sequence)
begin
set @tblname = (select tbname from #sequence where seq = @count)
set @colname = (select colname from #sequence where seq = @count)
set @maxvalue = (select value from #sequence where seq = @count) + 1

set @sqlstmt = 'update sequence set reserved = ' + cast(@maxvalue as varchar(10)) + ' where tbname = ''' + @tblname + ''' and name = ''' + @colname + ''''
exec (@sqlstmt)
set @count = @count + 1
end

drop table #sequence



If anyone can show me how to do this without the loops and dynamic SQL, I would bow deeply in your direction.

Thanks,
Curt

[/code]

visakh16
Very Important crosS Applying yaK Herder

52326 Posts

Posted - 2012-07-16 : 22:28:23
isnt it matter of putting an insert trigger for this on vendor ? something like

CREATE TRIGGER Trig_VendorCheck
ON Vendor
FOR INSERT
AS
BEGIN

DECLARE @MaxID int

SELECT @MaxID=MAX(vendorid)
FROM INSERTED


UPDATE Sequence
SET reserved = @MaxID +1
WHERE tbname = 'vendor'
AND name ='vendorid'
AND reserved < @MaxID

END


------------------------------------------------------------------------------------------------------
SQL Server MVP
http://visakhm.blogspot.com/

Go to Top of Page

Curt Blood
Starting Member

23 Posts

Posted - 2012-07-16 : 22:46:57
quote:
Originally posted by visakh16

isnt it matter of putting an insert trigger for this on vendor ? something like

CREATE TRIGGER Trig_VendorCheck
ON Vendor
FOR INSERT
AS
BEGIN

DECLARE @MaxID int

SELECT @MaxID=MAX(vendorid)
FROM INSERTED


UPDATE Sequence
SET reserved = @MaxID +1
WHERE tbname = 'vendor'
AND name ='vendorid'
AND reserved < @MaxID

END


------------------------------------------------------------------------------------------------------
SQL Server MVP
http://visakhm.blogspot.com/





That's a great idea, but I've made the problem seem a lot simpler than it is. There are 400+ tables, and I'd have to put a trigger on each of them. I was just using these two as examples.

Thanks, and if it seems to be on a few tables that it happens, I'll consider it.

Go to Top of Page

visakh16
Very Important crosS Applying yaK Herder

52326 Posts

Posted - 2012-07-17 : 09:55:11
so you want same rule to be applied for all 400 + tables?

------------------------------------------------------------------------------------------------------
SQL Server MVP
http://visakhm.blogspot.com/

Go to Top of Page

Curt Blood
Starting Member

23 Posts

Posted - 2012-07-17 : 10:05:51
quote:
Originally posted by visakh16

so you want same rule to be applied for all 400 + tables?

------------------------------------------------------------------------------------------------------
SQL Server MVP
http://visakhm.blogspot.com/





Essentially, yes. I'm looking for a way to check all the tables and update the sequence table for the ones that need updating.

What I have works, I'm just looking to expand my SQL knowledge and see if there's a way to do it set-based.
Go to Top of Page

Transact Charlie
Master Smack Fu Yak Hacker

3451 Posts

Posted - 2012-07-17 : 10:10:07
you could have your developers insert only values with negative numbers? That's assuming your ID seed starts at 0. That way you'd never encounter a PK conflict.

However,

Why wouldn't you use an IDENTITY auto incremental key here?

Transact Charlie
Msg 3903.. The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
http://nosqlsolution.blogspot.co.uk/
Go to Top of Page

Curt Blood
Starting Member

23 Posts

Posted - 2012-07-17 : 10:19:18
quote:
Originally posted by Transact Charlie

you could have your developers insert only values with negative numbers? That's assuming your ID seed starts at 0. That way you'd never encounter a PK conflict.



That's a very good idea. As well as stepping up the punishments.

quote:
Originally posted by Transact Charlie
However,

Why wouldn't you use an IDENTITY auto incremental key here?

Transact Charlie
Msg 3903.. The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
http://nosqlsolution.blogspot.co.uk/



I can't because I have no control over the structure of the base tables. All of it's proprietary. Which is frustrating as hell.

Thanks
Go to Top of Page

Transact Charlie
Master Smack Fu Yak Hacker

3451 Posts

Posted - 2012-07-17 : 10:33:11
harsh. I feel for you. Once worked on a set of tables made by hibernate that behaved in this way. Was a COMPLETE pain in the ass to work around.

Transact Charlie
Msg 3903.. The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
http://nosqlsolution.blogspot.co.uk/
Go to Top of Page

Curt Blood
Starting Member

23 Posts

Posted - 2012-07-17 : 10:48:09
If it weren't for Profiler, I wouldn't know what it was doing at all.

And they didn't have a DBA in the room when they were writing their queries. Some of them are pretty ugly.
Go to Top of Page

kmarshba
Starting Member

24 Posts

Posted - 2012-07-17 : 13:33:09
Hey Curt,

I hope this helps a bit. I didn't get rid of the dynamic sql but did get rid of any loops. I imagine this could be done even better but I only had a few minutes of time to play with it...back to my real job :-)

IF OBJECT_ID('TempDB.dbo.#seq', 'U') IS NOT NULL BEGIN
DROP TABLE dbo.#seq;
END;

CREATE TABLE dbo.#seq(tbname varchar(60), colname varchar(60), maxid bigint);

DECLARE @nsql nvarchar(max)='INSERT INTO #seq(tbname, colname, maxid)' + CHAR(13) + CHAR(10);

SELECT @nsql = @nsql + 'SELECT ''' + tbname + ''', ''' + name + ''', max('+name+') as MaxID FROM dbo.'+tbname+' UNION ' + CHAR(13) + CHAR(10)
FROM dbo.sequence;

/* get rid of extra "UNION" and CRLF at the end of the dynamic string */
SET @nsql = LEFT(@nsql, len(@nsql)-8);

exec sp_executesql @nsql;

/* check values */
--SELECT * FROM dbo.#seq;

UPDATE seq
SET seq.reserved = t.maxid
FROM dbo.sequence seq
INNER JOIN dbo.#seq t
ON seq.tbname = t.tbname AND seq.name = t.colname;
Go to Top of Page
   

- Advertisement -