How to create this kind of SQL trigger? - sql

Let's say in sqlite3 I create a table like this.
CREATE TABLE table1 (int a);
I want to create a trigger that activates before an insertion into table1. If the number inserted into table1 is greater than 10, then I want the trigger to not allow the insertion. What's the code to do that?

CREATE TRIGGER [dbo].[table1_trigger]
ON [dbo].[table1]
INSTEAD OF INSERT
AS
BEGIN
DECLARE #a INT
SET #a = (SELECT a FROM Inserted)
IF #a < 10 BEGIN
INSERT INTO dbo.table1 (a) VALUES (#a)
END
END

Related

Not showing all records in After Insert trigger in SQL Server

I have a table table1 with some data:
create table table1
(
c1 varchar(20),
c2 varchar(20)
)
insert into table1 values('1','A')
insert into table1 values('2','B')
insert into table1 values('3','C')
insert into table1 values('4','D')
insert into table1 values('5','E')
insert into table1 values('6','F')
Now I created another table with the same structure called table2 :
create table table2
(
c1 varchar(20),
c2 varchar(20)
)
Then I created an After Insert trigger on table2 :
CREATE TRIGGER trgAfterInsert
ON [dbo].[table2]
FOR INSERT
AS
declare #c1 varchar(20);
declare #c2 varchar(20);
declare #audit_action varchar(100);
select #c1 = i.c1 from inserted i;
select #c2 = i.c2 from inserted i;
set #audit_action = 'Inserted Record -- After Insert Trigger.';
insert into table2_Audit(c1, c2, Audit_Action, Audit_Timestamp)
values(#c1, #c2, #audit_action, getdate());
PRINT 'AFTER INSERT trigger fired.'
GO
There is a problem that when i copy all data of table1 to tabe2 then in Audit table only one record show.It not show all inserted record.
I use this query for copy the record in table2:-
insert into table2(c1,c2) select c1,c2 from table1
Your trigger fires once for each insert statement issued against this table. not one for each row inserted in the table. Hence if more than one row is inserted you trigger definition should be able to handle more than one row.
Instead of using variables to capture values from inserted table and then insert them in table two, simply select from the inserted table and insert the data into Table2_audit.
A trigger to handle this would look something like........
CREATE TRIGGER trgAfterInsert ON [dbo].[table2]
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
insert into table2_Audit (c1,c2,Audit_Action,Audit_Timestamp)
SELECT C1
, C2
, 'Inserted Record -- After Insert Trigger.'
, GETDATE()
FROM inserted ;
PRINT 'AFTER INSERT trigger fired.'
END
GO
Trigger is fired once per entire operation, change your code to:
CREATE TRIGGER trgAfterInsert ON [dbo].[table2]
FOR INSERT
AS
BEGIN
DECLARE #audit_action VARCHAR(100) = 'Inserted Record -- After Insert Trigger.';
INSERT INTO table2_Audit(c1,c2,Audit_Action,Audit_Timestamp)
SELECT i.c1, i.c2, #audit_action, GETDATE()
FROM inserted i;
END
Second don't use PRINT inside trigger;
More info
The behavior you are seeing is by design. DML triggers in SQL Server
are statement level triggers - they are fired once after
INSERT/UPDATE/DELETE/MERGE irrespective of how many rows are affected
by the DML statement. So you should write your logic in the trigger to
handle multiple rows in the inserted/deleted tables

For each inserted row create row in other table with foreign key constrain

I have 2 tables with foreign key constraint:
Table A:
[id] int identity(1, 1) PK,
[b_id] INT
and
Table B:
[id] int identity(1, 1) PK
where [b_id] refers to [id] column of Table B.
The task is:
On each insert into table A, and new record into table B and update [b_id].
Sql Server 2008 r2 is used.
Any help is appreciated.
Having misread this the first time, I am posting a totally different answer.
First if table B is the parent table, you insert into it first. Then you grab the id value and insert into table A.
It is best to do this is one transaction. Depending on what the other fields are, you can populate table A with a trigger from table B or you might need to write straight SQL code or a stored procedure to do the work.
It would be easier to describe what to do if you have a table schema for both tables. However, assuming table B only has one column and table A only has ID and B_id, this is the way the code could work (you would want to add explicit transactions for production code). The example is for a single record insert which would not happen from a trigger. Triggers should always handle multiple record inserts and it would have to be written differently then. But without knowing what the columns in the tables are it is hard to provide a good example of this.
create table #temp (id int identity)
create table #temp2 (Id int identity, b_id int)
declare #b_id int
insert into #temp default values
select #B_id = scope_identity()
insert into #temp2 (B_id)
values(#B_id)
select * from #temp2
Now the problem gets more complex if there are other columns, as you would have to provide values for them as well.
Without removing identity specification you can use the following option:
SET IDENTITY_INSERT B ON
Try this:
CREATE TRIGGER trgAfterInsert ON [dbo].[A]
FOR INSERT
AS
IF ##ROWCOUNT = 0 RETURN;
SET NOCOUNT ON;
SET IDENTITY_INSERT B ON
DECLARE #B_Id INT
SELECT #B_Id = ISNULL(MAX(Id), 0) FROM B;
WITH RES (ID, BIDS)
AS
(SELECT Id, #B_Id + ROW_NUMBER() OVER (ORDER BY Id) FROM INSERTED)
UPDATE A SET [b_Id] = BIDS
FROM A
INNER JOIN RES ON A.ID = RES.ID
INSERT INTO B (Id)
SELECT #B_Id + ROW_NUMBER() OVER (ORDER BY Id) FROM INSERTED
SET IDENTITY_INSERT B OFF
GO
Though Nadeem's answer is on the right track, his trigger for some reason takes max.id instead of NEW.id and doesn't update A accordingly.
For what you ask to be usable by trigger, you need the FK in table A to be nulleable, else you have a race condition between the tables.
EDIT: As SpectralGhost pointed out, my original code didn't support multiple rows, this one will do:
CREATE TRIGGER trgAfterInsertA ON TableA
FOR INSERT
AS
DECLARE db_cursor CURSOR FOR
SELECT id FROM INSERTED
DECLARE #an_id int
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #an_id
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO TableB VALUES(VALUE_PLACEHOLDER)
UPDATE TableA
SET b_id = SCOPE_IDENTITY()
WHERE id = #an_id
FETCH NEXT FROM db_cursor INTO #an_id
END
GO
The VALUE_PLACEHOLDER are the values you initialize TableB with.

SQL Server Stored Proc: Return ID of row deleted?

I have a stored proc where I am deleting a row from a table.
Is there a way to return the ID of the row deleted? I know there's a way to do it with inserting (SCOPE_IDENTITY()), but it doesn't seem to work for deletions.
The code:
BEGIN
declare #returnVal int
DELETE FROM table WHERE num = 1;
set #returnVal = /*HOW TO GET ID OF ROW DELETED?*/
END;
Yes you can. From here:-
CREATE TABLE TestTable (ID INT, TEXTVal VARCHAR(100))
----Creating temp table to store ovalues of OUTPUT clause
DECLARE #TmpTable TABLE (ID INT, TEXTVal VARCHAR(100))
----Insert values in real table
INSERT TestTable (ID, TEXTVal)
VALUES (1,'FirstVal')
INSERT TestTable (ID, TEXTVal)
VALUES (2,'SecondVal')
----Update the table and insert values in temp table using Output clause
DELETE
FROM TestTable
OUTPUT Deleted.ID, Deleted.TEXTVal INTO #TmpTable
WHERE ID IN (1,2)
----Check the values in the temp table and real table
----The values in both the tables will be same
SELECT * FROM #TmpTable
SELECT * FROM TestTable
----Clean up time
DROP TABLE TestTable
GO
You will have to run 2 commands: SELECT to retrieve ID and DELETE to actually perform deleteion.
BEGIN
declare #returnVal int
SELECT #returnVal = ID FROM table WHERE num = 1;
DELETE FROM table WHERE num = 1;
END;
Unless you delete based on ID - in that case you already know it.
Just insert an OUTPUT clause between the DELETE FROM and WHERE:
DECLARE #returnVal TABLE ([id] [int] NOT NULL)
DELETE FROM table
OUTPUT deleted.[id] INTO #returnVal
WHERE num = 1;
/* #returnVal contains the [id] values of all deleted rows. */
In a DELETE statement, you can access the data of deleted rows by the auto-created deleted table alias.

issue with trigger in ms sql server?

IF EXISTS (SELECT name FROM sysobjects WHERE name = 'myTrigger' AND type = 'TR')
BEGIN
DROP TRIGGER myTrigger
END
GO
go
create trigger myTrigger
on mytable_backup
instead of insert
as
begin
declare #seq int
select #seq = seq from inserted
if exists (select * from mytable_backup where seq= #seq) begin
delete from mytable_backup where seq=#seq
end
insert into mytable_backup
select * from inserted
end
go
I've written this trigger to check while inserting if seq column is repeated then update the previous row with same seq if seq doesn't exits insert it with new seq.
In ssis package I'm using OLEDB table(Mytable) as a source which contains.
Name,Age,Seq
Gauraw,30,1
Gauraw,31,1
Kiran,28,3
Kiran,29,3
kiran,28,3
Venkatesh,,4
Venkatesh,28,4
Now I'm loading this table to OLEDB destination(Mytable_backup) as destination.
I suppose to get output as.
Gauraw,31,1
kiran,28,3
Venkatesh,28,4
But I'm getting all the records from Mytable into Mytable_backup.
is anything wrong with my trigger?
I think that this trigger will just take the first row and compare it with the existing. If I understand what you want to do you can quit easy do this:
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'myTrigger' AND type = 'TR')
BEGIN
DROP TRIGGER myTrigger
END
GO
go
create trigger myTrigger
on mytable_backup
instead of insert
as
begin
insert into mytable_backup
select
*
from
inserted
WHERE NOT EXISTS
(
SELECT
NULL
FROM
mytable_backup AS mytable
WHERE
inserted.seq=mytable.seq
)
end
go
EDIT
So I found out what was going on. If you insert all of the rows in one go the inserted contains all the rows.. Sorry my mistake. If there are duplicates in your data your example do not show which to choose. I have chosen the one with the maximum of age (don't know what your requirements is). Here is a update with the full example
Table structure
CREATE TABLE mytable_backup
(
Name VARCHAR(100),
Age INT,
Seq INT
)
GO
Trigger
create trigger myTrigger
on mytable_backup
instead of insert
as
begin
;WITH CTE
AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY inserted.Seq ORDER BY Age) AS RowNbr,
inserted.*
FROM
inserted
WHERE NOT EXISTS
(
SELECT
NULL
FROM
mytable_backup
WHERE
mytable_backup.Seq=inserted.Seq
)
)
insert into mytable_backup(Age,Name,Seq)
SELECT
CTE.Age,
CTE.Name,
cte.Seq
FROM
CTE
WHERE
CTE.RowNbr=1
end
GO
Insert of test data
INSERT INTO mytable_backup
VALUES
('Gauraw',30,1),
('Gauraw',31,1),
('Kiran',28,3),
('Kiran',29,3),
('kiran',28,3),
('Venkatesh',20,4),
('Venkatesh',28,4)
SELECT * FROM mytable_backup
Drop of the database objects
DROP TRIGGER myTrigger
DROP TABLE mytable_backup
Your original code has two flaws:
It assumes that only one record is inserted at a time.
Your insert into mytable_backup happens outside of the if condition. That insert will execute every time.

Alternative to row level triggers?

MS SQL Server doesn't have row level triggers, correct? If I needed to insert a row from within a trigger and then insert another row, based on the result of the first insert, would a cursor be the best solution?
For example, is there a better way to do this:
CREATE TABLE t1 (foo int)
CREATE TABLE t2 (id int IDENTITY, foo int)
CREATE TABLE t3 (t2_id int)
GO
CREATE TRIGGER t1_insert_trg ON t1 FOR INSERT AS
DECLARE c CURSOR FOR
SELECT foo FROM inserted
DECLARE #foo int
OPEN c
FETCH NEXT FROM c INTO #foo
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO t2 (foo) VALUES (#foo)
INSERT INTO t3 (t2_id) VALUES (##IDENTITY)
FETCH NEXT FROM c INTO #foo
END
CLOSE c
DEALLOCATE c
I assume you are on 2005 or better? If so, look into the OUTPUT clause, you shouldn't need row-level triggers. For example:
USE tempdb;
GO
CREATE TABLE t1 (foo int);
CREATE TABLE t2 (id int IDENTITY, foo int);
CREATE TABLE t3 (t2_id int);
GO
CREATE TRIGGER t1_insert ON t1
FOR INSERT AS
BEGIN
DECLARE #new_rows TABLE(new_id INT, old_foo INT);
INSERT t2(foo)
OUTPUT inserted.id, inserted.foo
INTO #new_rows
SELECT foo
FROM inserted;
INSERT t3 SELECT new_id FROM #new_rows;
END
GO
INSERT t1(foo) SELECT 1 UNION ALL SELECT 5;
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
GO
DROP TABLE t1,t2,t3;
You could also manage this by having a trigger on T1 that inserts into T2, then a trigger on T2 that inserts into T3. This isn't going to be as efficient IMHO, and is not easier to manage, but I will submit that it is easier to follow (and may be your only option if you are stuck on 2000). Both could be set-based and wouldn't need cursors or any other row-by-row processing method.
USE tempdb;
GO
CREATE TABLE t1 (foo int);
CREATE TABLE t2 (id int IDENTITY, foo int);
CREATE TABLE t3 (t2_id int);
GO
CREATE TRIGGER t1_insert ON t1
FOR INSERT AS
BEGIN
INSERT t2(foo)
SELECT foo FROM inserted;
END
GO
CREATE TRIGGER t2_insert ON t2
FOR INSERT AS
BEGIN
INSERT t3(t2_id)
SELECT id FROM inserted;
END
GO
INSERT t1(foo) SELECT 1 UNION ALL SELECT 5;
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
GO
DROP TABLE t1,t2,t3;
(BTW, if you are going for identity values, use SCOPE_IDENTITY(), not ##IDENTITY.)
You might be able to avoid a cursor or the need to know what identity was inserted using the following inserts.
Insert INTO t2 (foo) Select foo from inserted
Insert into t3 (t2_id) Select t2.id from t2
inner join inserted i on t2.foo = i.foo
Why not cascade the triggers - Use an INSERT trigger on T2 to perform the insert on T3. Then you can avoid the cursor within t1_insert_trg and just use inserted - as in:
CREATE TRIGGER t1_insert_trg ON t1 FOR INSERT AS
INSERT INTO t2
SELECT foo FROM inserted -- fires t2 INSERTED trigger
CREATE TRIGGER t2_insert_trg ON t2 FOR INSERT AS
INSERT INTO t3
SELECT id FROM inserted