Trigger recognising data as duplicates when it isn't - sql-server

I have this trigger in SQL Server:
CREATE TRIGGER MyTrigger ON [dbo].[practiseduplicates]
AFTER INSERT
AS
IF EXISTS (SELECT *
FROM [practiseduplicates] t
INNER JOIN inserted i ON i.[money] = t.[money]
AND i.[Name] = t.[Name]
AND i.[year month] = t.[year month])
BEGIN
ROLLBACK
RAISERROR ('Duplicated Data', 16, 1);
END
I then insert these values (which are already in the data table):
insert into [practiseduplicates]
values ('2017-02', 'buzzlightyear', '10.09')
When I click execute I expected the error message to pop up... which it did, however when I change the values to information that I know is not in the data table
e.g.
'2056-12', 'mr potato head', '12345.09'
The error message still pops up, when in actual fact it should have just inserted the data into the table, does anyone know why this is the case?
I suspect its to do with my inner join statement but I am not sure.

Quoted from your question
When I click execute I expected the error message to pop up... which it did, however when I change the values to information that I know is not in the data table
That italic part of the statement is not quite accurate, because even if those values were not in the table before, after you run insert, they are in there. And the trigger will fire.
In short, you are creating an AFTER INSERT trigger and check if the data inserted to the table is already in the table (after the insert is ran). Of course the trigger will fire every time because if the data is in the inserted table, it is in the table (because it was just inserted).

So basically I used a constraint to solve this rather than a trigger statement.
ALTER TABLE [dbo].[practiseduplicates]
ADD CONSTRAINT [constraintforduplicates] UNIQUE NONCLUSTERED
( [column name],
[column name], etc etc
)
This stops any duplicates using the columns you state in the constraint statement as the data to compare to and flag if they're duplicates and doesn't insert the data into the table.
Note that the columns in the statement have 900 byte constraint themselves. So e.g if you had varchar (max) column, the constraint would not run as the maximum it can do is 900 bytes. In my script I put varchar (800).

Related

Exception after dropping table

If I execute a procedure that drops a table and then recreate it using 'SELECT INTO'.
IF that procedure raises an exception after dropping the table, does table dropping take place or not?
Unless you wrap them in a transaction,table will be dropped since each statement will be considered as an implicit transaction..
below are some tests
create table t1
(
id int not null primary key
)
drop table t11
insert into t1
select 1 union all select 1
table t11 will be dropped,even though insert will raise an exception..
one more example..
drop table orderstest
print 'dropped table'
waitfor delay '00:00:05'
select * into orderstest
from Orders
now after 2 seconds,kill session and you can still see orderstest being dropped
I checked with some other statements other than select into ,i don't see a reason why select into will behave differently and this applies even if you wrap statements in a stored proc..
IF you want to rollback all,use a transaction or more better use set xact_Abort on
Yes, the dropped table will be gone. I have had this issue when I script a new primary key. Depending on the table, it saves all the data to a table variable in memory, drops the table, creates a new one with the new pk, then loads the data. If the data violates the new pk, the statement fails and the table variable is dropped leaving me with a new table and no data.
My practice is to create the new table with a slightly different name, load the data, change both table names in a statement, then once all the data is confirmed loaded, drop the original table.

SQL Server trigger inserting whole table rows, instead of new

I m making a mistake somewhere but can't figure out.
Scenario is this:
Whenever a new row is inserted in table 'TestTrigger', the table TriggerInsert should get a value capturing time when a new row was inserted in TestTrigger.
The problem is, whenever the trigger hits, instead of just a new row, the whole table gets repopulated in TestTrigger, each time. I only want to capture, the new rows not the entire table getting inserted on each trigger.
Here is my trigger:
USE [irfaan]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER Trigger [dbo].[InsertTriggerTest] on [irfaan].[dbo].[TestTrigger]
--For Insert
After insert
as
INSERT into TriggerInsert (CurrTime, IOFNum) SELECT (GetDate()), SONum FROM TestTrigger
Please assist where I am going wrong.
If you want to look at just the rows that were inserted, you must use the Inserted pseudo table - not the dbo.TestTrigger base table....
ALTER TRIGGER dbo.InsertTriggerTest
ON irfaan.dbo.TestTrigger
FOR INSERT
AS
INSERT INTO TriggerInsert (CurrTime, IOFNum)
SELECT GETDATE(), SONum
FROM Inserted
Your SELECT query is returning all rows from your source table (TestTrigger). Use the table inserted instead. It will contain just the rows that are being inserted into TestTrigger instead of all of the rows as you are currently getting.
INSERT into TriggerInsert (CurrTime, IOFNum) SELECT (GetDate()), SONum FROM Inserted

What is the use of OUTPUT clause in sql server

What is the purpose of the OUTPUT clause? I have gone through the MSDN documentation for the OUTPUT clause, which includes the following example:
DELETE FROM dbo.table1
OUTPUT DELETED.* INTO #MyTableVar
WHERE id = 4 OR id = 2;
From the above query, it seems that deleted records are saved in some magic table called deleted, and the query will load those records into table called MyTableVar from the magic deleted table. .
I still do not understand the purpose of the OUTPUT clause usage.
As another SQL example:
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO #MyTableVar
VALUES (N'Operator error', GETDATE());
--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM #MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate
FROM Production.ScrapReason;
GO
What is this actually doing? Can anyone explain what this clause is doing with an easy example?
UPDATE with non-functioning example:
create proc test
as
CREATE TABLE dbo.table1
(
id INT,
employee VARCHAR(32)
)
go
INSERT INTO dbo.table1 VALUES
(1, 'Fred')
,(2, 'Tom')
,(3, 'Sally')
,(4, 'Alice')
delete from table1
select * from deleted
This gives me an error when I run it, because it can't see the deleted table.
The general purpose of this clause is to capture the changes made to your data without an additional query, which would introduce locking and blocking issues. Example:
DELETE FROM X WHERE Name = 'Foo'
You want to know which IDs were deleted. You can do this naively like this:
SELECT ID FROM X WHERE Name = 'Foo'
DELETE FROM X WHERE Name = 'Foo'
But these selected IDs are unreliable unless you are running in a transaction with isolation level SERIALIZABLE which is usually not the case. Someone else can add, delete or change "Foo"-Records between your two statements. So instead you can use the OUTPUT clause and get back exactly and reliably the deleted IDs without any performance or reliability issues.
Another frequent use is to get the value of inserted default values, especially when using identity columns. For a single insert you can do this:
CREATE TABLE X
(
ID INT IDENTITY,
Name VARCHAR(10)
);
INSERT X (Name) VALUES ('Foo')
SELECT SCOPE_IDENTITY()
But SCOPE_IDENTITY() can give you only the last inserted ID. If you do multiple inserts, like
INSERT X (Name) VALUES ('Foo'), ('Bar')
or
INSERT X (Name) SELECT OtherName FROM Y
and you want to know the inserted IDs, you are out of luck. You can try to find them with another SELECT, but you need another unique column to even formulate the query and then you run into the same issues as with the DELETE sample above. So, the OUTPUT clause lets you identify neatly which Names got which IDs.
You will need these IDs for example when creating dependent records with foreign keys. Think "Order" and "OrderDetails" which are linked by an OrderID column with an IDENTITY clause. Again, with a single INSERT you can get away with using SCOPE_IDENTITY() or ##IDENTITY, but when inserting multiple orders at once, you will need OUTPUT.
When you perform Insert/Update/Delete operation on particular table and want to know what rows are affected OR want to log them for audit trail OR you want to use multiple values of affected rows in subsequent sql statements, you can use OUTPUT clause.
For Insert statement, it will have INSERTED table.
For Delete statement, it will have DELETED table. In case of Update DELETED table will contain rows (with old values) before update operation performed.
For Update statement, it will have DELETED and INSERTED tables.
DELETED table will contain rows (with old values) before update operation performed.
INSERTED table will contain rows (with new values) after update operation performed.
USE AdventureWorks2012;
GO
DECLARE #MyTableVar table( NewScrapReasonID smallint,
Name varchar(50),
ModifiedDate datetime);
INSERT Production.ScrapReason
OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
INTO #MyTableVar
VALUES (N'Operator error', GETDATE());
--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM #MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate
FROM Production.ScrapReason;
Now your query inserts rows in Production.ScrapReason as well as table variable #MyTableVar. Later it selects inserted rows from Production.ScrapReason and #MyTableVar. Thus you can compare both the resultset and it must have identical rows (considering Production.ScrapReason is empty table.)
I hope it makes sense!
Edit:
Inserted/Deleted tables will be available with Insert/Update/Delete statement and not after that. You may want to store those magic table values in db table or temp table.
Without the OUTPUT clause, how would you know which rows were deleted? Your example seems so simple because you already know the Id values, but what if you did this:
DELETE FROM T WHERE SomeColumn LIKE 'SomePattern%'
And you want to find out what was deleted. That's the purpose of the OUTPUT clause.

Will a trigger be able to copy a primary key that's an identity id?

I want to do after I an INSERT in table X to copy that record into another History table immediately.
Now the table has the primary key as an Identity column, so the record won't have an primary key id until it is actually inserted.
My question is if I do a trigger on this record will I get the identity id for that record or will it still be blank?
Yes the identity is available in the trigger but make sure you get that id correctly.
##identity, SCOPE_IDENTITY etc are NOT what you want to do in a trigger!
SELECT #id = id FROM inserted
Is also a bad idea.
Always write your triggers to expect multiple changes being made simultaneously. The above approaches will all cause subtle but important errors when you insert more than one record into the table at a time.
The correct approach is to insert into your audit table FROM the inserted table
i.e.
INSERT INTO myAuditTable(Id, Datetime, user)
SELECT id, GETDATE(), USER_NAME())
FROM inserted
if you do the 'after insert' trigger, the record is already there with a value for the identity column.
Just make sure you declare the trigger as "AFTER" insert, not "FOR" or "INSTEAD OF" (guess you wouldn't use the last one... ;)
http://msdn.microsoft.com/en-us/library/ms189799.aspx

how to work with after insert trigger in sql server 2008

i am working on sql server, where i want to insert the record in a particular table say (a), this table contains two column [id (Identity Field) and name(nvarchar(max)] now after the records is inserted in table (a), a trigger should fire and insert the identity field value in table b.... i am using after insert trigger for this purpose but i am not getting how i would be getting the identity field value in trigger... which should be inserted in table b.
This is what i am using
create trigger tri_inserts on (a)
after insert
as
begin
insert into b (id, name) values (?,?)
end
Please reply as soon as possible..
Thanks and Regards
Abbas Electricwala
create trigger tri_inserts on a
after insert
as
set nocount on
insert into b (id, name)
SELECT id, name FROM INSERTED
GO
#gbn has the best solution, but I want you to understand why the SELECT clause is better than using a VALUES clause in a trigger. Triggers fire for each batch of records inserted/updated/deleted. So the inserted pseudotable or the deleted pseudotable may have one record or they may have a million. The trigger has to be able able to handle either case. If you use a values clause, you only get the action happening for one of the records out the the million. This casues data integrity issues. If you decide to loop through the records in a cursor and use the VALUES clause, your performance will be horrible when you get a large number of records. When I came to this job, we had one such trigger, it took 45 minutes to insert a 40,000 record insert. Removing the cursor and using a set-based solution based on athe SELECT clause (Although a much more complex one than the example)reduced the time for the same insert to around 40 seconds.

Resources