Trigger on View not firing on SQL Server 2012 - sql

I have seen some articles mention the possibility of a Trigger on a View, triggering on either insert, updates or deletes to one of the base tables from which the View is created.
However I am not able to get a simple example to work.
CREATE TABLE [Test].[Data] (
Id INT PRIMARY KEY IDENTITY (1,1),
Data VARCHAR(255) NOT NULL,
);
GO
CREATE VIEW [Test].[View] AS SELECT * FROM [Test].[Data];
GO
CREATE TABLE [Test].[Queue] (
Id INT PRIMARY KEY IDENTITY (1,1),
DataId INT NOT NULL,
Action VARCHAR(255) NOT NULL,
Timestamp DATETIME NOT NULL,
);
GO
CREATE TRIGGER InsertTrigger ON [Test].[View] INSTEAD OF INSERT AS
BEGIN
DECLARE #DataId INT;
DECLARE #Timestamp DATETIME;
SET #DataId = (SELECT Id FROM INSERTED);
SET #Timestamp = GETDATE();
INSERT INTO [Test].[Queue] (DataId, Action, Timestamp) VALUES (#DataId, 'Insert', #Timestamp)
END
GO
ENABLE TRIGGER InsertTrigger ON [Test].[View];
GO
INSERT INTO [Test].[Data] (Data) VALUES ('Testdata');
The trigger is not firing, is the above not possible or is there something wrong with my Sql?
Edit: Although answered I would like to clarify the question. The idea was to get the trigger on the View to fire, when there was an Insert to the base table and not the View itself.

A trigger on a view will only work on inserts into that view, not on any inserts into tables to which the view references.
In your script you're not inserting into that view, you're inserting into a table.

In addition to not testing this correctly, your view is wrong. You are not considering that inserted represents multiple rows, not one.
So:
CREATE TRIGGER InsertTrigger ON [Test].[View] INSTEAD OF INSERT AS
BEGIN
INSERT INTO [Test].[Queue] (DataId, Action, Timestamp)
SELECT i.Id, 'Insert', GETDATE()
FROM Inserted;
END;
GO
INSERT INTO [Test].[View] (Data)
VALUES ('Testdata');

Related

Supposedly easy trigger

I've created a trigger which is to block inserted records with a date already existing in a table.
CREATE TRIGGER [dbo].[SpecialOffers_Insert]
ON [dbo].[SpecialOffers]
FOR INSERT,UPDATE
AS
SET NOCOUNT ON
IF EXISTS (SELECT * FROM inserted WHERE SPO_DateFrom IN (SELECT SPO_DateFrom FROM dbo.SpecialOffers))
BEGIN
RAISERROR('Error. ', 16, 1)
ROLLBACK TRAN
SET NOCOUNT OFF
RETURN
END
SET NOCOUNT OFF
It is added to a table:
CREATE TABLE [dbo].[SpecialOffers](
[SPO_SpoId] [int] IDENTITY(1,1) NOT NULL,
[SPO_DateFrom] [datetime] NOT NULL,
[SPO_DateTo] [datetime] NOT NULL)
The table is empty but when trying to insert such record:
INSERT INTO dbo.SpecialOffers (SPO_DateFrom, SPO_DateTo) VALUES ('2015-01-15','2015-01-15')
I got the Error from the trigger. How should I modify the trigger not to get the error?
If the goal is to block inserted records with date already existing in a table, you don't need a trigger - just create a unique constraint on the date field:
ALTER TABLE [dbo].[SpecialOffers]
ADD CONSTRAINT SpecialOffersUQ UNIQUE (SPO_DateFrom)
If you wanted a trigger to prevent overlaps, why didn't you say so:
CREATE TABLE [dbo].[SpecialOffers](
[SPO_SpoId] [int] IDENTITY(1,1) NOT NULL,
[SPO_DateFrom] [datetime] NOT NULL,
[SPO_DateTo] [datetime] NOT NULL,
constraint CK_SO_NoTimeTravel CHECK (SPO_DateFrom <= SPO_DateTo)
)
GO
CREATE TRIGGER NoOverlaps
on dbo.SpecialOffers
after insert,update
as
set nocount on
if exists (
select *
from dbo.SpecialOffers so1
inner join
dbo.SpecialOffers so2
on
so1.SPO_DateFrom < so2.SPO_DateTo and
so2.SPO_DateFrom < so1.SPO_DateTo and
so1.SPO_SpoId != so2.SPO_SpoId
inner join
inserted i
on
so1.SPO_SpoId = i.SPO_SpoId
)
begin
RAISERROR('No overlaps',16,1)
ROLLBACK
end
Examples:
--Works
INSERT INTO SpecialOffers (SPO_DateFrom,SPO_DateTo)
values ('20010101','20011231')
GO
--Fails (Trigger)
INSERT INTO SpecialOffers (SPO_DateFrom,SPO_DateTo)
values ('20010101','20011231')
GO
--Fails (Constraint)
INSERT INTO SpecialOffers (SPO_DateFrom,SPO_DateTo)
values ('20011231','20010101')
GO
--Fails (Trigger)
INSERT INTO SpecialOffers (SPO_DateFrom,SPO_DateTo)
values ('20020101','20021231'),
('20020701','20030630')
I also added a check constraint so that I didn't have to deal with nonsense data in the trigger.
You might have to change swap some of the <s for <=s or vice-versa, depending on what definition of intervals you want to use (i.e. are DateFrom and DateTo meant to be inclusive or exclusive endpoints for the interval they're describing?)
Since the trigger runs in the transaction context of the SQL statement that fired it, after this INSERT, there will be a row in your table dbo.SpecialOffers with the SPO_DateFrom values you've just inserted and the SELECT from the table will succeed ...
Therefore, the trigger will assume that there's already been a value - and it throws the error (as designed).
You could rewrite the trigger to not look at the newly inserted rows, but anything else - but as others have pointed out, a UNIQUE constraint does that much more simply
You should check if the rows you found are actually NOT the ones you have just inserted. Change the line
IF EXISTS (
SELECT * FROM inserted
WHERE SPO_DateFrom IN (
SELECT SPO_DateFrom
FROM dbo.SpecialOffers)
)
To
IF EXISTS (
SELECT * FROM inserted a
WHERE SPO_DateFrom IN (
SELECT SPO_DateFrom
FROM dbo.SpecialOffers b
WHERE a.SPO_SpoId <> b.SPO_SpoId)
)

Create trigger on dummy view

I'm having problem creating a trigger. The database model look something like this:
CREATE TABLE Unit (
SerialNO VARCHAR(3)
PRIMARY KEY (serialNO)
);
CREATE TABLE PackageConfig (
PartNO VARCHAR(15),
SomeValue VARCHAR(20),
PRIMARY KEY (PartNO)
);
CREATE TABLE Package (
ID INT IDENTITY(1,1),
Config VARCHAR(15),
PRIMARY KEY (ID),
FOREIGN KEY (Config) REFERENCES PackageConfig(PartNO)
);
CREATE TABLE UnitInPackage (
Package INT,
Unit VARCHAR(3),
PRIMARY KEY (Package,Unit),
FOREIGN KEY (Package) REFERENCES Package(ID),
FOREIGN KEY (Unit) REFERENCES Unit(SerialNO)
);
There are Units, PackageConfigurations and Packages. A package has exactly one PackageConfiguration and the relation between Package and Unit is a many (units) to at most one (package).
What I'm trying to accomplish is a trigger on a view that would let me do the following (assuming that the PackageConfig and Units exist):
INSERT INTO v_MultiUnitPackage (PackageConfig, Unit1, Unit2, Unit3)
VALUES ('SomeConfig','abc','bcd','cde');
Which inside the trigger would do something like:
DECLARE #last INT;
INSERT INTO Package (Config) VALUES ('SomeConfig');
SET #last = SCOPE_IDENTITY();
INSERT INTO UnitInPackage (Package,Unit)
VALUES (#last,'abc'),(#last,'bcd'),(#last,'cde');
Or for more columns:
INSERT INTO Package (Config) VALUES ('SomeConfig');
SET #last = SCOPE_IDENTITY();
INSERT INTO UnitInPackage (Package,Unit)
VALUES (#last,'hgf'),(#last,'gfe'),(#last,'fed'),(#last,'edc'),(#last,'dcb'),(#last,'cba');
To summarize, I'd like to create a trigger that takes a PackageConfig and N-number of Units and insert them in the corresponding tables.
I've been looking into creating a dummy view that simply has the correct data types and a great enough number of columns to allow for the number of Units I want but didn't find a solution.
I also looked into something like grouping by Package.ID and for each group selecting the first unit that hasn't already been selected to a previous column. Since GROUP BY is only usable with aggregate functions I'm unsure how to realize this idea.
Realistically I don't see ever needing more than 5 or so Units but would prefer a generic solution to my problem.
Perhaps there is a really simple solution that I'm just not seeing. Any help is greatly appreciated.
Insert with a view is one way of doing it, so you just need to have your dummy view update with the number of units you need. Here is how the trigger should looks like:
create view v_MultiUnitPackage
as
select 'Some-Config' as PackageConfig, 'abc' as Unit1, 'bcd' as Unit2, 'cde' as Unit3
go
create TRIGGER tg_I_v_MultiUnitPackage
ON v_MultiUnitPackage
INSTEAD OF INSERT
AS
BEGIN
DECLARE #last INT;
INSERT INTO Package (Config)
SELECT inserted.PackageConfig
FROM inserted
SET #last = SCOPE_IDENTITY();
INSERT INTO UnitInPackage (Package,Unit)
SELECT #last, inserted.Unit1
FROM inserted;
INSERT INTO UnitInPackage (Package,Unit)
SELECT #last, inserted.Unit2
FROM inserted;
INSERT INTO UnitInPackage (Package,Unit)
SELECT #last, inserted.Unit3
FROM inserted;
END
A better option in my opinion, would be passing the units to a stored procedure as XML and handle the insert there. Here is how the procedured should looks like:
CREATE PROCEDURE usp_i_PackageUnits
(
#PackageConfig varchar(15),
#Units xml -- <root><unit>abc</unit><unit>bcd</unit><unit>cde</unit>
)
AS
BEGIN
DECLARE #last INT;
INSERT INTO Package (Config) VALUES (#PackageConfig);
SET #last = SCOPE_IDENTITY();
INSERT INTO UnitInPackage (Package,Unit)
select node.value('(.)[1]', 'VARCHAR(3)') from #xml.nodes('/root/unit')as result(node)
END

Trigger to update another table WITH auto-increment value

I'm having trouble creating a trigger for my database.
I Have two tables, lets call them A and AHistory.
A has an ID and a value.
I want AHistory to keep track of the value and date for a set time.
AHistory Has an Auto Incremented ID, the value and a timestamp.
this is as far as I have gotten:
CREATE TRIGGER Atrigger
ON A
AFTER INSERT
AS
BEGIN
INSERT INTO AHistory
SELECT value FROM INSERTED
END
GO
Assuming that your db schema looks like
CREATE TABLE a
(id int identity primary key,
value varchar(32));
CREATE TABLE ahistory
(id int identity primary key,
aid int, value varchar(32),
eventdate datetime);
Your trigger might look like
CREATE TRIGGER atrigger ON A
AFTER INSERT AS
INSERT INTO ahistory (aid, value, eventdate)
SELECT id, value, GETDATE() FROM INSERTED;
Here is SQLFddle demo

Select from another table within an Insert Trigger

I am maintaining an audit table, where in I have a parent table and it's child table.I want to insert the primary key of the parent audit table into it's child audit table.
Should I be declaring a "before insert" instead of a "for insert" trigger. Here's my code:
CREATE trigger [trgAudtblChild] On [tblChild]
for Insert
as
BEGIN
declare #serNo bigint
declare #expSerNo int
declare #postPercent numeric (12, 2)
declare #prdSAPid varchar (50)
declare #lastUpdatedBy int
declare #lastUpdatedOn smalldatetime
SELECT
--#serno = serno,
#expSerNo = expSerNo ,
#postPercent = postPercent ,
#prdSAPid = prdSAPid ,
#lastUpdatedBy = lastUpdatedBy ,
#lastUpdatedOn = lastUpdatedOn
FROM INSERTED
select #serno = max(at_serno) from AT_tblParent
insert into AT_tblChild(serNo, expSerNo, postPercent
, prdSAPid, lastUpdatedBy, lastUpdatedOn
, change_column_index) values(
#serNo, #expSerNo, #postPercent
, #prdSAPid, #lastUpdatedBy, #lastUpdatedOn
, 'INSERTED')
End
Return
The above code, does not work and puts the table into transaction.
Before Trigger - When you want to Intercept the data before it actually gets Inserted in Table.
For Trigger - Your record is Inserted but can still modify it.
The only difference is that about record is actually Inserted or not.
Back to the original Query
In you above mentioned situation, you should not use Before Trigger. Consider a case, when your Parent Table record Insertion in under some Transaction and same Transaction gets Rollbacked. In that case, It will crash for the Foreign key constraint. Because you will try to Reference a Foreign key Record of Parent Table into Child Table during Insertion which does not exist in Parent Table.

Get last UNIQUEIDENTIFIER inserted in a table inside a TRIGGER from a SQL 2005 database

I need to be able to monitor a Table and react very time a record is inserted. This table has no ITN IDENTITY field, only a UNIQUEIDENTIFIER as its primary key. Without any alteration of existing inputs, SPs, etc. I need to be able to find the last inserted ID from within a trigger. This is what I have (obviously does not work):
CREATE TRIGGER TR_UserInserted
ON Users
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
EXEC UserInserted (SELECT User_Id FROM INSERTED);
END
GO
Here I am trying to get the User_Id from the last inserted record in the Users table and run it through the UserInserted SP. Thank you for the help, I am stumped.
HLGEM Made a great point - even on a bulk insert, I only need the last record inserted - I know this is a strange request.
You need to change your trigger to fire INSTEAD OF INSERT. A uniqueidentifier variable must be generated using the NEWID() function. In the INSERT statement in the trigger body, the columns must be provided in order. Assuming a table defined this way:
CREATE TABLE Users (
First int,
User_Id uniqueidentifier PRIMARY KEY,
Third int,
Fourth int)
Then the trigger is:
CREATE TRIGGER TR_UserInserted ON Users
INSTEAD OF INSERT AS
BEGIN
DECLARE #newid uniqueidentifier = NEWID()
INSERT INTO Users
SELECT
First,
#newid,
Third,
Fourth
FROM inserted
EXECUTE UserInserted(#newid)
-- you can actually provide all the columns to UserInserted
END
For this to work properly, make sure that the table does not have a default for the primary key as NEWID().