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 2000 Forums
 SQL Server Development (2000)
 SQL Trigger and ADO

Author  Topic 

budgie
Starting Member

18 Posts

Posted - 2004-07-16 : 12:42:11
I have just finished writing a trigger - see below - for a table that updates a column in the same table and checks some flags in another table. It works fine when run in QA - runs in under a sec - but the main client is an old ASP management interface.

The problem is that when the one of the fields that trigger the vast majority of the code is modified the script timeouts. I have searched and searched for solutions to this problem without success. The trigger works find for the other conditions in it just this one condition which requires me to update the table that I am already updating. The problem only araises when the "active" bit column is updated. All other conditions work nicely.

I am thinking that it maybe a locking issue between ADO and SQL.

Any help in tracking this down would be much appreciated.

G.

ALTER TRIGGER ReIndex
ON dbo.Restaurants
FOR INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON
DECLARE @tbl_upstreams TABLE (id_auto INT NOT NULL IDENTITY(1,1), str_upstream NVARCHAR(4000))
DECLARE @tbl_areas TABLE (id_area INT)
DECLARE @int_loop INT
DECLARE @int_rc INT
DECLARE @int_i INT
DECLARE @int_d INT
DECLARE @str_upstream NVARCHAR(4000)

SELECT @int_d = COUNT(*) FROM Deleted
SELECT @int_i = COUNT(*) FROM Inserted

IF UPDATE(parent) OR UPDATE(active) OR (@int_d > 0 AND NOT (@int_i > 0))
BEGIN
IF @int_d > 0
BEGIN
IF @int_i > 0 AND NOT UPDATE(parent)
BEGIN
INSERT INTO @tbl_upstreams (str_upstream)
SELECT DISTINCT LTRIM(RTRIM(ISNULL(CAST(r.upstream AS NVARCHAR), '')))
FROM restaurants AS r
JOIN deleted AS d
ON r.[id] = d.[id]
JOIN inserted AS i
ON d.[id] = i.[id]
WHERE i.active = 0
AND d.active != i.active
END
ELSE
BEGIN
INSERT INTO @tbl_upstreams (str_upstream)
SELECT DISTINCT LTRIM(RTRIM(ISNULL(CAST(r.upstream AS NVARCHAR), '')))
FROM restaurants AS r
JOIN deleted AS d
ON r.[id] = d.[id]
END

SET @int_rc = @@ROWCOUNT
SET @int_loop = 0

WHILE @int_loop < @int_rc
BEGIN
SET @int_loop = @int_loop + 1
SET @str_upstream = (SELECT str_upstream FROM @tbl_upstreams WHERE id_auto = @int_loop)

INSERT INTO @tbl_areas
SELECT DISTINCT Items
FROM dbo.Split(@str_upstream, ' ')
WHERE Items NOT IN (SELECT id_area FROM @tbl_areas)
END

DELETE FROM @tbl_areas
WHERE EXISTS (
SELECT [id] FROM restaurants AS r
WHERE r.upstream LIKE '% ' + CAST(id_area AS VARCHAR) + ' %'
AND r.active != 0
AND r.[id] NOT IN (SELECT [id] FROM deleted)
)

UPDATE Areas SET Site7 = 0
WHERE [ID] IN (SELECT id_area FROM @tbl_areas)
AND Site7 != 0

DELETE FROM @tbl_areas
DELETE FROM @tbl_upstreams
END

IF @int_i > 0
BEGIN
IF UPDATE(parent)
BEGIN
UPDATE restaurants
SET upstream = dbo.GetUpstream(i.parent)
FROM restaurants AS r
JOIN inserted AS i
ON r.[id] = i.[id]

INSERT INTO @tbl_upstreams (str_upstream)
SELECT DISTINCT LTRIM(RTRIM(ISNULL(CAST(r.upstream AS NVARCHAR), '')))
FROM restaurants AS r
JOIN inserted AS i
ON r.[id] = i.[id]
WHERE i.active != 0
END

IF @int_d > 0
BEGIN
INSERT INTO @tbl_upstreams (str_upstream)
SELECT DISTINCT LTRIM(RTRIM(ISNULL(CAST(r.upstream AS NVARCHAR), '')))
FROM restaurants AS r
JOIN deleted AS d
ON r.[id] = d.[id]
JOIN inserted AS i
ON d.[id] = i.[id]
WHERE i.active != 0
AND d.active != i.active
END
ELSE
BEGIN
INSERT INTO @tbl_upstreams (str_upstream)
SELECT DISTINCT LTRIM(RTRIM(ISNULL(CAST(r.upstream AS NVARCHAR), '')))
FROM restaurants AS r
JOIN inserted AS i
ON r.[id] = i.[id]
WHERE i.active != 0
END

SET @int_rc = (SELECT COUNT(str_upstream) FROM @tbl_upstreams)
SET @int_loop = 0

WHILE @int_loop < @int_rc
BEGIN
SET @int_loop = @int_loop + 1
SET @str_upstream = (SELECT TOP 1 str_upstream FROM @tbl_upstreams)
DELETE FROM @tbl_upstreams WHERE str_upstream = @str_upstream

INSERT INTO @tbl_areas
SELECT DISTINCT Items
FROM dbo.Split(@str_upstream, ' ')
WHERE Items NOT IN (
SELECT id_area FROM @tbl_areas
)
END

UPDATE Areas SET Site7 = -1
WHERE [ID] IN (
SELECT id_area FROM @tbl_areas
)
AND Site7 = 0
END
END

tkizer
Almighty SQL Goddess

38200 Posts

Posted - 2004-07-16 : 13:11:46
Oh my! That's a lot of work for a trigger to do. I certainly would not recommend doing this in a trigger as you will encounter performance problems (which is why you are timing out). Triggers should be quick so that the transaction completes fast. You've got loops in there which is going to slow it down a lot. If you can't move this code somewhere else, then you need to make sure that everything is probably indexed and probably also increase the timeout value in the connection string.

Tara
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2004-07-17 : 03:25:13
I'm with Tara on that.

In the main, I only use TRIGGERS for:

o Setting "Update Date"
o Checking that non-FK referential data exists
o Storing copies of records in Audit tables

all the heavy data validation etc. is done in SProcs

Kristen
Go to Top of Page

kselvia
Aged Yak Warrior

526 Posts

Posted - 2004-07-17 : 04:39:41
I would start with replacing dbo.GetUpstream(i.parent) with an inline SELECT if it is not too complex to do it.

If it is to complex, try creating a computed column called UpStream on Resturants that equals dbo.GetUpstream(parent) and index it.

Caveat: Computed columns have cause me grief and it seems that is the consensus of many others as well.

Also, what does your split function look like? Parsing lists is often unavoidable but if you have an option of turning them into tables that will obviously help. If you can't, fixed length elements can be parsed faster than delimited lists if your data lends itself to doing it that way. Obviously you need a well implemented split() but I suspect it's pretty good.


--Ken
Your Kung-Fu is not strong. -- 'The Core'
Go to Top of Page

Lumbago
Norsk Yak Master

3271 Posts

Posted - 2004-07-17 : 16:29:46
Just a lame shot in the dark here but will it make a difference if you change the trigger to run *after* the update? And does it need to run for inserts and deletes also?

ALTER TRIGGER ReIndex ON dbo.Restaurants
AFTER INSERT, UPDATE, DELETE
AS
...
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2004-07-17 : 16:59:35
I think FOR is synonymous with AFTER?
BOL:
quote:

AFTER is the default, if FOR is the only keyword specified.

Kristen
Go to Top of Page

Lumbago
Norsk Yak Master

3271 Posts

Posted - 2004-07-17 : 17:02:37
And that's what I get for trying to help?? Hehe...

*ignorance is bliss*
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2004-07-17 : 17:52:38
But he would have come back:
"Hi guys, I've changed FOR to AFTER and it still times out" ... <bg>

Kristen
Go to Top of Page

budgie
Starting Member

18 Posts

Posted - 2004-07-19 : 07:24:20
quote:
Originally posted by kselvia

I would start with replacing dbo.GetUpstream(i.parent) with an inline SELECT if it is not too complex to do it.

If it is to complex, try creating a computed column called UpStream on Resturants that equals dbo.GetUpstream(parent) and index it.

Caveat: Computed columns have cause me grief and it seems that is the consensus of many others as well.


Unfortunately it is way to complex to replace with inline SELECT which would have been my preference. This is a retrofit on a badly designed DB structure (IMHO) but I don't have the option of rebuilding the DB structure as it would mean changing a lot and we are already in the process of builind our "next generation" system. This is a temporary measure to solve some of the problems in the old system.

Also with a computed column it would slow down the front end systems which is unacceptable. We are less concerned with the admin systems being "slightly" slower than we are about the front ends being slower.

quote:
Originally posted by kselvia

Also, what does your split function look like? Parsing lists is often unavoidable but if you have an option of turning them into tables that will obviously help. If you can't, fixed length elements can be parsed faster than delimited lists if your data lends itself to doing it that way. Obviously you need a well implemented split() but I suspect it's pretty good.



Here is what our Split function looks like:

ALTER FUNCTION dbo.Split
(@String nvarchar(4000), @Delimiter char(1))
RETURNS @Results TABLE (Idx int not null identity(1,1), Items nvarchar(4000))
AS


BEGIN
DECLARE @INDEX INT
DECLARE @SLICE nvarchar(4000)
-- HAVE TO SET TO 1 SO IT DOESNT EQUAL ZERO FIRST TIME IN LOOP
SELECT @INDEX = 1
WHILE @INDEX !=0 BEGIN
-- GET THE INDEX OF THE FIRST OCCURENCE OF THE SPLIT CHARACTER
SELECT @INDEX = CHARINDEX(@Delimiter,@STRING)
-- NOW PUSH EVERYTHING TO THE LEFT OF IT INTO THE SLICE VARIABLE
IF @INDEX !=0
SELECT @SLICE = LEFT(@STRING,@INDEX - 1)
ELSE
SELECT @SLICE = @STRING
-- PUT THE ITEM INTO THE RESULTS SET
INSERT INTO @Results(Items) VALUES(@SLICE)
-- CHOP THE ITEM REMOVED OFF THE MAIN STRING
SELECT @STRING = RIGHT(@STRING,LEN(@STRING) - @INDEX)
-- BREAK OUT IF WE ARE DONE
IF LEN(@STRING) = 0 BREAK
END
RETURN
END


It is very basic but does the job well enough.
Go to Top of Page

Kristen
Test

22859 Posts

Posted - 2004-07-19 : 08:52:23
A set-base SPLIT using a TALLY table would be quicker - see http://www.sommarskog.se/arrays-in-sql.html#tblnum-core

A quick test here shows that your SPLIT is 30 times slower that sommarskog's one.

But I don't know if that will help with the overall performance by much.

Kristen
Go to Top of Page
   

- Advertisement -