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.
Related
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
I have 2 tables mpayment and account. When someone updates data in mpayment, then the trigger should automatically update the account table with the newly updated data. I wrote this trigger on my mpayment table, but when I try to update some data, I get an error:
The row value update or deleted either do make the row unique or they alter multiplerows [2 rows]
This is my trigger that I am trying to use
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[mpacupdate]
ON [dbo].[mpayment]
AFTER UPDATE
AS
BEGIN
DECLARE #pid AS NCHAR(10)
DECLARE #memid AS NCHAR(10)
DECLARE #pdate AS DATE
DECLARE #pamount AS MONEY
DECLARE #flag AS INT
SELECT #pid = list.pid FROM inserted list;
SELECT #memid = list.memid FROM inserted list;
SELECT #pdate = list.pdate FROM inserted list;
SELECT #pamount = list.pamount FROM inserted list;
SELECT #flag = list.flag FROM inserted list;
BEGIN
UPDATE [dbo].[account]
SET memid = #memid, pdate = #pdate,
pamount = #pamount, flag = #flag
WHERE pid = #pid
END
END
Your trigger is assuming that only 1 row will be updating at a time; it shouldn't. Treat the data as what it is, a dataset (not a set a scalar values).
This maynot fix the problem, as there's no sample data here to test against. I'm also not really sure that what you're after here is the right design choice, however, there's no information on what that is. Generally, you shouldn't be repeating data across tables; if you need data from another table then use a JOIN, don't INSERT or UPDATE both. If they become out of sync you'll start to get some really odd results I imagine.
Anyway, on track, this is probably what you're looking for, however, please consider the comments I've made above:
ALTER TRIGGER [dbo].[mpacupdate] ON [dbo].[mpayment]
AFTER UPDATE
AS BEGIN
UPDATE A
SET memid = i.memid,
pdate = i.pdate,
pamount = i.pamount,
flag = i.flag
FROM dbo.Account A
JOIN inserted i ON A.pid = i.pid;
END
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;
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');
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