Dexstar
asked on
Need to fix this trigger to run on both SQL7 and SQL 2000.
I need to have code for this trigger that works properly on both SQL7 and SQL 2000. If I try to run the original version (made for SQL7) on SQL2000, it gives me an error. With a small change, I can make it so that SQL 2000 accepts it, but then SQL7 doesn't like it. Help me come up with a version that works on both so I can have one script that I can use on both platforms.
FYI: I can't accept any answer that uses cursors or that doesn't work properly on batch updates.
Here is the trigger code:
-------------------------- ---------- -
-- SQL Server 7.0
-------------------------- ---------- -
CREATE TRIGGER tblAccountEntries_Maintain _Balance
ON dbo.tblAccountEntries
FOR INSERT, UPDATE, DELETE
AS
-- Do not return row count
SET NOCOUNT ON
-- Update the balance if the amount, AssetID, or AccountID has changed
-- OR if the record is being deleted (indicated by COLUMNS_UPDATED() = 0)
IF ( UPDATE(Amount) OR UPDATE(AssetID) OR UPDATE(AccountID) OR (COLUMNS_UPDATED() = 0) )
BEGIN
-- Update the Balances
UPDATE
dbo.tblAccountBalances
SET
CurrentBalance = AB.CurrentBalance + Change
FROM
dbo.tblAccountBalances AB INNER JOIN
(
SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I FULL OUTER JOIN Deleted D ON
I.AccountID = D.AccountID
AND
I.AssetID = D.AssetID
AND
I.EntryID = D.EntryID
GROUP BY
COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
) AS ChangeData ON
AB.AssetID = AssetID
AND
AB.AccountID = AccountID
-- Rollback on error
IF ( @@ERROR <> 0 )
BEGIN
ROLLBACK TRANSACTION
END
END
-------------------------- ---
-- SQL Server 2000
-------------------------- ----
CREATE TRIGGER tblAccountEntries_Maintain _Balance
ON dbo.tblAccountEntries
FOR INSERT, UPDATE, DELETE
AS
-- Do not return row count
SET NOCOUNT ON
-- Update the balance if the amount, AssetID, or AccountID has changed
-- OR if the record is being deleted (indicated by COLUMNS_UPDATED() = 0)
IF ( UPDATE(Amount) OR UPDATE(AssetID) OR UPDATE(AccountID) OR (COLUMNS_UPDATED() = 0) )
BEGIN
-- Update the Balances
UPDATE
dbo.tblAccountBalances
SET
CurrentBalance = AB.CurrentBalance + Change
FROM
dbo.tblAccountBalances AB INNER JOIN
(
SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I FULL OUTER JOIN Deleted D ON
I.AccountID = D.AccountID
AND
I.AssetID = D.AssetID
AND
I.EntryID = D.EntryID
GROUP BY
COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
) AS ChangeData ON
AB.AssetID = ChangeData.AssetID
AND
AB.AccountID = ChangeData.AccountID
-- Rollback on error
IF ( @@ERROR <> 0 )
BEGIN
ROLLBACK TRANSACTION
END
END
---
(Yes, I really do always format my SQL code that way...)
The only change is to this part, where in the SQL2000 version, I specify the sub-query's alias on the columns:
) AS ChangeData ON
AB.AssetID = ChangeData.AssetID
AND
AB.AccountID = ChangeData.AccountID
If I run the SQL7 version on SQL2K, I get this error:
Server: Msg 209, Level 16, State 1, Procedure tblAccountEntries_Maintain _Balance, Line 2 Ambiguous column name 'AssetID'.
If I run the SQL2K version on SQL7, I get this error:
Error 107: the column prefix 'ChangeData' does not match with a table name or alias name used in the query
Oh, and I'm always open to any other suggestions you guys could make about optimizing the query or anything like that. This trigger runs quite frequently, so anything that improves the performance is worth extra points.
Dex*
FYI: I can't accept any answer that uses cursors or that doesn't work properly on batch updates.
Here is the trigger code:
--------------------------
-- SQL Server 7.0
--------------------------
CREATE TRIGGER tblAccountEntries_Maintain
ON dbo.tblAccountEntries
FOR INSERT, UPDATE, DELETE
AS
-- Do not return row count
SET NOCOUNT ON
-- Update the balance if the amount, AssetID, or AccountID has changed
-- OR if the record is being deleted (indicated by COLUMNS_UPDATED() = 0)
IF ( UPDATE(Amount) OR UPDATE(AssetID) OR UPDATE(AccountID) OR (COLUMNS_UPDATED() = 0) )
BEGIN
-- Update the Balances
UPDATE
dbo.tblAccountBalances
SET
CurrentBalance = AB.CurrentBalance + Change
FROM
dbo.tblAccountBalances AB INNER JOIN
(
SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I FULL OUTER JOIN Deleted D ON
I.AccountID = D.AccountID
AND
I.AssetID = D.AssetID
AND
I.EntryID = D.EntryID
GROUP BY
COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
) AS ChangeData ON
AB.AssetID = AssetID
AND
AB.AccountID = AccountID
-- Rollback on error
IF ( @@ERROR <> 0 )
BEGIN
ROLLBACK TRANSACTION
END
END
--------------------------
-- SQL Server 2000
--------------------------
CREATE TRIGGER tblAccountEntries_Maintain
ON dbo.tblAccountEntries
FOR INSERT, UPDATE, DELETE
AS
-- Do not return row count
SET NOCOUNT ON
-- Update the balance if the amount, AssetID, or AccountID has changed
-- OR if the record is being deleted (indicated by COLUMNS_UPDATED() = 0)
IF ( UPDATE(Amount) OR UPDATE(AssetID) OR UPDATE(AccountID) OR (COLUMNS_UPDATED() = 0) )
BEGIN
-- Update the Balances
UPDATE
dbo.tblAccountBalances
SET
CurrentBalance = AB.CurrentBalance + Change
FROM
dbo.tblAccountBalances AB INNER JOIN
(
SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I FULL OUTER JOIN Deleted D ON
I.AccountID = D.AccountID
AND
I.AssetID = D.AssetID
AND
I.EntryID = D.EntryID
GROUP BY
COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
) AS ChangeData ON
AB.AssetID = ChangeData.AssetID
AND
AB.AccountID = ChangeData.AccountID
-- Rollback on error
IF ( @@ERROR <> 0 )
BEGIN
ROLLBACK TRANSACTION
END
END
---
(Yes, I really do always format my SQL code that way...)
The only change is to this part, where in the SQL2000 version, I specify the sub-query's alias on the columns:
) AS ChangeData ON
AB.AssetID = ChangeData.AssetID
AND
AB.AccountID = ChangeData.AccountID
If I run the SQL7 version on SQL2K, I get this error:
Server: Msg 209, Level 16, State 1, Procedure tblAccountEntries_Maintain
If I run the SQL2K version on SQL7, I get this error:
Error 107: the column prefix 'ChangeData' does not match with a table name or alias name used in the query
Oh, and I'm always open to any other suggestions you guys could make about optimizing the query or anything like that. This trigger runs quite frequently, so anything that improves the performance is worth extra points.
Dex*
whats the point of the entryid condition on the Join ?
ASKER
@Lowfatspread:
It uniquely identifies each entry record, so that if a record is updated, the inserted and deleted values match up in the join. Does it look like it isn't necessary to you? I'd have to try removing it and do some testing. Either way, I'm not sure it would fix the immediate problem.
Dex*
It uniquely identifies each entry record, so that if a record is updated, the inserted and deleted values match up in the join. Does it look like it isn't necessary to you? I'd have to try removing it and do some testing. Either way, I'm not sure it would fix the immediate problem.
Dex*
try this
you shouldn't have to modify the trigger for SQL 7 or 2000
i don't see the point of doing the individual column update tests
particularly as the trigger functions across the insert,update,delete
functions and the results are consistent...
so use a temp table, and only do the updates if a "Total" change has occurred for the account...
CREATE TRIGGER tblAccountEntries_Maintain _Balance
ON dbo.tblAccountEntries
FOR INSERT, UPDATE, DELETE
AS
-- Do not return row count
SET NOCOUNT ON
Select AccountID,AssetId,Change Into #Temp
From (
SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I
FULL OUTER JOIN Deleted D
ON I.AccountID = D.AccountID
AND I.AssetID = D.AssetID
GROUP BY COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
) as X
Where Change <> 0
-- Update the Balances
IF @@ERROR = 0
BEGIN
UPDATE dbo.tblAccountBalances
SET CurrentBalance = AB.CurrentBalance + T.Change
FROM dbo.tblAccountBalances AB
INNER JOIN #Temp as T
ON AB.AssetID = T.AssetID
AND AB.AccountID = T.AccountID
-- Rollback on error
IF ( @@ERROR <> 0 )
BEGIN
ROLLBACK TRANSACTION
END
END
else
BEGIN
ROLLBACK TRANSACTION
END
you shouldn't have to modify the trigger for SQL 7 or 2000
i don't see the point of doing the individual column update tests
particularly as the trigger functions across the insert,update,delete
functions and the results are consistent...
so use a temp table, and only do the updates if a "Total" change has occurred for the account...
CREATE TRIGGER tblAccountEntries_Maintain
ON dbo.tblAccountEntries
FOR INSERT, UPDATE, DELETE
AS
-- Do not return row count
SET NOCOUNT ON
Select AccountID,AssetId,Change Into #Temp
From (
SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I
FULL OUTER JOIN Deleted D
ON I.AccountID = D.AccountID
AND I.AssetID = D.AssetID
GROUP BY COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
) as X
Where Change <> 0
-- Update the Balances
IF @@ERROR = 0
BEGIN
UPDATE dbo.tblAccountBalances
SET CurrentBalance = AB.CurrentBalance + T.Change
FROM dbo.tblAccountBalances AB
INNER JOIN #Temp as T
ON AB.AssetID = T.AssetID
AND AB.AccountID = T.AccountID
-- Rollback on error
IF ( @@ERROR <> 0 )
BEGIN
ROLLBACK TRANSACTION
END
END
else
BEGIN
ROLLBACK TRANSACTION
END
ASKER
@Lowfatspread:
I'll try it right now. Do you think that you way is going to be more efficient? If so, why? Isn't using a temporary table expensive?
Dex*
I'll try it right now. Do you think that you way is going to be more efficient? If so, why? Isn't using a temporary table expensive?
Dex*
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
@Dan: I like the fact that you got the source of the problem. That helps me out a lot. It helps me to know that I'm going crazy ... It's actually a known SQL bug. :)
@Lowfatspread: I'd still like to hear if you think using a temporary table is a good idea. I said I'd give points to optimizing the query. There seems to be some disagreement on whether or not my UPDATE() usage is appropriate.
Dex*
@Lowfatspread: I'd still like to hear if you think using a temporary table is a good idea. I said I'd give points to optimizing the query. There seems to be some disagreement on whether or not my UPDATE() usage is appropriate.
Dex*
Dex*,
I've fixed this one before...
Thanks,
Dan.
I've fixed this one before...
Thanks,
Dan.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I'm working a solution that combines the suggestions. I like lowfat's idea of filtering out the records where Change = 0, to avoid unnecessary updates because I know SQL Server will physically do the update, even if the value doesn't change. And, I will obviously need to change the order of the tables in the query to account for the SQL bug.
> Ps . I still don't understand the inclusion of the Entryid test in your update statement....
Consider a statement like this:
UPDATE tblAccountEntries
SET Amount = Amount + 1.50
Without the EntryID in the join, the AccountBalances get all messed up. Here is the result of the sub-queries. The 2nd one doesn't join on the EntryID, but the first one does.
AccountID AssetID Change
-------------------------- ---------- ----------- ---------------------
00000000-0000-0000-0000-00 0000000001 100 61.5000
CFEF54C9-8D9E-40FA-A754-1F DE43640985 100 10.5000
8A6A3B19-F91F-4103-B051-24 50673DD827 100 10.5000
490A7121-B1D1-4BC2-99B5-AD 0020D997F3 100 4.5000
A2909DF4-FAAF-4ADE-A1DF-E8 A7DD9D837E 100 3.0000
79186F24-09E8-4B49-93B4-F4 D930748917 100 33.0000
AccountID AssetID Change
-------------------------- ---------- ----------- ---------------------
00000000-0000-0000-0000-00 0000000001 100 2521.5000
CFEF54C9-8D9E-40FA-A754-1F DE43640985 100 73.5000
8A6A3B19-F91F-4103-B051-24 50673DD827 100 73.5000
490A7121-B1D1-4BC2-99B5-AD 0020D997F3 100 13.5000
A2909DF4-FAAF-4ADE-A1DF-E8 A7DD9D837E 100 6.0000
79186F24-09E8-4B49-93B4-F4 D930748917 100 726.0000
Dex*
> Ps . I still don't understand the inclusion of the Entryid test in your update statement....
Consider a statement like this:
UPDATE tblAccountEntries
SET Amount = Amount + 1.50
Without the EntryID in the join, the AccountBalances get all messed up. Here is the result of the sub-queries. The 2nd one doesn't join on the EntryID, but the first one does.
AccountID AssetID Change
--------------------------
00000000-0000-0000-0000-00
CFEF54C9-8D9E-40FA-A754-1F
8A6A3B19-F91F-4103-B051-24
490A7121-B1D1-4BC2-99B5-AD
A2909DF4-FAAF-4ADE-A1DF-E8
79186F24-09E8-4B49-93B4-F4
AccountID AssetID Change
--------------------------
00000000-0000-0000-0000-00
CFEF54C9-8D9E-40FA-A754-1F
8A6A3B19-F91F-4103-B051-24
490A7121-B1D1-4BC2-99B5-AD
A2909DF4-FAAF-4ADE-A1DF-E8
79186F24-09E8-4B49-93B4-F4
Dex*
ASKER
@Lowfatspread:
Just a quick question about your modification... Are those two identical, or is there a reason to use one over the other?
---
(Select AccountID,AssetId,Change
From (
SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I
FULL OUTER JOIN Deleted D
ON I.AccountID = D.AccountID
AND I.AssetID = D.AssetID
GROUP BY COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
) as X
Where Change <> 0
) as T
---
(SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I
FULL OUTER JOIN Deleted D
ON I.AccountID = D.AccountID
AND I.AssetID = D.AssetID
GROUP BY COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
HAVING
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) <> 0
) as T
---
Dex*
Just a quick question about your modification... Are those two identical, or is there a reason to use one over the other?
---
(Select AccountID,AssetId,Change
From (
SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I
FULL OUTER JOIN Deleted D
ON I.AccountID = D.AccountID
AND I.AssetID = D.AssetID
GROUP BY COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
) as X
Where Change <> 0
) as T
---
(SELECT COALESCE(I.AccountID, D.AccountID) AS AccountID,
COALESCE(I.AssetID, D.AssetID) AS AssetID,
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) AS Change
FROM Inserted I
FULL OUTER JOIN Deleted D
ON I.AccountID = D.AccountID
AND I.AssetID = D.AssetID
GROUP BY COALESCE(I.AccountID, D.AccountID),
COALESCE(I.AssetID, D.AssetID)
HAVING
SUM(COALESCE(I.Amount, 0) - COALESCE(D.Amount,0)) <> 0
) as T
---
Dex*
hopefully they are the same ...
you need to check the query plan to confirm that the Server has actually performed the same
operations....
i usually like to try an restrict the numbers of functions and aggregations I ask the optimiser to perform
as to my mind that usually makes for easier code maintenance, and simplifies query optimisation requirements..
you need to check the query plan to confirm that the Server has actually performed the same
operations....
i usually like to try an restrict the numbers of functions and aggregations I ask the optimiser to perform
as to my mind that usually makes for easier code maintenance, and simplifies query optimisation requirements..
ASKER
Thanks for your help guys! :) It is much appreciated!
-Dex*
-Dex*