I am trying to create a SQL Server trigger.
When I update the number of the department (dept_no) or the salary in the Employees table, it will insert a new entry into the changes table with the EMP_NO, the old and the new salary, the previous and the new department and the current date.
I tried this trigger code, but it is not working.
ALTER TRIGGER MODEMPLE
ON EMPLOYEES
FOR UPDATE
AS
BEGIN
DECLARE #n_dept_no varchar(4),
#n_salary money,
#a_salary money,
#emp_no int,
#a_dept_no int
SELECT #n_dept_no = dept_no FROM inserted
SELECT #n_salary = salary FROM inserted
SELECT #a_salary = SALARY FROM deleted
SELECT #emp_no = EMP_NO FROM deleted
SELECT #a_dept_no = DEPT_NO FROM deleted
INSERT INTO CHANGES (EMP_NO, A_SALARY, N_SALARY, A_DEPT_NO, N_DEPT_NO, DATE)
VALUES (#emp_no, #a_salary, #n_salary, #a_dept_no, #n_dept_no, GETDATE())
END
Get rid of all the variables, they make no sense in this context because you need to handle multi-row updates. So just:
ALTER TRIGGER dbo.MODEMPLE
ON dbo.EMPLOYEES FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.CHANGES
(EMP_NO, A_SALARY, N_SALARY, A_DEPT_NO, N_DEPT_NO, DATE)
SELECT [old].EMP_NO,
[old].SALARY, [new].salary,
[old].DEPT_NO, [new].DEPT_NO, GETDATE()
FROM inserted AS [new]
INNER JOIN deleted AS [old]
ON [new].EMP_NO = [old].EMP_NO;
END
GO
You also may want to filter out rows where the update didn't make a material change (like UPDATE dbo.EMPLOYEES SET Salary += 0;) but that's beyond the scope of this question.
Related
I expect below procedure to insert one row into the orders table, grab the PK then insert it into the orderlines table this PK with as many lines as many are in the datagridview on the form, then update the stock table with the quantity and finally insert 1 row into the payments table. Unfortunately it inserts as many rows to all 3 tables as many rows are in the datagridview. Is there a way to achieve my goal preferably without braking it down into more procedures?
CREATE PROCEDURE spNewSale
#customer INT, -- passed from a comboBox
#staff VARCHAR(12), -- the user logged in
#product INT, -- passed from a comboBox
#quantity INT -- passed from a textBox
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO orders (order_cust_id, order_staff_id, order_date) -- order_id (PK) = identity autoincremented
VALUES (#customer,
(SELECT logons.logon_st_id FROM logons
WHERE logons.logon_name = #staff), CURRENT_TIMESTAMP);
DECLARE #ordernbr INT = SCOPE_IDENTITY();
INSERT INTO orderlines (ol_order_id, ol_pro_id, ol_qty, ol_value) -- ol_id (PK) = identity autoincremented
VALUES (#ordernbr, #product, #quantity,
#quantity * (SELECT products.pro_price
FROM products WHERE pro_id = #product));
UPDATE products
SET pro_stock = (pro_stock - #quantity)
WHERE pro_id = #product;
INSERT INTO payments (paym_order_id, paym_amount, paym_taken_by, paym_date) -- paym_id (PK) = identity autoincremented
VALUES (#ordernbr,
(SELECT SUM(orderlines.ol_value) AS paym_amount
FROM orderlines WHERE orderlines.ol_order_id = #ordernbr),
(SELECT logons.logon_st_id FROM logons
WHERE logons.logon_name = #staff), CURRENT_TIMESTAMP)
END
I have created a single trigger to catch all insert, update and delete operations in a replica table but in case of update, two rows are inserted one with before update values and another row with after update values but it is not working as expected.
I have tried all possible ways to do it. Now, I want to know whether I should use transaction at trigger level for update operation or should try something else.
ALTER TRIGGER [dbo].[trgAfterInsertUpdateDelete_xyz] ON [dbo].[xyz]
FOR UPDATE,INSERT, DELETE
AS
declare #accountID int;
declare #billingDate date;
declare #amount decimal(18, 2);
---- Get data from inserted/ updated
select #accountID = i.AccountID from inserted i;
select #billingDate=i.BillingDate from inserted i;
select #amount=i.Amount from inserted i;
-- Insert Case
IF EXISTS( SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted)
BEGIN
insert into xyz_Audit
(AccountID, BillingDate, Amount, Audit_Action)
values(#accountID,#billingDate,#amount,'INSERT');
END
-- Update Case
IF EXISTS( SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted)
BEGIN
INSERT INTO xyz_Audit
(AccountID, BillingDate, Amount, Audit_Action)
SELECT d.AccountID, d.BillingDate, d.Amount,
'BeforeUpdate' FROM Inserted i
INNER JOIN Deleted d ON i.ID = d.ID
INSERT INTO xyz_Audit
(AccountID, BillingDate, Amount,Audit_Action)
values(#accountID,#billingDate,#amount,'AfterUpdate');
END
-- Delete Case
IF EXISTS( SELECT * FROM deleted) AND NOT EXISTS(SELECT * FROM inserted)
BEGIN
INSERT INTO xyz_Audit
(AccountID, BillingDate, Amount, Audit_Action)
select accountID,billingDate,amount, 'DELETE'
from deleted
END
I expected that if 9 records are updated then there should be before and after update values for all rows but sometimes it skips. As you can see for Inst.8 there should be before and after rows but it has captured only before row and then insert but in actual there was no row deleted and inserted, only updation was done. Sometime out of 9, it pick only before rows not after update rows or sometime 2 or 3 after update rows not before update rows.
your trigger assuming that only one row will be affected for insert / update / delete operatons. Which means the inserted or deleted may contains multiple rows
and the following part does not handle that.
---- Get data from inserted/ updated
select #accountID = i.AccountID from inserted i;
select #billingDate=i.BillingDate from inserted i;
select #amount=i.Amount from inserted i;
Actually you don't required the above at all. You should just insert into the Audit table directly from the inserted or deleted table
For example the "INSERT CASE" should be
-- Insert Case
IF EXISTS( SELECT * FROM inserted) AND NOT EXISTS(SELECT * FROM deleted)
BEGIN
insert into xyz_Audit (AccountID, BillingDate, Amount, Audit_Action)
select AccountID, BillingDate, Amount, Audit_Action= 'INSERT'
from inserted;
END
similarly you need to change accordingly for the Update section. And you are already doing it correctly for the delete section
Part1 on View
1-1) Create a view named VDept_Headcount that reports headcount for each department.
The report includes 3 columns as follow:
Dept_Name, Dept_Number, No_Emp.
Include all the departments.
Show the content of the view through SQL (Select * from VDept_Headcount;)
USE COMPANY
GO
--1-1) Create a view named VDept_Headcount that reports headcount for each department.
CREATE VIEW VDept_Headcount(Dept_Name, Dept_Number, No_Emp)
AS
SELECT d.DNAME, d.DNUMBER, COUNT(e.DNO)
FROM DEPARTMENT d LEFT OUTER JOIN EMPLOYEE e ON e.DNO = d.DNUMBER
GROUP BY d.DNAME, d.DNUMBER;
GO
SELECT *
FROM VDept_Headcount;
USE COMPANY
GO
--Add yourself into Employee
INSERT INTO EMPLOYEE
VALUES('Dan', NULL, 'Chan', '122345687', '13-Nov-1948', '323 Williams Ct, Columbus, OH', 'M', 55000, '987654321', 5);
SELECT *
FROM VDept_Headcount;
My question is there a different way to replace null value if I want add things back in? Can I use a blank value?
You should detect if it's an INSERT OR DELETE , try this :
CREATE TRIGGER AuditEmp_Trigger
ON YOUR_TABLE
AFTER INSERT , DELETE
AS
if (SELECT COUNT(*) FROM INSERTED ) > 0 AND (SELECT COUNT(*) FROM DELETED) =0
BEGIN
INSERT INTO destination_table SELECT * FROM INSERTED;
END
if (SELECT COUNT(*) FROM INSERTED ) = 0 AND (SELECT COUNT(*) FROM DELETED) >0
BEGIN
INSERT INTO destination_table SELECT * FROM DELETED;
END
GO
If you need to check just the columns updated you can use COLUMNS_UPDATED ()
Your trigger needs to be able to handle multiple rows in the Inserted and Deleted pseudo table, since if your UPDATE statement affects 10 rows, the trigger is called ONCE, but these pseudo table contain 10 rows each. You cannot just try to "select" the values from those table - you'd get one arbitrary row, and all other would be ignored and not handled.
You need to write your triggers in a set-based fashion, and in the case of UPDATE, join the two pseudo tables on the primary key of the table they're attached to:
CREATE TRIGGER dbo.EMPLOYEE_FKDno_UPDATE
ON dbo.DEPARTMENT FOR UPDATE
AS
BEGIN
INSERT INTO dbo.Audit_Emp_Record (date_of_change, old_Lname, new_Lname, old_dno, new_dno, old_ssn, new_ssn)
SELECT
GETDATE(), d.Name, i.Name, d.Number, i.Number, d.Mgr_Ssn, i.Mgr_Ssn
FROM
Inserted i
INNER JOIN
Deleted d ON i.PrimaryKey = d.PrimaryKey
In case of the DELETE, you'll just have only the Deleted table with the "old" values - so you'll need something like this:
CREATE TRIGGER dbo.EMPLOYEE_FKDno_DELETE
ON dbo.DEPARTMENT FOR DELETE
AS
BEGIN
INSERT INTO dbo.Audit_Emp_Record (date_of_change, old_Lname, new_Lname, old_dno, new_dno, old_ssn, new_ssn)
SELECT
GETDATE(), d.Name, NULL, d.Number, NULL, d.Mgr_Ssn, NULL
FROM
Deleted d
And in case of the INSERT, you'll just have only the Inserted table with the new values - so you'll need something like this:
CREATE TRIGGER dbo.EMPLOYEE_FKDno_DELETE
ON dbo.DEPARTMENT FOR DELETE
AS
BEGIN
INSERT INTO dbo.Audit_Emp_Record (date_of_change, old_Lname, new_Lname, old_dno, new_dno, old_ssn, new_ssn)
SELECT
GETDATE(), NULL, i.Name, NULL, i.Number, NULL, i.Mgr_Ssn
FROM
Inserted i
I Have the following scenario:
CREATE TABLE dbo.Orders
(
OrderID int IDENTITY (1,1) NOT NULL
, OrderVersion int DEFAULT(1)
, Customer varchar(30)
, ScheduleDate date
, PaymentOption int
);
CREATE TABLE dbo.OrdersItems
(
OrderItemsID int IDENTITY (1,1) NOT NULL
, OrderID int
, Product varchar(100)
, Qty int
, value decimal(18,2)
);
CREATE TABLE dbo.logOrders
(
OrderID int NOT NULL
, OrderVersion int DEFAULT(1)
, Customer varchar(30)
, ScheduleDate date
, PaymentOption int
);
CREATE TABLE dbo.logOrdersItems
(
OrderItemsID int NOT NULL
, OrderID int
, Product varchar(100)
, Qty int
, value decimal(18,2)
);
-- Insert values into the table.
INSERT INTO dbo.Orders (Customer , ScheduleDate, PaymentOption)
VALUES ('John', 2016-09-01, 1);
INSERT INTO dbo.OrdersItems( OrderId, Product, Qty, Value)
VALUES (1, 'Foo', 20, 35.658),
(1, 'Bla', 50, 100)
(1, 'XYZ', 10, 3589)
First Statement
UPDATE Orders set ScheduleDate = 2016-10-05 WHERE OrderId = 1
Second Statement
Delete From OrdersItems WHERE OrderItemsID = 2
UPDATE OrdersItems set Qty = 5 WHERE OrderItemsID = 1
Third Statement
Update Orders set PaymentOption = 2 WHERE OrderId = 1
Update OrdersItems set Value = 1050 WHERE OrderItemsID = 3
I am trying to figure out how to make a trigger that after each one of the Statements Sample above Insert on the log Tables the data before the changing. And setting the OrderVersion to OrderVersion + 1 on table Orders.
So on the log Tables I will have all versions after the later one.
Is it possible to make a single trigger to monitor both tables and execute getting the original data before the UPDATE, DELETE , INSERT statement to get the original data and INSERT on the logTables ?
Here comes a sample to explain better what result I want.
This is the Initial Data on table Orders and OrdersItems
If I make an Update on Orders ( any column ) or Make an Update,Insert,Delete on OrdersItems I need to Insert on respectively logTables the data on the image.
And with this I'll have on logOrders and logItems the original data and on the Orders and Items the altered data.
I Hope I could explain better what I mean.
You will need two triggers. The trigger for the Orders table handles Orders table update/delete. The trigger for the OrdersItems table does the same for OrdersItems. The triggers look like this:
For the Orders table:
CREATE TRIGGER dbo.Orders_trigger
ON dbo.Orders
AFTER DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.logOrders
SELECT * FROM DELETED;
INSERT INTO dbo.logOrdersItems
SELECT oi.* FROM OrdersItems oi
WHERE oi.OrderID IN (SELECT OrderId FROM DELETED);
END
GO
For OrdersItems:
CREATE TRIGGER dbo.OrdersItems_trigger
ON dbo.OrdersItems
AFTER DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
--Inerst the changed/deleted OrdersItems into the log
INSERT INTO dbo.logOrdersItems
SELECT * FROM DELETED
--Inserts the unchanged sibling OrdersItems records into the log
INSERT INTO dbo.logOrdersItems
SELECT oi.* FROM OrdersItems oi
WHERE oi.OrderId IN (SELECT DISTINCT OrderId FROM DELETED)
AND oi.OrderItemsID NOT IN (SELECT DISTINCT OrderItemsID FROM DELETED);
INSERT INTO dbo.logOrders
SELECT o.* FROM Orders o
WHERE o.OrderID IN (SELECT DISTINCT OrderId FROM DELETED);
END
GO
The Orders Trigger is fairly straightforward. Use the virtual DELETED table to insert the original version of the records into the log. Then join to the child OrdersItems records and insert them into the log as well. The way this is written, it will work even if you update or delete multiple Order records at a time.
The OrdersItems Trigger is a bit more complicated. You need to log the pre-chage version of the OrdersItems and Orders Records. But you also want (I think) to log the unchanged "sibling" OrdersItems records as well so that you have a complete picture of the records.
I know this is just your sample data, but you will want to add some kind of a timestamp to the records in the log tables. Otherwise you just end up with a bunch of duplicate rows and you cannot tell which is which. At the beginning of the trigger you can create a variable to hold the update datetime and then append that to your INSERT statement for the logs.
DECLARE #UpdateDateTime DATETIME;
SET #UpdateDateTime = GETUTCDATE();
Create table data with column StudentId (varchar type), Marks (Double). Create table data1 with column StudentId (varchar type), OldMarks (Double),NewMarks,Date.
Create trigger on data table.If mark is changed,create entry in data1 table for student with old marks,new marks & current date.
Here is the code I've tried:
CREATE TRIGGER marksss ON [dbo].[data] after UPDATE
AS declare #studentid int;
declare #marks int;
declare #xyz int;
declare #newmarks int;
declare #oldmarks int;
select #studentid=i.student_id from inserted i;
--to fetch inserted values
select #marks=i.marks from inserted i;
begin if update(marks) --set #oldmarks=#mark set #newmarks=#marks
insert into data1(student_id,new_marks,old_marks,date)
values (#studentid,#newmarks,#oldmarks,getdate()enter code here);
end
go
the problem is that it does not display old marks
I've managed to get what you want. First of all, you want to use an instead of trigger instead. Oracle has a before trigger which is what you ideally need however MSSQL doesn't have this feature so we have to do the passed in update manually too...
Here is the code with the table setup that I used, just changed to suit your needs.
CREATE TABLE A (ID INT IDENTITY PRIMARY KEY, SCORE INT)
CREATE TABLE B (ID INT FOREIGN KEY REFERENCES A(ID), SCORE INT, OLDSCORE INT, [date] DATETIME)
GO
CREATE TRIGGER marksss ON A INSTEAD OF UPDATE
AS
BEGIN
IF (SELECT A.SCORE FROM A INNER JOIN INSERTED I ON I.ID = A.ID) != (SELECT I.SCORE FROM INSERTED I)
BEGIN
INSERT INTO B(ID,SCORE,OLDSCORE,[date])
SELECT I.ID, I.SCORE, A.SCORE, GETDATE()
FROM INSERTED I
INNER JOIN A ON I.ID = A.ID
END
BEGIN
UPDATE A
SET SCORE = (SELECT I.SCORE FROM INSERTED I)
END
END