SQl trigger after insert error - sql

I'm trying to update/insert a value after I inserted a new row into my SQL table. I'm using a trigger to accomplish this but I get an error which I do understand what it means but I don't know how to fix it. I was under the impression that I could get the inserted row from the inserted table based on the #id I declared, that part seems to work but why do I get the error that I'm not allowed to enter a NULL value into my "invoicenumber" field (I know it's the PK but I don't wont to enter anything into this field (it's already filled with correct data from the insert itself).
So, can anyone explain to me what I did wrong?
The table I used:
table delivery_invoice
id - int
invoicenumber(PK) - varchar(50)
amount6 - money
amount21 - money
distributor - int
payed - char(3)
deliverycosts - money
deliverydate - datetime
deliverytime - datetime
VATamount6 - calculated field
VATamount21 - calculated field
VATamountdelivery - int
The trigger I used
USE TestDelivery
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: James
-- Create date: 11-08-2017
-- Description: change VATamountdelivery field after a new row is inserted
-- =============================================
CREATE TRIGGER trgVATAmountDelivery
ON dbo.delivery_invoice
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
Declare #id int
Declare #amount money
Declare #invoice varchar(50)
Select #id = id from inserted
Select #amount = VATamount21 from inserted
IF #amount > 0
insert into dbo.delivery_invoice(VATamountdelivery)
values(21)
else
insert into dbo.delivery_invoice(VATamountdelivery)
values(6)
END
GO
And last but not least the error I got:
Error. Cannot insert the value NULL into column 'invoicenumber', table
'TestDelivery delivery_invoice'; column does not allow nulls. INSERT fails.
The statement has been terminated.
Any help would be appriciated!
J.

You don't want to insert a new row with the value. I think you just want to change the value.
However, your trigger has other issues. In particular, never assume that inserted contains only one row. That is just a bug waiting to happen.
CREATE TRIGGER trgVATAmountDelivery ON dbo.delivery_invoice
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
update di
set VATamountdelivery = (case when i.VATamount21 > 0 then 21 else 6 end)
from dbo.delivery_invoice di join
inserted i
on i.id = di.id
END;

Related

Generate a unique column sequence value based on a query handling concurrency

I have a requirement to automatically generate a column's value based on another query's result. Because this column value must be unique, I need to take into consideration concurrent requests. This query needs to generate a unique value for a support ticket generator.
The template for the unique value is CustomerName-Month-Year-SupportTicketForThisMonthCount.
So the script should automatically generate:
AcmeCo-10-2019-1
AcmeCo-10-2019-2
AcmeCo-10-2019-3
and so on as support tickets are created. How can ensure that AcmeCo-10-2019-1 is not generated twice if two support tickets are created at the same time for AcmeCo?
insert into SupportTickets (name)
select concat_ws('-', #CustomerName, #Month, #Year, COUNT())
from SupportTickets
where customerName = #CustomerName
and CreatedDate between #MonthStart and #MonthEnd;
One possibility:
Create a counter table:
create table Counter (
Id int identify(1,1),
Name varchar(64)
Count1 int
)
Name is a unique identifier for the sequence, and in your case name would be CustomerName-Month-Year i.e. you would end up with a row in this table for every Customer/Year/Month combination.
Then write a stored procedure similar to the following to allocate a new sequence number:
create procedure [dbo].[Counter_Next]
(
#Name varchar(64)
, #Value int out -- Value to be used
)
as
begin
set nocount, xact_abort on;
declare #Temp int;
begin tran;
-- Ensure we have an exclusive lock before changing variables
select top 1 1 from dbo.Counter with (tablockx);
set #Value = null; -- if a value is passed in it stuffs us up, so null it
-- Attempt an update and assignment in a single statement
update dbo.[Counter] set
#Value = Count1 = Count1 + 1
where [Name] = #Name;
if ##rowcount = 0 begin
set #Value = 10001; -- Some starting value
-- Create a new record if none exists
insert into dbo.[Counter] ([Name], Count1)
select #Name, #Value;
end;
commit tran;
return 0;
end;
You could look into using a TIME type instead of COUNT() to create unique values. That way it is much less likely to have duplicates. Hope that helps

What am I doing wrong with this SQL Server 2012 trigger?

I can't get what should be a simple trigger to work.
It fires after INSERT, tests whether Column A is null, and if so sets it to the value of Column B on that same row.
After the insert, I check the inserted row, and Column B has a value, but Column A is null.
The trigger is enabled and is being fired, because in despair I was logging values to a work table at various points,
and these values are what is expected.
I even tried to do a temporary bypass of this issue by going into Design on this table and specifying a "Default Value" of 1000 for this column, but it still comes out null.
And I tried changing the trigger to set Column A to a fixed value of 987, but it still comes out null.
Is there some idiosyncrasy about inserts that I don't know about? Any advice is appreciated.
BACKGROUND -----------------------------------------------------------------------------
OPTIONS is a table which stores "Options" which an attendee at one of our events can register for (e.g., a ticket to the event, an optional steak or chicken dinner, etc.)
These Options are inserted when the event is being defined (dates, venues, contact info, and the options which attendees can register for).
The Options table:
OptionID INT (Identity, primary key)
...
MaxAttendees INT
...
AvlbRemainingRegQty INT
That last column is the column not getting set - it's supposed to be a running total of how many chicken dinners are remaining to be sold
I thought I was going crazy and/or doing something stupid, so I created a zTestTrigger table to store the values from the INSERTED row and to verify that the trigger got to certain points.
WhichPartOfTest VARCHAR(100)
OptionID INT
AvlbRemRegQty INT
MaxAttendees INT
--DETAILS ---------------------------------------------------
USE [dbname]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[trig_Options_AvlbRemainingRegQty]
ON [dbo].[Options]
AFTER INSERT
AS
SET NOCOUNT ON
DECLARE #I_OptionID INT
DECLARE #I_AvlbRemainingRegQty INT
DECLARE #I_MaxAttendees INT
SELECT
#I_OptionID = I.OptionID,
#I_AvlbRemainingRegQty = ISNULL(I.AvlbRemainingRegQty,-1),
#I_MaxAttendees = MaxAttendees
FROM Inserted I
insert into zTestTrigger values ('Start of new test - values from INSERTED row are:',#I_OptionID, #I_AvlbRemainingRegQty, #I_MaxAttendees)
IF ISNULL(#I_AvlbRemainingRegQty,-1) = -1 --(I had also tried checking IF I_AvlbRemainingRegQty IS NULL)
begin
insert into zTestTrigger values ('About to update table, setting AvlbRemQty = ' + cast(#I_MaxAttendees as varchar) + ' where id = ' + cast(#I_OptionID as varchar),
#I_OptionID, #I_AvlbRemainingRegQty, #I_MaxAttendees)
UPDATE Options SET AvlbRemainingRegQty = #I_MaxAttendees WHERE OptionID = #I_OptionID
--Originally, I tried this:
--UPDATE OPTIONS SET AvlbRemainingRegQty = ISNULL(MaxAttendees,1000) WHERE OptionID = #I_OptionID
--This doesn't work either:
--UPDATE OPTIONS SET SET AvlbRemainingRegQty = SELECT ISNULL(MaxAttendees,1000) FROM Options WHERE OptionID = #I_OptionID) WHERE OptionID = #I_OptionID
--I even tried:
--UPDATE OPTIONS SET AvlbRemainingRegQty = 987 WHERE OptionID = #I_OptionID
declare #error int
set #error = ##ERROR
insert into zTestTrigger values ('##ERROR is ' + cast(#error as varchar), 0,0,0) --Shows that #Error is 0
end
Selecting from the zTestTrigger table shows
"About to update table, setting AvlbRemQty = 1000 where id = 491" , 491, -1, and 1000
and I can select the row with ID 491, but its Avlb Rem Qty is null.
I would personally use an instead of trigger for this, since it allows us to put the data in correctly, right from the start.
But for now, we'll do the after trigger:
ALTER TRIGGER [dbo].[trig_Options_AvlbRemainingRegQty]
ON [dbo].[Options]
AFTER INSERT
AS
SET NOCOUNT ON
UPDATE Options
SET
AvlbRemainingRegQty = COALESCE(AvlbRemainingRegQty, MaxAttendees)
WHERE
OptionID in (select OptionID from inserted)
And note that this trigger (unlike yours) recognizes that inserted can contain 0, 1 or multiple rows. So treat it as a table, not as something from which separate, scalar variables can have meaningful values assigned.
Just as another point though:
it's supposed to be a running total of how many chicken dinners are remaining to be sold
In general, this is a bad idea. If you are storing details of each sale separately, the number remaining is a value that can be calculated. All that you do be storing such a value is give yourself an opportunity for the stored value to be inconsistent with the rest of your data.

FIM - SQL Triggers for updating records in Delta table

I'm writing a DML trigger when change (update or Insert) happens in one table (Master table), I want to write the whole row into another table (Delta table).
The Master and Delta tables contains the same column with same datatype, except that Delta table contains an additional column called 'change_type', which should say either 'INSERT' OR 'MODIFY', depending on which trigger is updating the delta table.
The difficulty I'm having is I want to use the inserted table to update the Delta table row but its giving me errors.
CREATE TRIGGER [dbo].[TR_Update]
ON [dbo].[People_Master]
AFTER Update
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
Declare #RowCount int
Declare #ID int
Declare #Email nvarchar(50)
Declare #ct nvarchar(10)
select #ID = ID from inserted
Select #RowCount=COUNT(*) from People_Delta where People_Delta.ID = #ID and People_Delta.change_type = 'Modify';
if(#RowCount = 0)
Begin
Insert into People_Delta (ID,Email,uac,Department,FirstName,change_type)
values (iserted.ID,inserted.Email,inserted.uac,inserted.Department,inserted.Firstname'Modify');
END
END
GO
My table has 5 columns.
ID (primary key)
Email
Firstname
uac
Department
You are missing a , in your INSERT statement.
And because the number of columns you have specified does not match with the number of values you are inserting, you get an error.
inserted.Firstname , 'Modify'
Insert into People_Delta (ID,Email,uac,Department,FirstName,change_type)
values (iserted.ID,inserted.Email,inserted.uac,inserted.Department,inserted.Firstname,'Modify');

SQL Trigger & Inserted Idenities

I've got a problem with a trigger that i can't figure out.
Assume i have two tables, Stu_Table2 & Stu_log. Stu_table2 has some columns, one of which is an automatically generated primary key [stu_id]. The link between the two tables is [stu_name]=[user_id]
The below code works fine for Updates & Deletions (as the primary key already exists). But i'm stuck on the insert - how can i insert the automatically generated primary key from stu_name to log table if it hasn't been generated yet?
Stu_name columns, [stu_id] [Stu_name] [Stu_class]
Stu_log columns, [user_id] [stu_name]
obviously this isn't a real world example, just testing proof of concept.
ALTER TRIGGER [dbo].[stu_testtrigger]
ON [dbo].[Stu_Table2] FOR INSERT, UPDATE, DELETE
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with caller queries SELECT statements.
-- If an update/insert/delete occurs on the main table, the number of records affected
-- should only be based on that table and not what records the triggers may/may not
-- select.
SET NOCOUNT ON;
--
-- Variables Needed for this Trigger
--
DECLARE #stu_ID int
DECLARE #stu_name varchar(15)
DECLARE #stu_class int
--
-- Determine if this is an INSERT,UPDATE, or DELETE Action
--
DECLARE #Action as char(1)
DECLARE #Count as int
SET #Action = 'I' -- Set Action to 'I'nsert by default.
SELECT #Count = COUNT(*) FROM DELETED
if #Count > 0
BEGIN
SET #Action = 'D' -- Set Action to 'D'eleted.
SELECT #Count = COUNT(*) FROM INSERTED
IF #Count > 0
SET #Action = 'U' -- Set Action to 'U'pdated.
END
if #Action = 'D'
-- This is a DELETE Record Action
--
BEGIN
SELECT #Stu_id =[stu_id]
,#Stu_name = [stu_name]
FROM DELETED
DELETE [dbo].[stu_log]
WHERE [user_id]=#stu_id
END
Else
BEGIN
--
-- Table INSERTED is common to both the INSERT, UPDATE trigger
--
SELECT #stu_id =[stu_id]
,#stu_name = [stu_name]
FROM INSERTED
if #Action = 'I'
-- This is an Insert Record Action
--
--THIS IS WHERE I'm STUCK i think!!!
BEGIN
INSERT INTO [stu_log]
([user_id]
,[description])
VALUES
(#stu_id
,#stu_name)
END
else
-- This is an Update Record Action
--
BEGIN
UPDATE [stu_log]
SET [user_id] = #stu_id
,[description] = #Stu_name
WHERE [user_id]=#stu_id
END
END
HELP!
Since you seem to want to carry out distinctly different actions for inserts, updates and deletes, I'm not sure why you're cramming all of the actions into a single trigger. I'd just have:
CREATE TRIGGER [dbo].[stu_testtrigger_I]
ON [dbo].[Stu_Table2] AFTER INSERT
AS
INSERT INTO stu_log ([user_id],[description])
SELECT stu_id,stu_name from inserted
GO
CREATE TRIGGER [dbo].[stu_testtrigger_D]
ON [dbo].[Stu_Table2] AFTER DELETE
AS
DELETE FROM stu_log WHERE [user_id] IN (
SELECT stu_id from deleted)
GO
CREATE TRIGGER [dbo].[stu_testtrigger_U]
ON [dbo].[Stu_Table2] AFTER UPDATE
AS
UPDATE l SET user_name = i.user_name
FROM
stu_log l
inner join
inserted i
on l.[user_id] = i.stu_id
GO
Notes:
This works for multi-row inserts, updates and deletes, which your original didn't
I've said AFTER instead of FOR, to make it clearer to you that these actions occur after any activity in Stu_Table2 has already occurred (e.g. the identity value has already been generated, which seems to be your concern).
You should note, however, that AFTER and FOR are synonymous. You'd only get different behaviour if we were doing an INSTEAD OF trigger.
I removed the pointless [user_id] = #stu_id setting from the UPDATE. Given the WHERE clause of this update (or my join equivalent, above), those two must already be equal.

sql stored procedure not working(no rows affected)

trying to get this stored procedure to work.
ALTER PROCEDURE [team1].[add_testimonial]
-- Add the parameters for the stored procedure here
#currentTestimonialDate char(10),#currentTestimonialContent varchar(512),#currentTestimonialOriginator varchar(20)
AS
BEGIN
DECLARE
#keyValue int
SET NOCOUNT ON;
--Get the Highest Key Value
SELECT #keyValue=max(TestimonialKey)
FROM Testimonial
--Update the Key by 1
SET #keyValue=#keyValue+1
--Store into table
INSERT INTO Testimonial VALUES (#keyValue, #currentTestimonialDate, #currentTestimonialContent, #currentTestimonialOriginator)
END
yet it just returns
Running [team1].[add_testimonial] ( #currentTestimonialDate = 11/11/10, #currentTestimonialContent = this is a test, #currentTestimonialOriginator = theman ).
No rows affected.
(0 row(s) returned)
#RETURN_VALUE = 0
Finished running [team1].[add_testimonial].
and nothing is added to the database, what might be the problem?
There may have problems in two place:
a. There is no data in the table so, max(TestimonialKey) returns null, below is the appropriate way to handle it.
--Get the Highest Key Value
SELECT #keyValue= ISNULL(MAX(TestimonialKey), 0)
FROM Testimonial
--Update the Key by 1
SET #keyValue=#keyValue+1
b. Check your data type of the column currentTestimonialDate whether it is char or DateTime type, if this field is datetime type in the table then convert #currentTestimonialDate to DateTime before inserting to the table.
Also, check number of columns that are not null allowed and you're passing data to them.
If you're not passing data for all columns then try by specifying columns name as below:
--Store into table
INSERT INTO Testimonial(keyValue, currentTestimonialDate,
currentTestimonialContent, currentTestimonialOriginator)
VALUES (#keyValue, #currentTestimonialDate,
#currentTestimonialContent, #currentTestimonialOriginator)
EDIT:
After getting the comment from marc_s:
Make keyValue as INT IDENTITY, If multiple user call it concurrently that wont be problem, DBMS will handle it, so the ultimate query in procedure might be as below:
ALTER PROCEDURE [team1].[add_testimonial]
-- Add the parameters for the stored procedure here
#currentTestimonialDate char(10),
#currentTestimonialContent varchar(512),#currentTestimonialOriginator varchar(20)
AS
BEGIN
SET NOCOUNT ON;
--Store into table
INSERT INTO Testimonial VALUES (#currentTestimonialDate,
#currentTestimonialContent, #currentTestimonialOriginator)
END
Two issues that I can spot:
SELECT #keyValue=max(TestimonialKey)
should be
SELECT #keyValue=ISNULL(max(TestimonialKey), 0)
To account for the case when there are no records in the database
Second, I believe that with NOCOUNT ON, you will not return the count of inserted rows to the caller. So, before your INSERT statement, add
SET NOCOUNT OFF