I'm trying to understand an existing Trigger on one of my tables (DocumentDistribution), that it seems is the bridge table between DocumentInfo and DocumentSource tables. I don’t understand how this line (SET….FROM INSERTED) gets the value from? Is this what it called Dynamic SQL and it gets the value from the front end App for this database based on the user’s selections?
I’m getting errors when I try to test, as the sub-query is returning multiple rows while the syntax is expecting a single row, any advice?
I'm using SQL Server 2008R2.
Alter TRIGGER [dbo].[DocDist_Dup_Check]
ON [dbo].[DocumentDistribution]
AFTER UPDATE, INSERT
AS
BEGIN
SET NOCOUNT ON;
SET ANSI_WARNINGS OFF;
DECLARE @docid int,
@sourceid int,
@errstr varchar(255);
SET @errstr = 'The distribution you have attempted to create already exists in the database.' + CHAR(13) +
'Duplicate distributions are not allowed for any document source except eBinder.';
SET @docid = (SELECT DocumentDistDocID FROM INSERTED);
SET @sourceid = (SELECT DocumentDistSourceID FROM INSERTED);
IF (SELECT COUNT(*) FROM DocumentDistribution WHERE DocumentDistDocID = @docid AND DocumentDistSourceID = @sourceid) > 1
AND
(@sourceid NOT IN(SELECT DocumentSourceID FROM DocumentSource WHERE DocumentSourceName = 'eBinder' OR DocumentSourceName = 'eBinder - West Division' OR DocumentSourceName = 'Consolidated e-Binder' OR DocumentSourceName = 'MA Consolidated eBinder'))
BEGIN
ROLLBACK TRANSACTION
END
ELSE
BEGIN
RETURN
END
END
Best Answer
The problem is that the trigger was initially written with very simplistic logic and with minimal testing - it assumed that the update or insert could only ever affect a single row at a time.
The line you are asking about is attempting to assign a single value to a variable from a table that may contain more than one row (triggers in SQL Server fire per action, not per row). Different forms of variable assignment behave differently when there is more than one row to choose from, for example:
If you run the second version, you will get an error message you've probably already seen from your trigger:
Not all is lost - your trigger can be re-written to handle multiple rows being affected - I am going to make the assumption that if you insert two rows and only one of them fails, you want the whole transaction rolled back, even though the other row was perfectly fine. (Also, your initial code populates
@errstr
but never uses it, so I've just left that out for now.)I think I have your logic correct; please test on a restored backup or dev system.
You might consider implementing this as an
INSTEAD OF
trigger - this allows you to either insert or not depending on your business logic (or only insert the rows that don't violate your constraints), instead of always inserting and then sometimes rolling back.You might also consider implementing a unique constraint (or maybe a filtered unique index or an indexed view, if the duplicates only need to be enforced on a subset) if the primary purpose of this trigger is to prevent duplicates.