SQL Trigger on Insert to call a Function and Manipulate Values - sql

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)

Related

How to convert numeric to nvarchar in SQL Server?

Consider the following table. I use a trigger to add to the table. In the column of converting the number to the string, it fails.
CREATE TABLE [dbo].[tblAIAgent]
(
[AgentCode] [NVARCHAR](10) NOT NULL,
[NationalCode] [BIGINT] NOT NULL
CONSTRAINT [DF_tblAIAgent_NationalCode] DEFAULT ((0)),
[FirstName] [NVARCHAR](50) NOT NULL
CONSTRAINT [DF_tblAIAgent_Name] DEFAULT (''),
[LastName] [NVARCHAR](50) NOT NULL
CONSTRAINT [DF_tblAIAgent_Family] DEFAULT (''),
[IsActive] [BIT] NOT NULL
CONSTRAINT [DF_tblAIAgent_Active] DEFAULT ((1)),
[Counter] [INT] IDENTITY(1,1) NOT NULL,
CONSTRAINT [PK_tblAIAgent]
PRIMARY KEY CLUSTERED
)
ALTER TRIGGER [dbo].[AgentInsert]
ON [dbo].[tblAIAgent]
INSTEAD OF INSERT
AS
BEGIN
DECLARE #AgentCode NVARCHAR(10)
DECLARE #NationalCode BIGINT
DECLARE #FirstName NVARCHAR(50)
DECLARE #LastName NVARCHAR(50)
DECLARE #IsActive BIT
DECLARE #CounterIs INT
SET #CounterIs = ##IDENTITY
SELECT
#AgentCode = AgentCode,
#NationalCode = NationalCode,
#FirstName = FirstName,
#LastName = LastName,
#IsActive = IsActive
FROM inserted
INSERT INTO tblAIAgent (NationalCode, FirstName, LastName, IsActive, AgentCode)
VALUES (#NationalCode, #FirstName, #LastName, #IsActive, 'Agent_' + CAST(#CounterIs AS NVARCHAR(4)))
END
You have a few problems here:
The ##IDENTITY is a system function contains the last identity value that is generated when an INSERT, SELECT INTO, or BULK COPY statement is completed. If the statement did not affect any tables with identity columns, ##IDENTITY returns NULL. If multiple rows are inserted, generating multiple identity values, ##IDENTITY returns the last identity value generated.
In your case, you have an INSTEAD OF INSERT trigger, so there is no INSERT.
This below query is completely wrong and will gives wrong results, it works as expected only if one row inserted, if there is more than 1 row, then those variables will hold just the values of one row, and you will lose the other values of the other rows, cause the pseudo INSERTED may contains 1 or more rows
select #AgentCode=AgentCode,
#NationalCode=NationalCode,
#FirstName=FirstName,
#LastName=LastName,
#IsActive=IsActive
from inserted
Now, looking to your table, you already have an IDENTITY column, so you don't need to a TRIGGER at all, you can just make a computed column as
CREATE TABLE [dbo].[tblAIAgent](
[AgentCode] AS 'Agent_' + CAST([Counter] AS VARCHAR(10)),
[NationalCode] [bigint] NOT NULL
CONSTRAINT [DF_tblAIAgent_NationalCode] DEFAULT ((0)),
[FirstName] [nvarchar](50) NOT NULL
CONSTRAINT [DF_tblAIAgent_Name] DEFAULT (''),
[LastName] [nvarchar](50) NOT NULL
CONSTRAINT [DF_tblAIAgent_Family] DEFAULT (''),
[IsActive] [bit] NOT NULL
CONSTRAINT [DF_tblAIAgent_Active] DEFAULT ((1)),
[Counter] [int] IDENTITY(1,1) NOT NULL,
CONSTRAINT [PK_tblAIAgent] PRIMARY KEY ([Counter])
);
UPDATE:
According to your comment "a computed column can no longer be selected as the PK. I want this column to be placed in other relevant tables as a FK.I wrote the trigger to get the column value instead of the computed column so that I can select the column as the primary key". You are trying to make it a PRIMARY KEY so you can do as
CREATE TABLE T(
Counter INT IDENTITY(1,1) NOT NULL,
OtherCol INT,
Computed AS CONCAT('Agent_', CAST(Counter AS VARCHAR(10))) PERSISTED,
CONSTRAINT PKT PRIMARY KEY(Computed)
);
CREATE TABLE TT(
ReferenceComputedColumn VARCHAR(16) NOT NULL,
OtherColumn INT,
CONSTRAINT FK_ReferencedComputedColumn
FOREIGN KEY(ReferenceComputedColumn)
REFERENCES T(Computed)
)
INSERT INTO T(OtherCol) VALUES
(1), (2), (3);
INSERT INTO TT(ReferenceComputedColumn, OtherColumn) VALUES
('Agent_1', 10),
('Agent_3', 20);
SELECT *
FROM T LEFT JOIN TT
ON T.Computed = TT.ReferenceComputedColumn;
See how it's working.
See also this article Properly Persisted Computed Columns by Paul White.
Try this
SELECT CONVERT(NVARCHAR(255), #AgentCode)
SELECT CAST([PictureId] AS NVARCHAR(4)) From Category

How to get the auto-generated primary key after insertion in stored procedure?

My SQL Server table:
[dbo].[Rep] (
[rep_ID] INT IDENTITY (300, 1) NOT NULL,
[Restaurant_ID] INT NULL,
[Fname] VARCHAR (50) NOT NULL,
[Lname] VARCHAR (50) NOT NULL,
CONSTRAINT [PK_Rep_ID] PRIMARY KEY CLUSTERED ([rep_ID] ASC),
CONSTRAINT [FK_Restaurant_ID] FOREIGN KEY ([Restaurant_ID]) REFERENCES [dbo].[Restaurants] ([ID])
);
I want to create a stored procedure that return the auto generated key for the inserted record.
My current stored procedure:
CREATE PROCEDURE [dbo].[createRepAcc]
#first varchar(50),
#last varchar(50)
AS
INSERT INTO Rep([Fname], [Lname])
OUTPUT inserted.rep_ID
VALUES (#first, #last);
It's able to insert into the table but only return 1 instead of the primary key generated.
What am I doing wrong?
You can use scope_identity() to get the last generated id. For your case here is a detailed worksheet
/* creating a simple table with identity column */
CREATE TABLE dbo.a(identity_column INT IDENTITY(300,1), x CHAR(1));
/* Procedure that inserts values and returns generated output*/
CREATE PROCEDURE [dbo].[ap]
#x char(1),
#id int output
AS
INSERT INTO a(x) VALUES (#x);
select #id = SCOPE_IDENTITY();
/* This is how the procedure could be called */
declare #ident int -- declare a variable to receive output
execute dbo.ap #x='b', #id=#ident output -- execute or call the procedure
select #ident -- select or print the genereated id

Cannot insert the value NULL into column 'Id' although Id is set

I have a strange problem.
I want to insert an item to a table from database. I use Entity Framework.
Although the Id is set, I keep getting the following error:
Cannot insert the value NULL into column 'Id', table 'project_atp.dbo.ShoppingCarts'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated."}
The table definition:
CREATE TABLE [dbo].[ShoppingCarts] (
[Id] INT NOT NULL,
[Guid] UNIQUEIDENTIFIER NULL,
[Name] NVARCHAR (255) NULL,
[Code] NVARCHAR (255) NULL,
[SupplierNo] NVARCHAR (255) NULL,
[SupplierName] NVARCHAR (255) NULL,
[Price] NVARCHAR (50) NULL,
[Quantity] INT NULL,
CONSTRAINT [PK_ShoppingCarts] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Can you please advise what could be wrong here! Thanks!
By default Entity Framework assumes that an integer primary key is database generated. As the result Entity Framework would not include Primary Key field in the actual INSERT statement.
I would try to either play along and ALTER the table to auto-generate the ID (which judging by your comment you did)
or set StoreGeneratedPattern property of OnlineCarStore.Models.ShoppingCarts Id column to 'None'
or use annotation: [DatabaseGenerated(DatabaseGeneratedOption.None)].

Audit Trail Trigger generation

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.

SQL create statement incorrect syntax near auto increment

I created the following table, but I got the error below;
Incorrect syntax near 'AUTO_INCREMENT'.
SQL:
CREATE TABLE [dbo].[MY_TABLE] (
[ID] INT NOT NULL AUTO_INCREMENT,
[NAME] NVARCHAR (100) NULL,
[SCHOOL] NVARCHAR (100) NULL,
PRIMARY KEY (ID)
);
I think I have done everything right. Can someone help me out?
It is IDENTITY not AUTO_INCREMENT in SQL Server.
Try this instead:
CREATE TABLE [dbo].[MY_TABLE] (
[ID] INT NOT NULL IDENTITY(1, 1),
[NAME] NVARCHAR (100) NULL,
[SCHOOL] NVARCHAR (100) NULL,
PRIMARY KEY (ID)
);
Its not AUTO_INCREMENT. Here the Demo from sqlfiddle
Use IDENTITY for MSSQL as shown below. *AUTO_INCREMENT is for MySQL and MariaDB:
CREATE TABLE [dbo].[MY_TABLE] (
[ID] INT NOT NULL IDENTITY, -- Here
...
);
In addition, you can have a custom IDENTITY with "()" as shown below. The 1st argument is for start value and the 2nd argument is for increment value so in the example below, if first inserting a row, ID is 20 and if second inserting a row, ID is 23, then ID is 26, 29, 32...:
-- Increment value
CREATE TABLE [dbo].[MY_TABLE] (-- ↓
[ID] INT NOT NULL IDENTITY(20, 3),
... -- ↑
); -- Start value
And, IDENTITY is equivalent to IDENTITY(1, 1).