trigger in sql server 2008 - sql

I've been researching but I just can't seem to get it right.
I have the following tables:
create table school_tb
(idSchool int identity primary key,
nameSchool varchar(100),
schoolPopulation int
)
create table career_tb
(idCareer int identity primary key,
nameCareer varchar(100),
carrerPopulation int,
numberClasses int,
idSchool int foreign key references school_tb(idSchool)
)
to find out the populatuon in the first table I have to SUM() population from the careers in the same school.
I need to create a trigger that will update the column population in table school_tb when I update population in career_tb. please help me.
I had something like this, but I can't get it to work.
--create trigger updatePopulation
--on career_tb
--for update as
--if UPDATE(carrerPopulation)
--update school_tb set schoolPopulation =(SELECT add(carrerPopulation)
-- from career_tb
-- where idSchool=(SELECT idSchool
-- from career_tb
-- where idCareer=#idCareer)
-- )
--go
I appreciate any help given. thanks

This should help you out. Please see the comments inside the body of a trigger.
create trigger updatePopulation
on career_tb
-- to update sum even if carreer gets deleted or inserted
after insert, update, delete
as
-- to avoid trigger messing up rows affected
set nocount on
if UPDATE(carrerPopulation)
begin
-- update sum by difference between previous and current state of one record in career
update school_tb
set schoolPopulation = schoolPopulation + difference
from school_tb
-- derived table sums all the careers changed in one go
inner join
(
-- sum all values from careers by school
select idSchool, sum (carrerPopulation) difference
from
(
-- change sign of previous values
select deleted.idSchool, -deleted.carrerPopulation carrerPopulation
from deleted
union all
-- + current values
select inserted.idSchool, inserted.carrerPopulation
from inserted
) a
group by idSchool
-- Skip update in case of no change
having sum (carrerPopulation) <> 0
) a
on school_tb.idSchool = a.idSchool
end

CREATE TRIGGER name ON career_tb
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
MERGE school_tb T
USING
(
SELECT idSchool, SUM(carrerPopulation) res
FROM
(
SELECT idSchool, carrerPopulation
FROM INSERTED
UNION ALL
SELECT idSchool, -carrerPopulation
FROM DELETED
) t
GROUP BY idSchool
) S
ON T.idSchool = S.idSchool
WHEN MATCHED THEN UPDATE SET
schoolPopulation = T.schoolPopulation +S.res
;
END

Related

Trigger to handle Multi Insert Statements

I have two table Stocks and Purchases
CREATE TABLE Stocks
(
id int PRIMARY KEY IDENTITY(1,1),
itemId int NOT NULL,
qty int NOT NULL,
status NVarChar(50) NOT NULL,
)
CREATE TABLE Purchases
(
id int PRIMARY KEY IDENTITY(1,1),
itemId int NOT NULL,
suppId int NOT NULL,
qty int NOT NULL,
Date NVarChar(50) NOT NULL,
status NVarChar(50) NOT NULL,
)
Here is what I want do:
ON Inserting Multiple Records into Purchases table, I want Create a Trigger that Iterates over each Record Inserted and Check itemId FROM the Stocks Table
UPDATE TheQuantity(qty in this case) of each itemId found AND INSERT into the Stocks Table if itemId isn't found
I have tried several ways, in fact I have been struggling with it since yesterday.
this Post was the closest one I have seen but could figure out what is going on, seems like it's only Updating already existing records
Here is what I have tried so far, I meanthe closest I came
CREATE trigger [dbo].[trPurchaseInsert] on [dbo].[Purchases] FOR
INSERT
AS
DECLARE #id int;
DECLARE #itemId int;
DECLARE #status NVarChar(50) = 'available';
SELECT #itemId=i.ItemId FROM INSERTED i
IF EXISTS(SELECT * FROM Stocks WHERE itemId=#itemId)
BEGIN
SET NOCOUNT ON;
DECLARE #itemIdUpdate int;
DECLARE #qty int;
SELECT #qty=[quantity], #itemIdUpdate=[itemId] FROM INSERTED i
UPDATE Stocks SET qty=qty+#qty WHERE itemId=#itemIdUpdate
END
ELSE
BEGIN
SET NOCOUNT ON;
INSERT INTO Stocks SELECT [itemId], [id], [quantity], #status
FROM INSERTED i
END
this works fine in a single Insert but doesn't work when multiple records are inserted into the Purchase Table at once
The above Trigger Updates the first itemId only and doesn't update the rest or even insert new ones if one itemid is found
The Goal here is to Update in stock items if itemid is found and Insert if itemId isn't found
For Further Details see this SQL Fiddle. It contains Tables & what I have tried with commented details
I have seen several comments advising to use set base operations with joins but couldn't figure a direction
How can I get it to work?
Your trigger is fatally flawed. It does not take into account multiple rows being inserted, it also doesn't deal with updates and deletes.
Instead you should use this:
Note how inserted and deleted are both used. deleted.qty is subtracted.
The whole thing is grouped up by itemId and then the difference is applied to Stocks.
Since you also want to insert, you need to use MERGE. Alternatively, you can have separate joined UPDATE and INSERT...WHERE NOT EXISTS statements.
CREATE OR ALTER TRIGGER dbo.trPurchaseInsert
ON dbo.Purchases
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
MERGE Stocks s
USING (
SELECT
diff.ItemId,
qty = SUM(diff.qty)
FROM (
SELECT
i.ItemId,
i.qty
FROM inserted i
UNION ALL
SELECT
d.ItemId,
-d.qty
FROM deleted d
) diff
GROUP BY
diff.ItemId
) diff
ON diff.ItemId = s.ItemId
WHEN NOT MATCHED THEN
INSERT (itemId, qty, status)
VALUES (diff.itemId, diff.qty, 'available')
WHEN MATCHED THEN
UPDATE SET
qty += diff.qty;
I must say, I would not implement this with triggers at all.
It's best not to denormalize such data into another table. If you need to query it, you can just do it on the fly from the Purchase table. Use a view if you want, and even index it.
CREATE VIEW dbo.vTotalStock
WITH SCHEMABINDING -- an indexed view must be schema-bound
AS
SELECT
p.ItemId,
qty = SUM(p.qty),
count = COUNT_BIG(*) -- must always include COUNT_BIG if aggregating and indexing
FROM dbo.Purchases p
GROUP BY
p.ItemId;
CREATE UNIQUE CLUSTERED INDEX UX ON vTotalStock (ItemId)
db<>fiddle

Delete trigger and getting field from another table

I have this delete trigger on an SQL database. The record deletes currently and gets written to an audit table. I have been asked to include in this history table a field from another table that is related to the record being deleted based on SurveyID. I thought I could do something like
select #Status = Status from table where Survey = deleted.Survey
But this is incorrect syntax.
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
Declare #SurveyId int
Declare #StudentUIC varchar(10)
Declare #Status varchar(10)
select #SurveyId = deleted.SurveyID,
#StudentUIC = deleted.StudentUIC
from deleted
select #Status = Status from tbly when SurveyID = deleted.SurveyID
insert into fupSurveyAudit
values(#SurveyId,#StudentUIC,#Status)
End
Arrgh. I think you want this insert in your trigger (and nothing else):
insert into fupSurveyAudit(SurveyId, StudentUIC, status)
select d.SurveyId, d.StudentUIC, y.status
from deleted d left join
tbly y
on d.SurveyId = y.SurveyId;
Notes:
deleted could contain more than one row, so assuming that it has one row can lead to a run-time error or incorrect results.
A left join is needed in case there is no matching row for the status.
You should always include the columns in an insert
Your archive table should have additional columns, such as an identity column and the date of the insert, which are set automatically (and hence not explicitly part of the insert).
Triggers are fired once for each statement (Delete,insert,update) not for each row inside the statement.
You cannot use variables here because when multiple lines are deleted from the table only one line will be inserted in the Audit table because the variable can only hold one value.
You just need a simple insert from the deleted table into the Audit table something like this....
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
insert into fupSurveyAudit(SurveyId, StudentUIC,[Status])
select d.SurveyID
,d.StudentUIC
,y.[Status]
from deleted d
INNER JOIN tbly y ON y.SurveyID = deleted.SurveyID
End
Try this
ALTER trigger [dbo].[table_Selfdelete]
on [dbo].[table]
after delete
as
Begin
Set nocount on;
insert into fupSurveyAudit -- Better listed the column list here
select
d.SurveyID, d.StudentUIC, y.Status
from
deleted d JOIN tbly y ON d.SurveyID = y.SurveyID
End

FIM - SQL Triggers for updating records in Delta table

I'm writing a DML trigger when change (update or Insert) happens in one table (Master table), I want to write the whole row into another table (Delta table).
The Master and Delta tables contains the same column with same datatype, except that Delta table contains an additional column called 'change_type', which should say either 'INSERT' OR 'MODIFY', depending on which trigger is updating the delta table.
The difficulty I'm having is I want to use the inserted table to update the Delta table row but its giving me errors.
CREATE TRIGGER [dbo].[TR_Update]
ON [dbo].[People_Master]
AFTER Update
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
Declare #RowCount int
Declare #ID int
Declare #Email nvarchar(50)
Declare #ct nvarchar(10)
select #ID = ID from inserted
Select #RowCount=COUNT(*) from People_Delta where People_Delta.ID = #ID and People_Delta.change_type = 'Modify';
if(#RowCount = 0)
Begin
Insert into People_Delta (ID,Email,uac,Department,FirstName,change_type)
values (iserted.ID,inserted.Email,inserted.uac,inserted.Department,inserted.Firstname'Modify');
END
END
GO
My table has 5 columns.
ID (primary key)
Email
Firstname
uac
Department
You are missing a , in your INSERT statement.
And because the number of columns you have specified does not match with the number of values you are inserting, you get an error.
inserted.Firstname , 'Modify'
Insert into People_Delta (ID,Email,uac,Department,FirstName,change_type)
values (iserted.ID,inserted.Email,inserted.uac,inserted.Department,inserted.Firstname,'Modify');

Generating the Next Id when Id is non-AutoNumber

I have a table called Employee. The EmpId column serves as the primary key. In my scenario, I cannot make it AutoNumber.
What would be the best way of generating the the next EmpId for the new row that I want to insert in the table?
I am using SQL Server 2008 with C#.
Here is the code that i am currently getting, but to enter Id's in key value pair tables or link tables (m*n relations)
Create PROCEDURE [dbo].[mSP_GetNEXTID]
#NEXTID int out,
#TABLENAME varchar(100),
#UPDATE CHAR(1) = NULL
AS
BEGIN
DECLARE #QUERY VARCHAR(500)
BEGIN
IF EXISTS (SELECT LASTID FROM LASTIDS WHERE TABLENAME = #TABLENAME and active=1)
BEGIN
SELECT #NEXTID = LASTID FROM LASTIDS WHERE TABLENAME = #TABLENAME and active=1
IF(#UPDATE IS NULL OR #UPDATE = '')
BEGIN
UPDATE LASTIDS
SET LASTID = LASTID + 1
WHERE TABLENAME = #TABLENAME
and active=1
END
END
ELSE
BEGIN
SET #NEXTID = 1
INSERT INTO LASTIDS(LASTID,TABLENAME, ACTIVE)
VALUES(#NEXTID+1,#TABLENAME, 1)
END
END
END
Using MAX(id) + 1 is a bad idea both performance and concurrency wise.
Instead you should resort to sequences which were design specifically for this kind of problem.
CREATE SEQUENCE EmpIdSeq AS bigint
START WITH 1
INCREMENT BY 1;
And to generate the next id use:
SELECT NEXT VALUE FOR EmpIdSeq;
You can use the generated value in a insert statement:
INSERT Emp (EmpId, X, Y)
VALUES (NEXT VALUE FOR EmpIdSeq, 'x', 'y');
And even use it as default for your column:
CREATE TABLE Emp
(
EmpId bigint PRIMARY KEY CLUSTERED
DEFAULT (NEXT VALUE FOR EmpIdSeq),
X nvarchar(255) NULL,
Y nvarchar(255) NULL
);
Update: The above solution is only applicable to SQL Server 2012+. For older versions you can simulate the sequence behavior using dummy tables with identity fields:
CREATE TABLE EmpIdSeq (
SeqID bigint IDENTITY PRIMARY KEY CLUSTERED
);
And procedures that emulates NEXT VALUE:
CREATE PROCEDURE GetNewSeqVal_Emp
#NewSeqVal bigint OUTPUT
AS
BEGIN
SET NOCOUNT ON
INSERT EmpIdSeq DEFAULT VALUES
SET #NewSeqVal = scope_identity()
DELETE FROM EmpIdSeq WITH (READPAST)
END;
Usage exemple:
DECLARE #NewSeqVal bigint
EXEC GetNewSeqVal_Emp #NewSeqVal OUTPUT
The performance overhead of deleting the last inserted element will be minimal; still, as pointed out by the original author, you can optionally remove the delete statement and schedule a maintenance job to delete the table contents off-hour (trading space for performance).
Adapted from SQL Server Customer Advisory Team Blog.
Working SQL Fiddle
The above
select max(empid) + 1 from employee
is the way to get the next number, but if there are multiple user inserting into the database, then context switching might cause two users to get the same value for empid and then add 1 to each and then end up with repeat ids. If you do have multiple users, you may have to lock the table while inserting. This is not the best practice and that is why the auto increment exists for database tables.
I hope this works for you. Considering that your ID field is an integer
INSERT INTO Table WITH (TABLOCK)
(SELECT CASE WHEN MAX(ID) IS NULL
THEN 1 ELSE MAX(ID)+1 END FROM Table), VALUE_1, VALUE_2....
Try following query
INSERT INTO Table VALUES
((SELECT isnull(MAX(ID),0)+1 FROM Table), VALUE_1, VALUE_2....)
you have to check isnull in on max values otherwise it will return null in final result when table contain no rows .

SQL Trigger & Inserted Idenities

I've got a problem with a trigger that i can't figure out.
Assume i have two tables, Stu_Table2 & Stu_log. Stu_table2 has some columns, one of which is an automatically generated primary key [stu_id]. The link between the two tables is [stu_name]=[user_id]
The below code works fine for Updates & Deletions (as the primary key already exists). But i'm stuck on the insert - how can i insert the automatically generated primary key from stu_name to log table if it hasn't been generated yet?
Stu_name columns, [stu_id] [Stu_name] [Stu_class]
Stu_log columns, [user_id] [stu_name]
obviously this isn't a real world example, just testing proof of concept.
ALTER TRIGGER [dbo].[stu_testtrigger]
ON [dbo].[Stu_Table2] FOR INSERT, UPDATE, DELETE
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with caller queries SELECT statements.
-- If an update/insert/delete occurs on the main table, the number of records affected
-- should only be based on that table and not what records the triggers may/may not
-- select.
SET NOCOUNT ON;
--
-- Variables Needed for this Trigger
--
DECLARE #stu_ID int
DECLARE #stu_name varchar(15)
DECLARE #stu_class int
--
-- Determine if this is an INSERT,UPDATE, or DELETE Action
--
DECLARE #Action as char(1)
DECLARE #Count as int
SET #Action = 'I' -- Set Action to 'I'nsert by default.
SELECT #Count = COUNT(*) FROM DELETED
if #Count > 0
BEGIN
SET #Action = 'D' -- Set Action to 'D'eleted.
SELECT #Count = COUNT(*) FROM INSERTED
IF #Count > 0
SET #Action = 'U' -- Set Action to 'U'pdated.
END
if #Action = 'D'
-- This is a DELETE Record Action
--
BEGIN
SELECT #Stu_id =[stu_id]
,#Stu_name = [stu_name]
FROM DELETED
DELETE [dbo].[stu_log]
WHERE [user_id]=#stu_id
END
Else
BEGIN
--
-- Table INSERTED is common to both the INSERT, UPDATE trigger
--
SELECT #stu_id =[stu_id]
,#stu_name = [stu_name]
FROM INSERTED
if #Action = 'I'
-- This is an Insert Record Action
--
--THIS IS WHERE I'm STUCK i think!!!
BEGIN
INSERT INTO [stu_log]
([user_id]
,[description])
VALUES
(#stu_id
,#stu_name)
END
else
-- This is an Update Record Action
--
BEGIN
UPDATE [stu_log]
SET [user_id] = #stu_id
,[description] = #Stu_name
WHERE [user_id]=#stu_id
END
END
HELP!
Since you seem to want to carry out distinctly different actions for inserts, updates and deletes, I'm not sure why you're cramming all of the actions into a single trigger. I'd just have:
CREATE TRIGGER [dbo].[stu_testtrigger_I]
ON [dbo].[Stu_Table2] AFTER INSERT
AS
INSERT INTO stu_log ([user_id],[description])
SELECT stu_id,stu_name from inserted
GO
CREATE TRIGGER [dbo].[stu_testtrigger_D]
ON [dbo].[Stu_Table2] AFTER DELETE
AS
DELETE FROM stu_log WHERE [user_id] IN (
SELECT stu_id from deleted)
GO
CREATE TRIGGER [dbo].[stu_testtrigger_U]
ON [dbo].[Stu_Table2] AFTER UPDATE
AS
UPDATE l SET user_name = i.user_name
FROM
stu_log l
inner join
inserted i
on l.[user_id] = i.stu_id
GO
Notes:
This works for multi-row inserts, updates and deletes, which your original didn't
I've said AFTER instead of FOR, to make it clearer to you that these actions occur after any activity in Stu_Table2 has already occurred (e.g. the identity value has already been generated, which seems to be your concern).
You should note, however, that AFTER and FOR are synonymous. You'd only get different behaviour if we were doing an INSTEAD OF trigger.
I removed the pointless [user_id] = #stu_id setting from the UPDATE. Given the WHERE clause of this update (or my join equivalent, above), those two must already be equal.