Create a history trigger SQL Server 2012 - sql-server

I have no idea how to create this history trigger.
Imagine that we would like to store a history of price changes for each item. To do so, we would first need to create an Item_price_history table that stores at a minimum a reference to the item, its old price, 
its new price, and the date of the change. We could then define a trigger on the Item 
table that updates the Item_price_history table whenever an item price is updated. 
I have created two tables as below:
CREATE TABLE ITEM(
item_id DECIMAL(10) NOT NULL,
description VARCHAR(30),
price DECIMAL(10),
PRIMARY KEY (item_id));
CREATE TABLE Item_price_history (
history_id DECIMAL(10) NOT NULL,
item_id DECIMAL(10) NOT NULL,
old_price DECIMAL(10,2),
new_price DECIMAL(10,2),
date_of_change DATE,
PRIMARY KEY (HISTORY_ID),
FOREIGN KEY (ITEM_ID) REFERENCES item);

Here is the trigger:
CREATE TRIGGER trHistory ON dbo.ITEM
FOR UPDATE --only for update
AS
BEGIN
IF UPDATE(price) --if price is really updated and not the other columns
BEGIN
INSERT INTO dbo.Item_price_history(item_id, old_price, new_price, date_of_change)
SELECT d.item_id, d.price, i.price, GETDATE()
FROM Deleted d
JOIN Inserted i ON d.item_id = i.item_id
END
END

Try this.
CREATE TRIGGER Sample ON Item_price_history
FOR UPDATE
AS
BEGIN
DECLARE #nOldPRICE AS DECIMAL(10)
SET #nOldPRICE = (SELECT price FROM DELETED)
INSERT INTO Item_price_history (item_id, old_price, new_price, date_of_change)
SELECT item_id, #nOldPRICE, new_price, GETDATE() FROM INSERTED
END

For sure you can use a trigger. But why?
You can update the history table when you update the main table and keep all the business logic in one place.
I remember in old days, during a programming SQL2000 seminar that it was told:
"Avoid the use of triggers. In most cases they try to solve design problems."
Some info

Related

SQL Server how to populate data to table2 when table1 updated

I'm creating a table to store cars, and another table to store the time when the new car was added to the database, can someone please explain to me how to create the relationship to update time automatically when the car was created.
Create table Cars
(
CarID int Primary Key identity(1,1),
Make varchar(50),
Model varchar(50),
Colour varchar(59)
)
create Table TimeLogs
(
AddedOn SYSDATETIME(),
CarId int unique foreign key references Cars(CarId)
)
I would solve this by not using a second table for what should be a column in the Cars table. The table would be designed more appropriately like this.
Create table Cars
(
CarID int Primary Key identity(1,1),
Make varchar(50),
Model varchar(50),
Colour varchar(59),
AddedOn datetime default SYSDATETIME()
)
To automatically update one table whenever another table is updated, you need to use a TRIGGER.
You needs to use insert trigger for the same, as below
CREATE TRIGGER yourNewTrigger ON yourSourcetable
FOR INSERT
AS
INSERT INTO yourDestinationTable
(col1, col2 , col3, user_id, user_name)
SELECT
'a' , default , null, user_id, user_name
FROM inserted
go

Explicitly Set ValidFrom in Temporal Table

I'm trying to set the ValidFrom range for the current record in a temporal table. I'm doing this because I'm rebuilding history from another system (non SQL) into a data warehouse so the current version of records may be "as of" a date that's in the past. If I can't get this to work, my fall back is to add a row in the history table that fills in the gap but I'm thinking there's a way to get this to work. Maybe there are some ways with alter columns?
/******** CURRENT TIME=2018-03-10 15:32:26 *****/
CREATE TABLE TestHist(
ID int NOT NULL,
Name varchar(max),
--Temporal Stuff
ValidFrom datetime2(7) NOT NULL,
ValidTo datetime2(7) NOT NULL
)
GO
CREATE TABLE Test(
ID int IDENTITY(1,1) NOT NULL,
Name varchar(max),
--Temporal Stuff
ValidFrom datetime2(7) GENERATED ALWAYS AS ROW START NOT NULL,
ValidTo datetime2(7) GENERATED ALWAYS AS ROW END NOT NULL,
PRIMARY KEY CLUSTERED (ID ASC) ,
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH( SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.TestHist ) )
GO
ALTER TABLE Test SET (SYSTEM_VERSIONING = OFF)
go
--THIS WORKS BUT SETS THE VALIDFROM TO CURRENT TIME
insert into Test(name) values ('fred')
--AND BTW, THIS IS HOW I LOAD THE HISTORY (THIS WORKS TOO)
insert into TestHist(ID,Name,ValidFrom,ValidTo) values (1,'joe',null,'1/1/18','1/15/18')
insert into TestHist(ID,Name,ValidFrom,ValidTo) values (1,'steve','fred','2/1/18','3/1/18')
But the problem is that it sets the current ValidFrom time arbitrarily to when you do your insert statement:
select * from test
ID Name ParentName ValidFrom ValidTo
1 fred NULL 2018-03-10 15:32:26.4403041 9999-12-31 23:59:59.9999999
And here's what I wish I could do:
--THIS DOESN'T WORK
insert into Test(name,ValidFrom,ValidTo) values ('fred','2/1/18','9999-12-31 23:59:59.997')
I get this error:
Msg 13536, Level 16, State 1, Line 38
Cannot insert an explicit value into a GENERATED ALWAYS column in table 'CodeAnalytics.dbo.Test'. Use INSERT with a column list to exclude the GENERATED ALWAYS column, or insert a DEFAULT into GENERATED ALWAYS column.
You cannot update ValidFrom on the temporal table. However, you can update ValidFrom on the history table that keeps track of changes. You create a record there by changing any value in the temporal table.
So you can do following steps:
Change anything in rows of your temporal table where you want to change the value of the ValidFrom column. This step creates a record in the history table for every changed record in the original table.
Set system versioning off for your temporal table.
Update ValidFrom in your history table.
Set system versioning back on for your temporal table.
Edit: oops. it's 2019 now. anyway, i needed to do this, so leaving here in case anyone else finds useful.
maybe something like this is what you're looking for?
CREATE TABLE TestHist(
ID int NOT NULL,
Name varchar(max),
--Temporal Stuff
ValidFrom datetime2(7) NOT NULL,
ValidTo datetime2(7) NOT NULL
)
GO
CREATE TABLE Test(
ID int IDENTITY(1,1) NOT NULL,
Name varchar(max),
--Temporal Stuff
ValidFrom datetime2(7) GENERATED ALWAYS AS ROW START NOT NULL,
ValidTo datetime2(7) GENERATED ALWAYS AS ROW END NOT NULL,
PRIMARY KEY CLUSTERED (ID ASC) ,
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
WITH( SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.TestHist ) )
GO
ALTER TABLE Test SET (SYSTEM_VERSIONING = OFF);
insert into TestHist(ID,Name,ValidFrom,ValidTo) values (1,'steve','2/1/18','3/1/18')
insert into TestHist(ID,Name,ValidFrom,ValidTo) values (1,'joe','1/1/18','1/15/18')
SET IDENTITY_INSERT Test ON;
insert into Test(id, name) values (1, 'fred')
--after dropping period one can update validfrom on temporal table from max history
alter table Test DROP PERIOD FOR SYSTEM_TIME;
GO
update test set validfrom =(select max(validto) from TestHist where id=test.ID);
--now add period and turn system versioning back on
alter table test ADD PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
GO
alter table test set (system_versioning = on (HISTORY_TABLE=dbo.TestHist));
GO
--think this gives what you want
select * from test for system_time all

How to create a before delete trigger in SQL Server?

I want to create a before delete trigger. When I delete a record from a table that record has to be inserted into a history table. How can I do this in SQL Server?
In this situation, you're probably better off doing a regular "after" trigger. This is the most common approach to this type of situation.
Something like
CREATE TRIGGER TRG_AUD_DEL
ON yourTable
FOR DELETE
AS
INSERT INTO my_audit_table (col1, col2, ...)
SELECT col1, col2...
FROM DELETED
What will happen is, when a record (or records!) are deleted from your table, the deleted row will be inserted into my_audit_table The DELETED table is a virtual table that contains the record(s) as they were immediately prior to the delete.
Also, note that the trigger runs as part of the implicit transaction on the delete statement, so if your delete fails and rolls back, the trigger will also rollback.
You could also use INSTEAD OF DELETE
CREATE TRIGGER dbo.SomeTableYouWhatToDeleteFrom
ON dbo.YourTable
INSTEAD OF DELETE
AS
BEGIN
-- Some code you want to do before delete
DELETE YourTable
FROM DELETED D
INNER JOIN dbo.YourTable T ON T.PK_1 = D.PK_1
END
It could be done in following steps for let’s say in this example I am using customer table:
CREATE TABLE CUSTOMERS(
ID INT NOT NULL,
NAME VARCHAR (20) NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR (25) ,
LAST_UPDATED DATETIME,
PRIMARY KEY (ID)
);
Create History:
CREATE TABLE CUSTOMERS_HIST(
ID INT NOT NULL,
NAME VARCHAR (20) NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR (25) ,
LAST_UPDATED DATETIME,
PRIMARY KEY (ID)
);
Trigger on source table like below on delete event:
CREATE TRIGGER TRG_CUSTOMERS_DEL
ON CUSTOMERS
FOR DELETE
AS
INSERT INTO CUSTOMERS_HIST (ID, NAME, AGE, ADDRESS, LAST_UPDATED)
SELECT ID, NAME, AGE, ADDRESS, LAST_UPDATED
FROM DELETED
Try a trigger that executes before the delete and throws an error when the condition is not met.
CREATE TRIGGER [dbo].[TableName_PreventDeleteAndUpdate]
ON dbo.TableName
FOR DELETE, UPDATE -- runs before deletes and updates
AS
BEGIN
IF (APP_NAME() <> 'SomeApp')
BEGIN
RAISERROR ('Only delete/update with SomeApp', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END

Populate CreatedOn, ModifiedOn columns

I am building a WCF service with a SQL Server, which will be consumed by a WPF app. I want my database tables to have columns like:
CreatedOn, CreatedBy, LastModifiedOn, LastModifiedBy
Is there a way to create these authomatically, or if not I can create them, but is it possible somehow their values to be populated by SQL server?
Thanks
Made a couple of assumptions here - that CreatedBy/ModifiedBy would be populated with a system variable such as SUSER_SNAME() and that modified values should reflect the same values as created, initially. Assuming this base table:
USE tempdb;
GO
CREATE TABLE dbo.foo(fooID INT PRIMARY KEY);
GO
Make these modifications:
ALTER TABLE dbo.foo ADD CreatedOn SMALLDATETIME
NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dbo.foo ADD CreatedBy NVARCHAR(32)
NOT NULL DEFAULT SUSER_SNAME();
ALTER TABLE dbo.foo ADD ModifiedOn SMALLDATETIME
NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dbo.foo ADD ModifiedBy NVARCHAR(32)
NOT NULL DEFAULT SUSER_SNAME();
GO
Now you just need a trigger to handle subsequent updates:
CREATE TRIGGER dbo.foo_audit
ON dbo.foo
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM deleted)
BEGIN
UPDATE f
SET ModifiedOn = CURRENT_TIMESTAMP,
ModifiedBy = SUSER_SNAME()
FROM dbo.foo AS f
INNER JOIN inserted AS i
ON f.fooID = d.fooID;
END
END
GO
If you need the username to be passed in from the app, then WCF is going to have to help with that.

getting the autoincremnt value after inserting record using SQL Server

I'm developing a project using VB.NET connected to SQL Server database
and in this project i need to get the value of column called "ID" after inserting a record to
the database immediately.
thanx.
CREATE TABLE dbo.SomeTable (
ID int IDENTITY
,[NAME] varchar(50)
);
go
INSERT INTO dbo.SomeTable ([Name])
SELECT 'Joe' UNION
SELECT 'Jim' UNION
SELECT 'JIll'
;
SELECT ident_current('dbo.SomeTable') AS [LastID_1]
,##IDENTITY AS [LastID_2]
,scope_identity() AS [LastID_3]
;
USES:
ident_current ('TableName') for a specific table, not limited by scope and session
##IDENTITY last ID in current session
scope_identity() last ID in current session, current scope
Have a look at SCOPE_IDENTITY (Transact-SQL)
INSERT
INTO [News]
(
LanguageID,
Title,
Short,
[Full],
Published,
AllowComments,
CreatedOn
)
VALUES
(
#LanguageID,
#Title,
#Short,
#Full,
#Published,
#AllowComments,
#CreatedOn
)
set #NewsID=##identity
SCOPE_IDENTITY, IDENT_CURRENT, and ##IDENTITY are similar functions because they return values that are inserted into identity columns.
TABLE
EMPID ( AUTOINCREAMENT)
NAME VARCHAR(50)
INSERT INTO TABLE ( NAME) VALUES ('RAMKUMAR')
SELECT ##IDENTITY
use ##identity
returns the autoincreament value
You can create temp table variable and create a column called StuId.
--Student table creation
CREATE TABLE [dbo].[StuTable](
[StuId] [int] IDENTITY(1,1) NOT NULL,
[StuName] [nvarchar](50) NOT NULL,
[DOB] [date] NULL
)
DECLARE #TempTable TABLE(StuId INT)
INSERT INTO [dbo].[StuTable]([StuName]
,[DOB])
OUTPUT inserted.StuId INTO #TempTable
VALUES ('Peter', '1979-01-01')
SELECT StuId FROM #TempTable --Get the auto increment Id

Resources