Audit Trail Trigger generation - sql

Working on Audit Trail system and decided to do it with Shadow/History table with triggers.
Followed this Audit Trail Article and trying to use CodeSmith Generator tool
I dont understand how it creates the history table and the trigger.
Is any one could understand how it works and help me on it.
I tried google to understand it. But there is no clear example
Nothing is clear with the below to me
Audit Table looks like this
CREATE TABLE [dbo].[<%= AuditTableName %>] (
[ChangeLogID] [int] IDENTITY (1, 1) ,
[OperationType] [varchar] (10) NOT NULL ,
[ChangeTimestamp] [datetime] NOT NULL ,
[MadeBy] [varchar] (6) NOT NULL ,
[TableChanged] [varchar] (50) NOT NULL
) ON [PRIMARY]
Detail Table looks like this
CREATE TABLE [dbo].[<%= AuditFieldTableName %>] (
[FieldName] [varchar] (50) NOT NULL ,
[ChangeLogID] [int] NOT NULL ,
[BeforeValue] [sql_variant] NOT NULL ,
[AfterValue] [sql_variant] NOT NULL
) ON [PRIMARY]
How to generate this and add trigger and how can i insert AuditFieldTableName values?

As we have different types of columns in multiple tables, the audit table that you have specified wouldn't really suffice the cause. I suggest the following audit table:
TABLE auditEntry (
auditEntryId INTEGER PRIMARY KEY,
operationType VARCHAR(10) NOT NULL, -- For INSERT / UPDATE / DELETE
changeTimestamp DATETIME NOT NULL,
madeBy VARCHAR(50) NOT NULL,
tableName VARCHAR(30) not null, -- stores the name of the table changed
columnName VARCHAR(30) not null, -- stores the name of column changed
oldInt INTEGER,
newInt INTEGER,
oldVarchar VARCHAR(100),
newVarchar VARCHAR(100),
oldDate DATETIME,
newDate DATETIME)
Now I think it's a cakewalk for you to write row level triggers for INSERT, UPDATE and DELETE on your tables, if you have working knowledge of writing them. Search MSDN on how to write such triggers and you will be fine.

Related

Replacing a trigger with a stored procedure

I'm trying to replace a trigger statement with a stored procedure since enabled triggers are not allowed when using the tables in microsoft powerapps.
Simplified, I have to tables:
KPI_Dim (KPI_ID [PK] , KPIName, KPIGroup...)
KPICurrent_Fact (KPI_key [FK i.e KPI_Dim[KPI_ID], KPICurrent_ID, KPI_Value...)
Currently, for every new record in KPI_Dim my trigger adds a new row in KPICurrent_Fact with the FK and an autoincremented PK. The rest of the columns e.g. KPI_Value are supposed to be empty.
My simple trigger looks like this:
CREATE TRIGGER [dbo].[trg_insert_newKPI]
ON [dbo].[KPI_Dim]
FOR INSERT AS
INSERT INTO KPICurrent_Fact (KPI_key)
SELECT KPI_ID
FROM INSERTED
Now, I want to create a stored procedure that can achieve exactly the same. I have tried to find a solution myself but I'm new to stored procedures and could not find anything that would replicate a trigger.
I'm using SSMS v.18.4.
Thank you for any suggestions.
EDIT
Added Table creation and insert into statement code.
/* Create KPI_Dim table*/
CREATE TABLE [dbo].[KPI_Dim](
[KPI_ID] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL,
[KPIName] [varchar](200) NOT NULL,
[KPIDescription] [varchar](500) NULL,
[KPIGroup] [varchar](100) NOT NULL,
[KPISubGroup] [varchar](100) NULL,
[KPIOwner] [varchar] (50) NOT NULL,
[DateCreated] DATETIME NULL DEFAULT(GETDATE())
)
/* Example data */
INSERT INTO [dbo].[KPI_Dim]
(
KPIName,
KPIDescription,
KPIGroup,
KPISubGroup,
KPIOwner
)
VALUES
('TestKPIName','testtest','TestGroup', 'TestSubGroup', 'TestOwner');
You can go for OUTPUT Clause and insert into table variable. From the table variable, you can insert into fact table.
CREATE TABLE [dbo].[KPI_Dim](
[KPI_ID] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL,
[KPIName] [varchar](200) NOT NULL,
[KPIDescription] [varchar](500) NULL,
[KPIGroup] [varchar](100) NOT NULL,
[KPISubGroup] [varchar](100) NULL,
[KPIOwner] [varchar] (50) NOT NULL,
[DateCreated] DATETIME NULL DEFAULT(GETDATE())
)
CREATE TABLE dbo.KPI_Fact
(
[KPI_ID] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL,
[KPIDIMID] INT NULL FOREIGN KEY references [dbo].[KPI_Dim]([KPI_ID])
)
DECLARE #inserted table(KPI_DIMID INT)
INSERT INTO [dbo].[KPI_Dim]
(
KPIName,
KPIDescription,
KPIGroup,
KPISubGroup,
KPIOwner
)
OUTPUT inserted.KPI_ID INTO #inserted
VALUES
('TestKPIName','testtest','TestGroup', 'TestSubGroup', 'TestOwner');
INSERT INTO dbo.KPI_Fact([KPIDIMID])
SELECT * FROM #inserted
KPI_ID
KPIDIMID
1
1

Table is still exist after dropping it and the whole database too

I get this message after I dropped the table and the database completely
Msg 2714, Level 16, State 6, Line 12
There is already an object named 'Users' in the database
I tried to create and drop the same database but no use.
I also dropped the same table but when I want to create it, it shows the same error again.
I'll be glad for any help
CREATE DATABASE Music
GO
CREATE TABLE [Users](
[User_ID] int NOT NULL identity (1,1) Primary key,
[UserName] nvarchar (30) NOT NULL,
[UserEmail] nvarchar (30) NOT NULL,
[Password] nvarchar (30) NOT NULL
)
GO
CREATE DATABASE Music GO
USE [Music]
GO
CREATE TABLE [Users]( [User_ID] int NOT NULL identity (1,1) Primary key, [UserName]
nvarchar (30) NOT NULL, [UserEmail] nvarchar (30) NOT NULL, [Password] nvarchar (30) NOT NULL ) GO
you are creating the table in the database where you have the initial query to. See above to first switch to the new DB...

How to set ID value from another table

Let's say I have these tables:
CREATE TABLE [dbo].[Users]
(
[User_ID] [int] IDENTITY(1,1)PRIMARY KEY NOT NULL ,
[LogIn] [varchar](100) NULL,
[Pass] [varchar](100) NOT NULL,
)
CREATE TABLE [dbo].[Consecutives]
(
[Consecutives_ID] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL,
[Name] [varchar](100) NULL,
[Value] [int] NOT NULL,
)
I'm being asked to be able to set an edit the User_ID that is going to be used next when adding a new user using the value stated on the Consecutive table.
So if for example the Consecutive value is 50, even if the last user added has the User_ID set to 8 the new user's ID will be 50 and the consecutive updated to 51.
I would do it using a foreign key, but obviously I can't set a primary key to be a foreign key.
I can't find a way to do this.
Can someone help me out?
What you are describing is called a one-to-one relationship.
You create such a relationship by connecting both tables with a foreign key referencing their primary keys (or a unique index).
However, since this is a one-to-one relationship, only the main table actually needs the identity specification on it's primary key.
Your requirement to insert a record to the Users based on an existing record in the Consecutives table seems strange to me. Usually, when you have a one-to-one relationship you populate the related records in both tables in the same transaction.
To create a one-to-one relationship, where Consecutives is the main table, Your DDL should look like this:
CREATE TABLE [dbo].[Consecutives]
(
[Consecutives_ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](100) NULL,
[Value] [int] NOT NULL,
CONSTRAINT PK_Consecutives PRIMARY KEY (Consecutives_ID)
);
CREATE TABLE [dbo].[Users]
(
[User_ID] [int] NOT NULL,
[LogIn] [varchar](100) NULL,
[Pass] [varchar](100) NOT NULL,
CONSTRAINT PK_Users PRIMARY KEY (User_ID),
CONSTRAINT FK_Users_Consecutives FOREIGN KEY (User_ID) REFERENCES [dbo].[Consecutives]([Consecutives_ID])
);
Please note I've removed the identity specification from the User_ID column, and also changed the way the primary key is declared so that I could name it manually.
Naming constraints is best practice since if you ever need to change them it's much simpler when you already know their names.
Now, to insert a single record to both tables in the same transaction you can create a stored procedure like this:
CREATE PROCEDURE InsertUser
(
#Name varchar(100),
#Value int,
#LogIn varchar(100),
#Pass varchar(100)
)
AS
DECLARE #Consecutives AS TABLE
(
Id int
);
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO [dbo].[Consecutives] ([Name], [Value])
OUTPUT Inserted.Consecutives_ID INTO #Consecutives
VALUES (#Name, #Value)
INSERT INTO [dbo].[Users] ([User_ID], [LogIn], [Pass])
SELECT Id, #Login, #Pass
FROM #Consecutives
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLL BACK TRANSACTION
END CATCH
GO
and execute it like this:
EXEC InsertUser 'Zohar Peled', 1, 'Zohar', 'Peled'
You can see a live demo on rextester. (Please note that rextester doesn't allow using transactions so the try...catch and transaction parts are removed from the demo there)
Have you ever tried set identity insert on? This link may help you. To use the identity insert, the user needs some alter table permissions.

SQL Trigger on Insert to call a Function and Manipulate Values

I am having a problem knowing set up a trigger on insert to call a function and passing an identifier SiteID to that function which then operates on variables from another table.
I have the table, where values are inserted:
CREATE TABLE [dbo].[Tests] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[SiteID] NVARCHAR (128) NOT NULL,
[Date] NVARCHAR (MAX) NULL,
[Time] NVARCHAR (MAX) NULL,
[Tub] NVARCHAR (MAX) NULL,
[FCl] FLOAT (53) NOT NULL
PRIMARY KEY CLUSTERED ([Id] ASC)
);
Every time a value is inserted in to the Tests table I would like a function to be called which uses the table Review to perform a simple calculation (see the trigger down the post below):
CREATE TABLE [dbo].[Review] (
[SiteID] NVARCHAR (128) NOT NULL,
[TubNum] INT NOT NULL,
[Supplied] INT NULL,
[Performed] INT NULL,
[Remaining] INT NULL
PRIMARY KEY CLUSTERED ([SiteID] ASC)
);
This is my sqlfiddle so far, however, I am unable to save it with the functions I am working on, which are here:
The trigger - not sure how to pass the SiteID for the insert or call the function:
CREATE TRIGGER [dbo].[TRIG_MyTable]
ON [dbo].[Tests]
AFTER INSERT
AS
CALL CalcRemaining() //how to pass it SiteID?
and the the function itself:
CREATE FUNCTION [dbo].[CalcRemaining](#ID NVARCHAR (128))
RETURNS NULL
AS
BEGIN
SELECT (Supplied, Performed FROM Review WHERE SiteID = ID);
Performed = Performed + 1;
INSERT INTO Review (Performed, Remaining) VALUES (Performed, (Supplied-Performed))
RETURN;
END
The idea is that the SiteID from the inserted line is passed to the function when called, the function then selects the values Supplied and Performed for that matching SiteID from the Review table. The Performed value is incremented by 1 and that new value along with the retrieved Supplied value are subtracted and written to the Remaining field in the Review table.
If the assumptions I've asked about in the comments are valid (that Remaining and Performed can always be calculated), here's how I'd implement your database structure, with no trigger nor function:
Base tables:
CREATE TABLE dbo.Tests (
[Id] INT IDENTITY (1, 1) NOT NULL,
[SiteID] NVARCHAR (128) NOT NULL,
[Date] NVARCHAR (MAX) NULL,
[Time] NVARCHAR (MAX) NULL,
[Tub] NVARCHAR (MAX) NULL,
[FCl] FLOAT (53) NOT NULL
PRIMARY KEY CLUSTERED ([Id] ASC)
);
GO
CREATE TABLE dbo._Review (
[SiteID] NVARCHAR (128) NOT NULL,
[TubNum] INT NOT NULL,
[Supplied] INT NULL,
PRIMARY KEY CLUSTERED ([SiteID] ASC)
);
Then a view that calculates Performed:
CREATE VIEW dbo._Tests_Count
WITH SCHEMABINDING
AS
SELECT
SiteID,
COUNT_BIG(*) as Performed
FROM
dbo.Tests t
GROUP BY SiteID
GO
CREATE UNIQUE CLUSTERED INDEX IX_Tests_Count ON dbo._Tests_Count (SiteID)
And finally a view that re-creates the original Review table:
CREATE VIEW dbo.Review
WITH SCHEMABINDING
AS
SELECT
r.SiteID,
r.TubNum,
r.Supplied,
COALESCE(tc.Performed,0) as Performed,
r.Supplied - COALESCE(tc.Performed,0) as Remaining
FROM
dbo._Review r
left join
dbo._Tests_Count tc WITH (NOEXPAND)
on
r.SiteID = tc.SiteID
GO
If needed, at this point a trigger could be created on this Review view to allow any INSERTs, DELETEs and UPDATEs to be performed against it rather than _Review, if you cannot change some calling code.
You have to be completely sure you're manipulating ONLY one row at a time!
CREATE TRIGGER [dbo].[TRIG_MyTable]
ON [dbo].[Tests]
AFTER INSERT
AS
DECLARE #SiteID NVARCHAR (128)
SELECT #SiteID = ID FROM inserted
CALL CalcRemaining(#SiteID)

SQL Server IDENTITY column

How can I modify this command in order to have an identity column which has five digits integer like 00000 and start from 00001 ?
CREATE TABLE [dbo].[Company]
(
[CompanyId] [bigint] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](200) NOT NULL
)
An integer does not have any leading 0 by itself. It is a formatting issue to deal with when converting the integer to a string for displaying.
If you really, really need to be able to present such a string right out of SQL, you can do it with a computed column:
CREATE TABLE [dbo].[Company](
[CompanyId] [bigint] IDENTITY(1,1) NOT NULL,
[FormattedCompanyId] AS RIGHT('0000'+ CONVERT(VARCHAR,Num),5),
[Name] nvarchar NOT NULL,
I would never use that solution myself though, formatting doesn't belong in the data store.
You need to add the leading zeros yourself. As a solution you can add an other colomn named say "formatedID" and update it with an "after insert trigger" with the value from the identity column and formatted with the leading zeros you want to.
Example :
CREATE TABLE [dbo].[Company]
(
[CompanyId] [bigint] IDENTITY(1,1) NOT NULL,
[FormattedID] [VARCHAR(20)],
[Name] [nvarchar](200) NOT NULL
)
CREATE TRIGGER ON [dbo].[Company]
FOR INSERT
AS
BEGIN
UPDATE [dbo].[Company]
FROM inserted
SET FormattedID = RIGHT('0000'+ CONVERT(VARCHAR, [dbo].[Company].CompanyId),5)
WHERE dbo.Company.CompanyId = inserted.CompanyId
END