Creating a trigger in SQL Server Management Studio 2012 - sql-server

Given the following question based on the database below.
a) Create a trigger that will automatically update the quantity on hand each time that a product is sold as listed as a row in the LINE table. In other words, a line row is inserted as part of an invoice. Each line is linked to a product. Decrement the quantity on hand (P_QOH) by the number ordered.
http://i.stack.imgur.com/wWX17.png

You'll have to create insert trigger on table Line
CREATE TRIGGER [dbo].[TriggerUpdateQty] ON [dbo].[Line]
AFTER INSERT
AS
...
then you'll need to run an update Product table joining with inserted table. inserted table represents data inserted that triggered the trigger.
Update Product set Product.P_QOH = (Product.P_QOH - inserted.Line_units)
from Prduct inner join inserted on Product.P_Code = inserted.P_Code

Related

SQL Server trigger failing for row inserts in quick succession

I have looked around on SO and found many similar questions:
SQL Server A trigger to work on multiple row inserts
SQL trigger multiple insert update
Trigger to handle multiple row inserts and updates
update multiple rows with trigger after insert (sql server)
Trigger not working when inserting multiple records
But I am still having issues with my trigger to update multiple rows when Inserting multiple rows into a table.
Outline of code
I have a Reservation table which has a ReservationID and TourComponentID columns respectively. When I insert into the reservations table I have the following trigger to update the TourComponent table with the ReservationID from the row just inserted into the reservation table with matching TourComponentID:
CREATE TRIGGER [TR_Reservation_CurrentReservation] ON [Reservation] AFTER INSERT AS
UPDATE tc
SET tc.[CurrentReservationId] = I.ReservationID
FROM [tour].[TourComponent] tc
JOIN INSERTED I on I.TourComponentID = tc.TourComponentID
End
This trigger works perfectly when updating one tourComponent to have a new reservation (inserting one row into the reservation table). However if I try update multiple tour components (inserting multiple rows into the reservation table to update multiple rows in the TourComponent table) only the first tour component gets updated, any rows.
Other answers and research has shown me that
Triggers are NOT executed once per row but rather as a set based
operation so executed only ONCE for the entire DML operation. So you
need to treat it like any other update date with join statement.
So I would have expected my joining on the INSERTED table to have handled multiple rows or have I misunderstood this?
Interestingly if I log the trigger variables for TourComponentID, ReservationID and INSERTED rowcount to a temp table foo I can see two records are inserted into my temp table, each with a rowcount of 1.
Using sql profiler to catch the actual sql executed at runtime and running this manually against the database I get two rows updated as desired. It is only when using Entity Framework to update the database ie running the application do I find only one row is updated.
I have tried logging the values to a table FOO in the trigger
INSERT INTO FOO (TourComponentID, ReservationID, Rowcounts )
SELECT i.TourComponentID, I.ReservationID, 1 --#ReservationId
FROM
INSERTED I
This logs two rows with a rowcount of 1 each time and the correct tourcomponentsID and reservationID but the TourComponent table still only has one row updated.
Any suggestions greatly appreciated.
UPDATE
Tour component ID's are passed as strings in an Ajax post to the MVC Action where tour component models are populated and then passed to be updated one at a time in the code
public void UpdateTourComponents(IEnumerable<TourComponent> tourComponents)
{
foreach (var tourComponent in tourComponents)
{
UpdateTourComponent(tourComponent);
}
}
here is the call to UpdateTourComponent
public int UpdateTourComponent(TourComponent tourComponent)
{
return TourComponentRepository.Update(tourComponent);
}
and the final call to Update
public virtual int Update(TObject TObject)
{
Dictionary<string, List<string>> newChildKeys;
return Update(TObject, null, out newChildKeys);
}
So the Inserts are happening one at a time, hence my trigger is being called once per TourComponent. This is why when I count the ##Rowcount in INSERTED and log to Foo I get value of 1. When I run the inserts manually I get the correct expected results so I would agree with #Slava Murygin tests that the issue is probably not with the trigger itself. I thought it might be a speed issue if we are firing the requests one after the other so I put a wait in the trigger and in the code but this did not fix it.
Update 2
I have used a sql profiler to capture the sql that is run when only the first insert triggers work.
Interestingly when the EXACT same sql is then run in SQL Management Studio the trigger works as expected and both tour components are updated with the reservation id.
Worth mentioning also that all constraints have been removed off all tables.
Any other suggestions what might be causing this issue?
You have different problem than that particular trigger. Try to look at the table name you are updating "[tour].[TourComponent]" or "[dbo].[TourComponent]".
I've tried your trigger and it perfectly works:
use TestDB
GO
IF object_id('Reservation') is not null DROP TABLE Reservation;
GO
IF object_id('TourComponent') is not null DROP TABLE TourComponent;
GO
CREATE TABLE Reservation (
ReservationID INT IDENTITY(1,1),
TourComponentID INT
);
GO
CREATE TABLE TourComponent (
CurrentReservationId INT,
TourComponentID INT
);
GO
CREATE TRIGGER [TR_Reservation_CurrentReservation] ON [Reservation] AFTER INSERT AS
UPDATE tc
SET tc.[CurrentReservationId] = I.ReservationID
FROM [TourComponent] tc
JOIN INSERTED I on I.TourComponentID = tc.TourComponentID
GO
INSERT INTO TourComponent(TourComponentID)
VALUES (1),(2),(3),(4),(5),(6)
GO
INSERT INTO Reservation(TourComponentID)
VALUES (1),(2),(3),(4),(5),(6)
GO
SELECT * FROM Reservation
SELECT * FROM TourComponent
So the underlying problem was down to Entity Framework.
this.Property(t => t.CurrentReservationId).HasColumnName("CurrentReservationId");
Is one property for the SQL Data access layer. This was being cached and was causing the data being read out of the db to not be the latest current, thus if we have an insert in the Reservations table the second insert will be overwritten by the cached values which in my case were NULL.
Changing the line to this resolves the problem and makes the trigger work as expected.
this.Property(t => t.CurrentReservationId).HasColumnName("CurrentReservationId").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
See more info on HasDatabaseGeneratedOption

Recording info in SQL Server trigger

I have a table called dsReplicated.matDB and a column fee_earner. When that column is updated, I want to record two pieces of information:
dsReplicated.matDB.mt_code
dsReplicated.matDB.fee_earner
from the row where fee_earner has been updated.
I've got the basic syntax for doing something when the column is updated but need a hand with the above to get this over the line.
ALTER TRIGGER [dsReplicated].[tr_mfeModified]
ON [dsReplicated].[matdb]
AFTER UPDATE
AS
BEGIN
IF (UPDATE(fee_earner))
BEGIN
print 'Matter fee earner changed to '
END
END
The problem with triggers in SQL server is that they are called one per SQL statement - not once per row. So if your UPDATE statement updates 10 rows, your trigger is called once, and the Inserted and Deleted pseudo tables inside the trigger each contain 10 rows of data.
In order to see if fee_earner has changed, I'd recommend using this approach instead of the UPDATE() function:
ALTER TRIGGER [dsReplicated].[tr_mfeModified]
ON [dsReplicated].[matdb]
AFTER UPDATE
AS
BEGIN
-- I'm just *speculating* here what you want to do with that information - adapt as needed!
INSERT INTO dbo.AuditTable (Id, TriggerTimeStamp, Mt_Code, Old_Fee_Earner, New_Fee_Earner)
SELECT
i.PrimaryKey, SYSDATETIME(), i.Mt_Code, d.fee_earner, i.fee_earner
FROM Inserted i
-- use the two pseudo tables to detect if the column "fee_earner" has
-- changed with the UPDATE operation
INNER JOIN Deleted d ON i.PrimaryKey = d.PrimaryKey
AND d.fee_earner <> i.fee_earner
END
The Deleted pseudo table contains the values before the UPDATE - so that's why I take the d.fee_earner as the value for the Old_Fee_Earner column in the audit table.
The Inserted pseudo table contains the values after the UPDATE - so that's why I take the other values from that Inserted pseudo-table to insert into the audit table.
Note that you really must have an unchangeable primary key in that table in order for this trigger to work. This is a recommended best practice for any data table in SQL Server anyway.

How to write query/trigger that satisfies following scenario?

Solution required in SQL Server.
Suppose there are 2 tables
TICKET table with following columns:
item_id - PK
ticket_cost
TICKET_PAST with columns:
price - FK
ticket_new_cost
The question is to write a trigger such that whenever a price in the TICKET table is inserted, updated or deleted a new row should be generated by trigger?
Using an after update trigger, and accessing the new value from inserted and the old value from deleted:
create trigger dbo.item_price_update_trigger
on dbo.item
after update as
begin;
set nocount on;
insert into item_hist (item_id, price, new_price)
select i.item_id, d.price, i.price
from inserted i
inner join deleted d
on i.item_id = d.item_id
end;
go
rextester demo: http://rextester.com/PBB85814
This trigger answers your question, but would most likely be just one of three triggers for a complete auditing/history solution. You may want a trigger for after insert to store the initial insert of an item, and an after delete trigger to record the final price of an item that was deleted.
You would also probably want to include the dates of when these actions occurred, so you could query what the effective price was of an item at a given time.
For a somewhat automated option of creating audit/history tables and related triggers, this article and the related scripts may be helpful: Quick And Easy Audit Tables - Dave Britten
Reference:
Use the inserted and deleted Tables
create trigger (Transact-SQL)
DML Triggers

Stored procedure copying from one to another

I have a table called Sales and another SalesHistory. SalesHistory is the replica of Sales table.
Now the Sales table can be dropped anytime and recreated again with new columns being added and old columns being renamed to something different. I had written a stored procedure which copies data from the sales table to saleshistory table depending upon a condition if it needs insert or update
Now I am bit lost: how do I fix the issue that once sales table is dropped and recreated, how can I amend those changes to the saleshistory table?
Any idea or or same code, I can share my code off stored procedure if need but that is pretty simple
Here is the code
Insert into SalesHistory (Cusip, KeyFeatures1, KeyFeatures2, KeyFeatures3, KeyFeatures4, KeyFeatures5, KeyFeatures6, KeyFeatures7, KeyRisks1, KeyRisks2, KeyRisks3, Comments1, Comments2, Comments3)
select
Cusip, KeyFeatures1, KeyFeatures2, KeyFeatures3, KeyFeatures4,
KeyFeatures5, KeyFeatures6, KeyFeatures7, KeyRisks1, KeyRisks2,
KeyRisks3, Comments1, Comments2, Comments3
from
Sales
where
not exists (SELECT 1 FROM SalesHistory WHERE cusip = Sales.cusip)
UPDATE Hist
SET Cusip = A.Cusip,
KeyFeatures1 = A.KeyFeatures1,
KeyFeatures2 = A.KeyFeatures2,
KeyFeatures3 = A.KeyFeatures3,
KeyFeatures4 = A.KeyFeatures4,
KeyFeatures5 = A.KeyFeatures5,
KeyFeatures6 = A.KeyFeatures6,
KeyFeatures7 = A.KeyFeatures7,
KeyRisks1 = A.KeyRisks1,
KeyRisks2 = A.KeyRisks2,
KeyRisks3 = A.KeyRisks3,
Comments1 = A.Comments1,
Comments2 = A.Comments2,
Comments3 = A.Comments3
FROM
SalesHistory Hist
INNER JOIN
Sales A ON A.cusip = Hist.cusip
I have already explained in my question what I am trying to do
Assuming you are after the data when the table is dropped ...?
Unfortunately you can not create an instead of trigger for DDL statements such as drop table. So you can't simply copy the data before the table is dropped. However, you could create an After Insert, Update trigger on the Sales table that inserts the record straight away to SalesHistory. That way, when the Sales table is randomly dropped, you will have the data already in the SalesHistory table.
Note: Please be careful with triggers as they can produce unwanted results depending on your application. Also, if you have no control over the schema of the Sales table, then you are going to find it difficult to copy the table data for all columns to the SalesHistory table and ensure that it works for the lifetime of the application.
However, if there is a predefined list of columns in Sales table that will never change, then you can perform what you are after and simply just copy those columns that never change.

Logging deletion of rows in table sql server

I have been searching for a way to log the deletion of rows from a table.
Tried this Log record changes in SQL server in an audit table but it didn't help me.
I have a song list database, the log table has the columns: Title / Artist / Year / Position / SentinDate .
There is a list with songs from the years 1999 to 2014, and every year has 2000 songs (top2000 is what it is called in The Netherlands).
Basically what the log table should look like once a certain Year has been deleted:
I need a basic way trigger-log when someone deletes a certain year from the list of 1999-2014.
I hope to have informed enough for you to understand, if not I will try to explain in more detail.
A trigger rejects or accepts each data modification transaction as a whole.
Using a correlated subquery in a trigger can force the trigger to examine the modified rows one by one.
Examples
A. Use an AFTER INSERT trigger
The following example assumes the existence of a table called newsale in the pubs database. This the CREATE statement for newsale:
CREATE TABLE newsale
(stor_id char(4),
ord_num varchar(20),
date datetime,
qty smallint,
payterms varchar(12),
title_id tid)
If you want to examine each of the records you are trying to insert, the trigger conditionalinsert analyzes the insert row by row, and then deletes the rows that do not have a title_id in titles.
CREATE TRIGGER conditionalinsert
ON sales
AFTER INSERT AS
IF
(SELECT COUNT(*) FROM titles, inserted
WHERE titles.title_id = inserted.title_id) <> ##ROWCOUNT
BEGIN
DELETE sales FROM sales, inserted
WHERE sales.title_id = inserted.title_id AND
inserted.title_id NOT IN
(SELECT title_id
FROM titles)
PRINT 'Only sales records with matching title_ids added.'
END
When unacceptable titles have been inserted, the transaction is not rolled back; instead, the trigger deletes the unwanted rows. This ability to delete rows that have been inserted relies on the order in which processing occurs when triggers are fired. First, rows are inserted into the sales table and the inserted table, and then the trigger fires.
Simply create an INSTEAD OF DELETE trigger ! In that trigger, you have a "virtual" table called deletedwhich contains all records which are to be deleted.
So in the trigger, you can just insert all records contained in deleted to your log table, and then you delete the records from our table. (this will then be a DELETE statement with a join to the deleted table)

Resources