| 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 ReIndexON dbo.Restaurants FOR INSERT, UPDATE, DELETEAS 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 |
 |
|
|
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 existso Storing copies of records in Audit tablesall the heavy data validation etc. is done in SProcsKristen |
 |
|
|
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.--KenYour Kung-Fu is not strong. -- 'The Core' |
 |
|
|
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, DELETEAS... |
 |
|
|
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 |
 |
|
|
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* |
 |
|
|
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 |
 |
|
|
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 RETURNEND It is very basic but does the job well enough. |
 |
|
|
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-coreA 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 |
 |
|
|
|