SQL Server: Create Trigger For Audit / History Table - sql

I am creating my first SQL Server Trigger, and looking to INSERT into a "History" table after insert into another table. I think I have most of the code written, but can't seem to get the syntax finished. The current format states that the "HistoryColumnName" and "HistoryNewValue" are invalid. I have tried a JOIN to the variable table #HistoryRecord but it doesnt really make sense as they are independent.
Code below:
CREATE TRIGGER CreateHardwareAssetHistoryRecord
ON HardwareAsset
AFTER INSERT AS
DECLARE
#HardwareAssetID UNIQUEIDENTIFIER,
#HardwareAssetTitle VARCHAR(256),
#HardwareAssetSerialNumber VARCHAR(256)
SET #HardwareAssetID = (SELECT HardwareAssetID FROM inserted)
SET #HardwareAssetTitle = (SELECT HardwareAssetTitle FROM inserted)
SET #HardwareAssetSerialNumber = (SELECT HardwareAssetSerialNumber FROM inserted)
DECLARE #HistoryRecord TABLE (HistoryColumnName VARCHAR(256) NOT NULL, HistoryNewValue VARCHAR(256) NOT NULL)
INSERT #HistoryRecord(HistoryColumnName,HistoryNewValue) VALUES('Asset Name', #HardwareAssetTitle)
INSERT #HistoryRecord(HistoryColumnName,HistoryNewValue) VALUES('Serial Number', #HardwareAssetSerialNumber)
BEGIN
WHILE EXISTS(SELECT HistoryColumnName,HistoryNewValue FROM #HistoryRecord)
INSERT INTO HardwareAssetHistory
(HardwareAssetHistoryChangeTypeID, HardwareAssetHistoryUpdatedByID, HardwareAssetHistoryColumnName, HardwareAssetHistoryOldValue, HardwareAssetHistoryNewValue, HardwareAssetHistoryHardwareAssetID)
SELECT '1', HardwareAssetCreatedByID, HistoryColumnName, '', HistoryNewValue, HardwareAssetID
FROM HardwareAsset
WHERE HardwareAssetID = #HardwareAssetID
END
GO
Any suggestions or help would be appreciated.

You can accomplish this without your temp table and referring back original HardwareAsset table by picking another value:
NOTE: as pointed by #nick.mcdermaid, below will not work when there are multiple rows as we are using variables.
CREATE TRIGGER CreateHardwareAssetHistoryRecord
ON HardwareAsset
AFTER INSERT AS
BEGIN
DECLARE #HardwareAssetID UNIQUEIDENTIFIER,
#HardwareAssetTitle VARCHAR(256),
#HardwareAssetSerialNumber VARCHAR(256),
#HardwareAssetCreatedByID INT --CHANGE TO WHAT IS DATA TYPE OF THIS
SELECT #HardwareAssetID = HardwareAssetID, #HardwareAssetTitle = HardwareAssetTitle
, #HardwareAssetSerialNumber = HardwareAssetSerialNumber
, #HardwareAssetCreatedByID = HardwareAssetCreatedByID FROM inserted
INSERT INTO HardwareAssetHistory(HardwareAssetHistoryChangeTypeID
, HardwareAssetHistoryUpdatedByID, HardwareAssetHistoryColumnName
, HardwareAssetHistoryOldValue, HardwareAssetHistoryNewValue
, HardwareAssetHistoryHardwareAssetID)
VALUES ('1', #HardwareAssetCreatedByID, 'Asset Name', '', #HardwareAssetTitle
, #HardwareAssetID),
('1', #HardwareAssetCreatedByID, 'Serial Number', '', #HardwareAssetSerialNumber
, #HardwareAssetID)
END
GO
UPDATE: This will work with multiple rows as well:
CREATE TRIGGER CreateHardwareAssetHistoryRecord
ON HardwareAsset
AFTER INSERT AS
BEGIN
DECLARE #insertedTemp AS TABLE (HardwareAssetID UNIQUEIDENTIFIER, HardwareAssetTitle VARCHAR(256), HardwareAssetSerialNumber VARCHAR(256), HardwareAssetCreatedByID INT)
INSERT INTO #insertedTemp(HardwareAssetID, HardwareAssetTitle, HardwareAssetSerialNumber, HardwareAssetCreatedByID)
SELECT HardwareAssetID, HardwareAssetTitle, HardwareAssetSerialNumber, HardwareAssetCreatedByID FROM inserted
INSERT INTO HardwareAssetHistory(HardwareAssetHistoryChangeTypeID
, HardwareAssetHistoryUpdatedByID, HardwareAssetHistoryColumnName
, HardwareAssetHistoryOldValue, HardwareAssetHistoryNewValue
, HardwareAssetHistoryHardwareAssetID)
SELECT '1', HardwareAssetCreatedByID, 'Asset Name', '', HardwareAssetTitle, #HardwareAssetID
FROM #insertedTemp
UNION
SELECT '1', HardwareAssetCreatedByID, 'Serial Number', '', HardwareAssetSerialNumber, HardwareAssetID
FROM #insertedTemp
END
GO

Related

Inserting data into table from table-valued parameter

I am trying to create a stored procedure to which is passed a TVP and then some data from the TVP is inserted into two tables.
I have already implemented the stored procedure, but only the second insert (the only one that does not read from the TVP) is working. The other two are not working (do not insert anything) and I can't seem to figure out why.
I have tried to create a dummy TVP in SQL Server and run the procedure there, but that also did not work. Is this being caused by the fact TVPs are readonly? I would assume not, since I am not actually inserting or updating data inside the TVP.
Is there a way to make this work?
Thank you for your assistance!
Table-valued parameter definition:
CREATE TYPE dbo.Ingredients
AS TABLE
(
Quantity int,
Measure nvarchar(50),
Ingredient nvarchar(50),
)
GO
Stored procedure:
ALTER PROCEDURE uspCreateRecipe
(#IDUser int,
#RecipeName nvarchar(50),
#Category nvarchar(50),
#Difficulty nvarchar(50),
#Duration nvarchar(50),
#ING dbo.Ingredients READONLY,
#Execution text)
AS
BEGIN
INSERT INTO dbo.Ingredients
VALUES ((SELECT Ingredient FROM #ING WHERE NOT EXISTS (SELECT Ingredient FROM #ING WHERE Ingredient IN (SELECT IngredientName FROM dbo.Ingredients))), 2)
INSERT INTO dbo.Recipes
VALUES (#IDUser, #RecipeName, NULL,
(SELECT IDDifficulty FROM dbo.Difficulty WHERE Difficulty = #Difficulty),
(SELECT IDDuration FROM dbo.Duration WHERE Duration = #Duration ),
NULL,
(SELECT IDCategory FROM dbo.Category WHERE CategoryName = #Category ),
#Execution , NULL, 2, GETDATE())
INSERT INTO dbo.Recipes_Ingredients
VALUES (SCOPE_IDENTITY(),
(SELECT Quantity FROM #ING),
(SELECT IDMeasure FROM dbo.Measure WHERE Measure IN (SELECT Measure FROM #ING)),
(SELECT IDIngredient FROM dbo.Ingredients WHERE IngredientName IN (SELECT Ingredient FROM #ING)))
END
Rather than using VALUES with sub-queries, just use SELECT.
Always list the columns you are inserting into. Its clearer and will reduce errors especially if you modify the table structure in future,
Your first query appeared to be overcomplicated - if indeed it worked at all.
Your third query should have thrown an error because you have multiple IN sub-queries which should have resulted in a "sub-query returned multiple results" error.
The text datatype is depreciated use varchar(max).
Normally you want to SET NOCOUNT, XACT_ABORT ON.
Always RETURN a status so your calling app knows whether it succeeded or not. 0 will be returned by default by I prefer to be explicit.
Semi-colon terminate all statements.
ALTER PROCEDURE uspCreateRecipe
(
#IDUser int
, #RecipeName nvarchar(50)
, #Category nvarchar(50)
, #Difficulty nvarchar(50)
, #Duration nvarchar(50)
, #ING dbo.Ingredients READONLY
, #Execution nvarchar(max) -- text is depreciated
)
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
INSERT INTO dbo.Ingredients ([Name], Col2)
SELECT Ingredient, 2
FROM #ING
WHERE Ingredient NOT IN (SELECT IngredientName FROM dbo.Ingredients);
INSERT INTO dbo.Recipes (IDUser, RecipeName, Col3, IDDifficulty, IDDuration, Col6, IDCategory, Col8, Col9, Col10, Co11)
SELECT #IDUser, #RecipeName, NULL, IDDifficulty
, (SELECT IDDuration FROM dbo.Duration WHERE Duration = #Duration)
, NULL
, (SELECT IDCategory FROM dbo.Category WHERE CategoryName = #Category)
, #Execution, NULL, 2, GETDATE()
FROM dbo.Difficulty
WHERE Difficulty = #Difficulty;
INSERT INTO dbo.Recipes_Ingredients (IDRecipe, Quantity, IDMeasureid, IDIngredient)
SELECT SCOPE_IDENTITY(), Quantity
, (SELECT IDMeasure FROM dbo.Measure WHERE Measure = I.Measure)
, (SELECT IDIngredient FROM dbo.Ingredients WHERE IngredientName = I.Ingredient)
FROM #ING I;
RETURN 0;
END;

DELETE Trigger for Microsoft SQL

I am creating two triggers. One to catch the inserted values for the Rewards table and one to catch the deleted values for the Rewards table. The triggers main function is to record what type of change was made into the the audit table.
Rewards Table:
enter image description here
Audit Table:
enter image description here
The SQL for the audit_insert_trigger is :
CREATE TRIGGER tr_rewards_insert
ON Rewards AFTER INSERT, UPDATE
AS IF UPDATE(category)
BEGIN
DECLARE #category char(6)
DECLARE #type_change char(20)
SELECT #category = (SELECT category FROM inserted)
IF EXISTS (SELECT * FROM inserted)
BEGIN
SELECT #type_change = 'Inserted'
END
INSERT INTO audit_rewards
VALUES (
#category, USER_NAME(), GETDATE(), #type_change)
END
CREATE TRIGGER tr_rewards_delete
ON Rewards AFTER UPDATE
AS IF UPDATE(category)
BEGIN
DECLARE #category char(6)
DECLARE #type_change char(20)
SELECT #category = (SELECT category FROM deleted)
IF EXISTS (SELECT * FROM deleted)
BEGIN
SELECT #type_change = 'Deleted'
END
INSERT INTO audit_rewards
VALUES (
#category, USER_NAME(), GETDATE(), #type_change)
END
My idea was to just replace where "inserted" was and put "deleted". I'm not really understanding 1. the logic as to why that will not work and 2. How to get the deleted values into the audit table and record that it was a delete.
Cannot make it in one trigger. Must be two independent triggers.
Your insert trigger is incorrect. I think it should be:
CREATE TRIGGER tr_rewards_insert
ON Rewards AFTER INSERT, UPDATE
AS IF UPDATE(category)
BEGIN
INSERT INTO audit_rewards
SELECT 'INSERTED, USER_NAME(), GETDATE(), category
FROM inserted;
END;
The DELETED trigger would basically be the same. Never assume that inserted and deleted have only a single row.
Figured out the answer:
CREATE TRIGGER tr_rewards_insert
ON Rewards AFTER INSERT, UPDATE
AS IF UPDATE(category)
BEGIN
DECLARE #category char(2)
DECLARE #type_change char(20)
SELECT #category = (SELECT category FROM inserted)
SELECT #type_change = 'Inserted'
INSERT INTO audit_rewards
VALUES (#category, USER_NAME(), GETDATE(), #type_change)
END
CREATE TRIGGER tr_rewards_delete
ON Rewards AFTER DELETE, UPDATE
AS IF UPDATE(category)
BEGIN
DECLARE #category char(2)
DECLARE #type_change char(20)
SELECT #category = (SELECT category FROM deleted)
SELECT #type_change =
INSERT INTO audit_rewards
VALUES (#category, USER_NAME(), GETDATE(), #type_change)
END

Add Column To one table on inserting value to another table

I have two tables Branch_TB and Branch_City.
Branch_TB :
CREATE TABLE Branch_TB(
Branch_Id int NULL,
Branch_Name varchar(50) NULL
)
Whenever there is an entry for Branch_Name, I want to add that entry as column name in Branch_City.
Is there any way for this. I don't know how to do this and haven't try any solution.
Thanks in advance.
You can achieve that using AFTER INSERT TRIGGER.
CREATE TABLE Branch_TB(
Branch_Id int NULL,
Branch_Name varchar(50) NULL
)
go
--drop table BranchCity
create table BranchCity(abc varchar(20))
go
create TRIGGER dbo.AddCol
ON Branch_TB
AFTER INSERT AS
BEGIN
DECLARE #NewVal VARCHAR(20)
DECLARE #AlterSQL VARCHAR(100)
CREATE TABLE #New
(
VAL VARCHAR(20)
)
INSERT INTO #New
select Branch_Name from inserted
select #NewVal = Val from #New
SET #AlterSQL = 'ALTER TABLE BranchCity add ' + #NewVal + ' VARCHAR(20)'
exec(#AlterSQL)
END
go
insert into Branch_Tb
values(1, 'City1')
go
insert into Branch_Tb
values(2, 'City2')
But in my opinion, you should re-evaluate your database design.
you need to create trigger on insert row in Branch_TB table.
In trigger you need to add code for Add column in require table.
How to create Trigger in Sql ? check this.
I understand it like you want to duplicate inserted value in some other table in column name. If this is true then you can try with OUTPUT:
INSERT INTO Branch_TB( Branch_Id, Branch_Name )
OUTPUT 'someValue1', Inserted.Branch_Name, 'someValue2'
INTO Branch_City ( someCol1, Name, comeCol2 )
VALUES ( 1, 'some name' )
you can try this
create proc proc_branch (#b_name varchar(50)
as
begin
exec('alter table Branch_city add column '+ #b_name + ';');
end
go
create trigger tr_branch
on branch_TB
for insert
as
begin
declare #branch_name varchar(50);
set #branch_name=(select branch_name from inserted)
exec proc proc_branch
end

SQL Server: How to use result from one INSERT for another INSERT

I have a stored procedure that is meant to update two tables at once.
My problem here is that the first table has an auto-incrementing ID column ("commentID") and my second table has a relationship on this so I need the newly created ID from the first INSERT in order to make the second INSERT.
I tried the following which I can save without errors but it doesnt execute as it should and does not update the tables as intended.
Can someone tell me what I am doing wrong here ?
My SQL:
ALTER PROCEDURE [dbo].[MOC_UpdateComment]
#imgID int,
#commentID int = '999999',
#comment nvarchar(1000),
#lastUpdate nvarchar(50),
#modBy varchar(50)
AS
BEGIN
DECLARE #temp AS TABLE
(
commentID int
)
SET NOCOUNT ON;
BEGIN TRANSACTION;
INSERT INTO MOC_BlogComments
(
imgID,
comment
)
OUTPUT inserted.commentID INTO #temp(commentID)
SELECT #imgID,
#comment
INSERT INTO MOC_LogComments
(
commentID,
lastUpdate,
modTime,
modBy
)
SELECT commentID,
#lastUpdate,
GETDATE(),
#modBy
FROM #temp
COMMIT TRANSACTION;
END
DECLARE #imgID INT,
#commentID INT = '999999',
#comment NVARCHAR(1000),
#lastUpdate NVARCHAR(50),
#modBy VARCHAR(50)
DECLARE #MORC_BlogComments AS TABLE
(
id INT IDENTITY(1, 1) NOT NULL,
imgid INT,
comment VARCHAR(100)
)
DECLARE #MORC_LogComments AS TABLE
(
commentid INT,
lastupdate DATETIME,
modtime DATETIME,
modby VARCHAR(100)
)
DECLARE #TEMP AS TABLE
(
commentid INT
)
SET nocount ON;
BEGIN TRANSACTION;
INSERT INTO #MORC_BlogComments
(imgid,
comment)
output inserted.id
INTO #TEMP(commentid)
VALUES (#imgID,
#comment)
INSERT INTO #MORC_LogComments
(commentid,
lastupdate,
modtime,
modby)
SELECT commentid,
#lastUpdate,
Getdate(),
#modBy
FROM #temp
SELECT *
FROM #MORC_LogComments
Function SCOPE_IDENTITY() returns the identity of last insert operation. You can use it to get the value which you need to use in second INSERT statement
You can use it like this in your statement:
INSERT INTO MORC_BlogComments (imgID, comment)
VALUES (#imgID, #comment)
INSERT INTO MORC_LogComments (commentID, lastUpdate, modTime, modBy)
VALUES (SCOPE_IDENTITY(), #lastUpdate, GETDATE(), #modBy)

Passing multiple parameters into a Table valued function

I have a table valued function as below. When I am trying to pass more than one parameter at the same time I am getting a error like "Function has too many arguments specified" .
CREATE FUNCTION [dbo].[GetCompanyUsers](#CompanyId BIGINT)
RETURNS #Users TABLE (Id BIGINT,Contact NVarchar(4000))
AS
BEGIN
INSERT INTO #Users(Id,Contact)
SELECT [Id]
,ISNULL([FirstName],'')+' ' +ISNULL([LastName],'') AS [Contact]
FROM [dbo].[CompanyAddressesContacts]
WHERE [CompanyId]=#CompanyId
ORDER BY ISNULL([FirstName],'')+' ' +ISNULL([LastName],'')
RETURN
END
What modifications I require in the above code so that it allows multiple values and I need to use the function in a "WHERE" condition in my dataset.
WHERE(Document_RFIs.CreatedBy IN
(SELECT Id FROM dbo.GetCompanyUsers(#CompanyId)))
This may help (but the fundamental problem is - passing a comma delimited string is something to be avoided unless absolutely necessary - which explains why you have received so few answers):-
--set nocount on
--create table #Document_RFIs (
-- CreatedBy varchar(50),
-- columna varchar(50),
-- columnb varchar(50),
-- columnc varchar(50)
--)
--insert into #Document_RFIs values
-- ('albert einstein','another','value',null),
-- ('marie curie','some',null,'tuna'),
-- ('isaac newton','why','not','provide'),
-- ('kepler','some','test','data'),
-- ('robert boyle','with','your','question'),
-- ('john dalton','it',null,'would'),
-- ('enrico fermi','make','helping','you'),
-- ('peter higgs','so','much','easier')
--create table #CompanyAddressesContacts (
-- companyid int,
-- firstname varchar(50),
-- lastname varchar(50)
--)
--insert into #CompanyAddressesContacts values (22,'albert','einstein')
--insert into #CompanyAddressesContacts values (23,'marie','curie')
--insert into #CompanyAddressesContacts values (23,'isaac','newton')
--insert into #CompanyAddressesContacts values (24,null,'kepler')
--insert into #CompanyAddressesContacts values (25,'robert','boyle')
--insert into #CompanyAddressesContacts values (25,'enrico','fermi')
--insert into #CompanyAddressesContacts values (26,'peter','higgs')
declare #ids varchar(1024)
set #ids='23,24,25'
create table #id (
companyid int
)
declare #pos int
while DATALENGTH(#ids)>0 begin
set #pos=charindex(',',#ids)
if #pos>0 begin
insert into #id values (left(#ids,#pos-1))
set #ids=SUBSTRING(#ids,#pos+1,DATALENGTH(#ids))
end else begin
insert into #id values (#ids)
set #ids=''
end
end
select d.*
from #Document_RFIs d
where exists(
select cac.*
from #CompanyAddressesContacts cac
join #id i on i.companyid=cac.companyid
where isnull(cac.firstname+' ','')+isnull(cac.lastname,'')=d.CreatedBy
)
--drop table #id
--drop table #Document_RFIs
--drop table #CompanyAddressesContacts
I would do something like this:
First convert your #CompanyId to rows
WITH CompanyIds AS (
SELECT Id
FROM CompanyTable -- Same as the source of the #CompanyId
WHERE Id IN (#CompanyId)
)
Then extract all users
,Users AS (
SELECT UserId
FROM CompanyIds
CROSS APPLY (
SELECT Id AS UserId
FROM dbo.GetCompanyUsers(CompanyIds.Id)
) AS CA1
)
And then use it in the where statement
WHERE Document_RFIs.CreatedBy IN (SELECT UserId
FROM Users)