INSERT and UPDATE after one row inserted MS SQL - sql-server

I don't know if I'm thinking correct, so I'm open for suggestions.
I'm using MS SQL SERVER 2008 R2.
Here is the 'story'
Everytime someone insert a row into tblDelivered there is a trigger that insert in tblConditionDel five values(1,1,1,1,1). This is a table with an auto ID increment. And for that inserted row must fieldname ConditionID be updated with the ID from tblConditionDel.
I think there is something wrong with my where statement
If I delete the where statement the ID is update for the entire table, but it must be for the one inserted row.
My code:
ALTER TRIGGER [dbo].[trgCond2] ON [dbo].[tblDelivered]
AFTER INSERT
AS
insert into tblConditionDel(Con1,Con2,Con3,Con4, Con5)
values(1,1,1,1,1);
update tblDelivered set ConditionID = (select max(ConditionID) from tblConditionDel)
where (select 1 from inserted) = tblDelivered.ConditionID
Thx in advance

Your solution does not work if more than one row is inserted.
Do like this
CREATE TABLE tblDelivered (
DeliveredID int NOT NULL
,ConditionID int
);
CREATE TABLE tblConditionDel (
ConditionID int IDENTITY(1,1)
,Con1 int NOT NULL
,Con2 int NOT NULL
,Con3 int NOT NULL
,Con4 int NOT NULL
,Con5 int NOT NULL
);
CREATE TRIGGER [dbo].[trgCond2] ON [dbo].[tblDelivered]
AFTER INSERT
AS
SET NOCOUNT ON;
DECLARE #ConditionIDs AS table ( -- stores inserted conditionsIDs
DeliveredID int NOT NULL
,ConditionID int NOT NULL
);
MERGE INTO tblConditionDel -- INSERT does not support OUTPUT INTO for multiple rows
USING inserted AS triggerinserted
ON 1 = 0
WHEN NOT MATCHED THEN
INSERT (Con1, Con2, Con3, Con4, Con5)
VALUES (1,1,1,1,1)
OUTPUT triggerinserted.DeliveredID
,inserted.ConditionID
INTO #ConditionIDs;
UPDATE tblDelivered
SET ConditionID = ConditionIDs.ConditionID
FROM tblDelivered
INNER JOIN #ConditionIDs AS ConditionIDs
ON ConditionIDs.DeliveredID = tblDelivered.DeliveredID
-- Test code
INSERT INTO tblDelivered (DeliveredID)
VALUES (4),(5),(6);
SELECT * FROM tblConditionDel
SELECT * FROM tblDelivered

You would want to do something like this:
ALTER TRIGGER [dbo].[trgCond2] ON [dbo].[tblDelivered]
AFTER INSERT
AS
declare #new_id int, #delivered_id int
set #delivered_id = (select ConditionID from inserted) -- captures id from inserted table
insert into tblConditionDel(Con1,Con2,Con3,Con4, Con5)
values(1,1,1,1,1);
set #new_id = scope_identity() -- captures new id from tblCondition into a variable
update tblDelivered set ConditionID = #new_id -- set new tblcondition id
where ConditionID = #delivered_id -- for the record that matches the inserted one

Related

Issue with SQL INSERT Trigger

I'm reaching out for some help on this trigger I'm trying to get working.
Basically this is what I'm trying to do.
We have DMS software that writes to a Database and on a particular INSERT value I want the trigger to fire.
This is an example of an INSERT statement that will get processed.
INSERT INTO DOCSADM.ACTIVITYLOG (CR_IN_USE,ACTIVITY_DESC,BILLED_ON,BILLABLE,PAGES,KEYSTROKES,
TYPE_TIME,ELAPSED_TIME,TYPIST,AUTHOR,START_DATE,ACTIVITY_TYPE,REF_DOCUMENT,REF_LIBRARY,APPLICATION,VERSION_LABEL,DOCNUMBER,SYSTEM_ID)
VALUES ('','DOCSFusion','1753-01-01','',0,0,0,0,1920,1920,'2020-08-26T10:17:56',**115**,0,-1,1173,'',75,3252)
but I only want the trigger to fire when we see a value of 115 for the bold section in the INSERT statement (the Activity_type value).
For all other values that re not 115 I don't want to do anything.
This is what I have so far:
CREATE TRIGGER BW_TRIGGER
ON DOCSADM.ACTIVITYLOG
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--Declare some variable and set it as a value of 115.
--Example:
DECLARE #AlogType int = (SELECT I.ACTIVITY_TYPE FROM DOCSADM.ACTIVITYLOG A, INSERTED I) --This is the value you are looking for regarding the DM client/Matter actitivty type.
DECLARE #AlogDesc varchar(32) = (Select i.ACTIVITY_DESC from docsadm.ACTIVITYLOG A, INSERTED I)
--Next, you should have a fork or path in your trigger to determine how it proceeds.
--Path 1: The #AlogType value matches the inserted value so you want to process the rest of the trigger. Example path – “ProcessTrigger:”
--Path 2: The #AlogType value does NOT match the inserted value, you want to exit the trigger. Example Path – “ExitTrigger:”
IF #AlogType <> 115
GOTO TriggerExit;
ELSE
Begin
/*Create first temp table to collect insert values*/ --This table will have the SysID Value and the corresponding docnumber for the items you want.
--You can add whatever other values you think you need.
CREATE TABLE #TempSet1
(
AlogsysID INT,
Docnum INT,
AlogDate Varchar(64),
AlogTypist INT,
AlogAuthor INT,
AlogDesc varchar(32),
ALOGVER varchar(10),
ALOG_MATTER INT
)
INSERT INTO #TempSet1 (AlogsysID,Docnum,AlogDate,AlogTypist,AlogAuthor, ALOG_MATTER)
--SELECT  You SELECT STATEMENT WILL GO HERE MODIFIED TO POPULATE THE TABLE WITH THE DOCNUMBERS YOU WANT!!
select top 1 System_id, docnumber, LAST_ACCESS_DATE, TYPIST, AUTHOR, MATTER from docsadm.PROFILE where EXISTS (SELECT CLIENT.SYSTEM_ID FROM DOCSADM.CLIENT INNER JOIN DOCSADM.MATTER ON MATTER.CLIENT_ID = CLIENT.SYSTEM_ID
WHERE MATTER.SYSTEM_ID =#AlogDesc OR INH_LUP_SEC_FROM IS NULL OR INH_LUP_SEC_FROM = 0) AND MATTER=#AlogDesc
/*Set variable #SysID as the LASTKEY value -1. This will be used to set the SysID column on the #TempSet table*/
--DECLARE #SysID INT = (SELECT LASTKEY FROM DOCSADM.SEQ_SYSTEMKEY) -1;
/*Set the SysID value for every row on the #TempSet1 table as the #SysID variable +1*/
--UPDATE #TempSet1
--SET #SysID = AlogsysID = #SysID + 1
--Your #TempSet should now be set with ALL of the System_IDs and Docnumbers necessary for your insert!!!!—
--Verify this by doing a select against the #TempSet1 Table
SELECT * FROM #TempSet1;
--Next you need to set the SystemID to the correct value for future processing. To do this, we need to get a total count from the #TempSet table.
/*Set a variable to update the NEXTKEY value on the DOCSADM.SEQ_SYSTEMKEY table. The NEXTKEY value is used for the SYSTEM_ID field*/
--DECLARE #SeqUpdateCount INT = (SELECT COUNT(*) FROM #TempSet1);
/*Update the LASTKEY Value on the SEQ_SYSTEMKEY table to the next available value for DM.*/
--UPDATE DOCSADM.SEQ_SYSTEMKEY SET LASTKEY = LASTKEY+#SeqUpdateCount
--If you have all the values you need in your temp table, you can now insert them into the ACTIVITYLOG table.
--INSERT INTO DOCSADM.ACTIVITY
--(SYSTEM_ID, DOCNUMBER, START_DATE, version, EXT,)
--SELECT
--AlogSysID,Docnum,GETUTCDATE(),BLAH, BLAH
--FROM #TableSet1
INSERT INTO DOCSADM.ACTIVITYLOG
(SYSTEM_ID,
DOCNUMBER,
START_DATE,
TYPIST,
AUTHOR,
ACTIVITY_DESC,
VERSION_LABEL,
ACTIVITY_TYPE)
SELECT
AlogsysID, Docnum,AlogDate,AlogTypist, AlogAuthor, ALOG_MATTER, '',115
FROM #TempSet1;
--Now you need to Drop the Temp Table
DROP TABLE #TempSet1
--Go to the other half of your path above to exit the trigger.
END
TriggerExit:
END
Go
but when I try to run any INSERT statement on this table I get this error message. It doesn't matter if the activity_type has a value of 115 or not
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I know the issue is with this section of the trigger:
INSERT INTO #TempSet1 (AlogsysID,Docnum,AlogDate,AlogTypist,AlogAuthor, ALOG_MATTER)
--SELECT  You SELECT STATEMENT WILL GO HERE MODIFIED TO POPULATE THE TABLE WITH THE DOCNUMBERS YOU WANT!!
SELECT TOP 1
System_id
, docnumber
, LAST_ACCESS_DATE
, TYPIST
, AUTHOR
, MATTER
FROM docsadm.PROFILE
WHERE EXISTS (SELECT CLIENT.SYSTEM_ID
FROM DOCSADM.CLIENT
INNER JOIN DOCSADM.MATTER
ON MATTER.CLIENT_ID = CLIENT.SYSTEM_ID
WHERE MATTER.SYSTEM_ID =#AlogDesc
OR INH_LUP_SEC_FROM IS NULL
OR INH_LUP_SEC_FROM = 0)
AND MATTER=#AlogDesc
It's the SELECT statement that is causing it to fail.
I know that this statement will bring back multiple rows but I only need the value from one of them so I can use this value for my INSERT. I though having the "select top 1" would do this for me but it's not working like I think it should. What am I missing?
If I had to guess I would say your problem is here:
DECLARE #AlogType int = (SELECT I.ACTIVITY_TYPE FROM DOCSADM.ACTIVITYLOG A, INSERTED I) --This is the value you are looking for regarding the DM client/Matter actitivty type.
DECLARE #AlogDesc varchar(32) = (Select i.ACTIVITY_DESC from docsadm.ACTIVITYLOG A, INSERTED I)
How are ACTIVITYLOG and INSERTED joined in the above ? without a where it would be a CROSS JOIN. Why do you even drag ACTIVITYLOG into it, you can simply use INSERTED. Also please try to stop using implicit joins ( I can see that later down the script you use the proper, more verbose join syntax)
TRY:
DECLARE #AlogType int = (SELECT I.ACTIVITY_TYPE FROM INSERTED I) --This is the value you are looking for regarding the DM client/Matter actitivty type.
DECLARE #AlogDesc varchar(32) = (Select i.ACTIVITY_DESC from INSERTED I)
Be careful that this will work with single inserts only. When you do batched inserts the INSERTED is a table containing multiple rows and you will run into issues again.

SQL Server INSERT in an AFTER UPDATE Trigger

I'm new to SQL Server, and I'm trying to build a simple update trigger that writes a row to a staging table whenever the column ceu_amount is updated from zero to any number greater than zero.
From using PRINT statements, I know that the variables are containing the correct values to execute the INSERT statement, but no rows are being inserted.
Can you help?
CREATE TRIGGER [dbo].[TRG_Product_Function_Modified] ON [dbo].[Product_Function]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
--
-- Variable definitions
--
DECLARE #product_code_new as varchar(31)
DECLARE #product_code_old as varchar(31)
--
-- Check if the staging table needs to be updated.
--
SELECT #product_code_new = product_code FROM Inserted where ISNULL(ceu_amount,0) > 0;
SELECT #product_code_old = product_code FROM Deleted where ISNULL(ceu_amount,0) = 0;
IF #product_code_new IS NOT NULL
AND #product_code_old IS NOT NULL
INSERT INTO Product_Function_Staging VALUES (#product_code_new,CURRENT_TIMESTAMP);
END;
This part of code looks suspicious to me..
SELECT #product_code_new = product_code FROM Inserted where ISNULL(ceu_amount,0) > 0;
SELECT #product_code_old = product_code FROM Deleted where ISNULL(ceu_amount,0) = 0;
IF #product_code_new IS NOT NULL
AND #product_code_old IS NOT NULL
INSERT INTO Product_Function_Staging VALUES (#product_code_new,CURRENT_TIMESTAMP);
The above will work fine ,if there is only one row updated,what if there is more than one value..the product_code will default to last value
You can change the above part of code to below
Insert into Product_Function_Staging
select product_code ,CURRENT_TIMESTAMP from inserted where product_code is not null
You will get undetermined values for #product_code_new if there are more than one rows updated with ceu_amount>0; Similar for #product_code_old if more than one rows updated with ceu_amount NULL or equal 0.
Can you post some sample data?
I would not use variables like that in a trigger, since what causes the trigger could be an update to more than one row, at which point you would have multiple rows in your updated and deleted tables.
I think we can more safely and efficiently make this insert with one simple query, though I'm assuming you have a unique key to use:
CREATE TRIGGER [dbo].[TRG_Product_Function_Modified] ON [dbo].[Product_Function]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO Product_Function_Staging
SELECT i.product_code, CURRENT_TIMESTAMP
FROM inserted i
JOIN deleted d ON i.product_code = d.product_code -- assuming product_code is unique
WHERE i.ceu_amount > 0 -- new value > 0
AND ISNULL(d.ceu_amount, 0) = 0; -- old value null or 0
END;
I'm not sure where you need to check for nulls in your data, so I've made a best guess in the where clause.
Try using this
CREATE TRIGGER [dbo].[Customer_UPDATE]
ON [dbo].[Customers]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CustomerId INT
DECLARE #Action VARCHAR(50)
SELECT #CustomerId = INSERTED.CustomerId
FROM INSERTED
IF UPDATE(Name)
BEGIN
SET #Action = 'Updated Name'
END
IF UPDATE(Country)
BEGIN
SET #Action = 'Updated Country'
END
INSERT INTO CustomerLogs
VALUES(#CustomerId, #Action)
END

How do you pass identify value to out parameter for an UPDATE?

So far I have something like the following.
However I'm not sure what to do when I perform UPDATE - from another question here I found that you need to store the OUTPUT INSERTED result to a table because the update (or insert) may affect multiple rows? I tried using SCOPE IDENTITY but it return NULL on the UPDATE. Anyway if I use the table - then how do I get an individual integer that I can pass to the out parameter? Or do I have change the out parameter to a different type like a collection?
ALTER PROCEDURE [Data].[UpdateRecord]
#theValue decimal(4,2) = NULL,
#updatetime datetimeoffset(7),
#maxintervaltime datetimeoffset(7),
#recordID int = NULL output
AS
declare #mytable as TABLE
(
Id int
)
begin tran
if exists (select * from Data.theValue with (updlock,serializable) where Data.theValue.maxintervaltime = #maxintervaltime)
begin
update Data.theValue set theValue = #theValue, updatetime = #updatetime, maxintervaltime = #maxintervaltime
where Data.theValue.maxintervaltime = #maxintervaltime
-- OUTPUT INSERTED.id into #mytable (this line is wrong)
end
else
begin
insert into Data.theValue(theValue, updatetime, maxintervaltime) values(#theValue, #updatetime, #maxintervaltime);
SET #recordID = SCOPE_IDENTITY();
end
commit tran
The output clause should be placed between update and from/where clause. UPDATE can affect multi rows so you have to ensure your logic is correct.
update Data.theValue set theValue = #theValue, updatetime = #updatetime, maxintervaltime = #maxintervaltime
OUTPUT INSERTED.id into #mytable
where Data.theValue.maxintervaltime = #maxintervaltime
SET #recordID = top 1 id from #mytable

Create Transaction and Trigger on a field

Please am new in vb.net and sql server, I have created a two tables in database called Service and Trans.
Create Table Service(
ServiceID int,
ServiceName varchar(30),
ServiceStartValue int
);
Create Table Trans(
EntryTS datetime,
EntryCounter int,
ServedTS datetime,
ServedCounter int,
Skipped int
);
I am trying to create a 'transaction and trigger' that will check and update ServedCounter based on the values in EntryCounter upon ServiceID which the update statement must not allow the ServedCounter > EntryCounter.
I don't quite understand the full requirement, but here's how you can prevent a update (or insert) from happening with a trigger.
CREATE TRIGGER Trans_upd_trg ON Trans AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
--Don't allow the update
IF EXISTS(SELECT 1 FROM inserted WHERE ServedCounter > EntryCounter)
RAISERROR ('ServedCounter > EntryCounter', 16, 1 );
END
GO
Within the context of the trigger you have two logical tables, INSERTED, and DELETED.
These tables contain the old and new values. (deleted is empty for a insert operation)
Hope that helps.
Use a Instead Of Trigger
CREATE TRIGGER Trans_upd_trg
ON Trans
Instead OF INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT *
FROM inserted
WHERE ServedCounter > EntryCounter)
AND EXISTS (SELECT *
FROM deleted)
UPDATE A
SET EntryTS = I.EntryTS,
EntryCounter = I.EntryCounter,
ServedTS = I.ServedTS,
ServedCounter = I.ServedCounter,
Skipped = I.Skipped
FROM Trans A
JOIN inserted I
ON A.EntryTS = I.EntryTS
AND A.ServedTS = I.ServedTS
WHERE i.ServedCounter > i.EntryCounter
ELSE
INSERT INTO Trans
SELECT *
FROM inserted
WHERE ServedCounter > EntryCounter
END
GO

SQL Server check constraint failing on correct input

I'm using a check constraint on a table to restrict what values are inserted in the table..
Here's an explanation of what I'm trying to do
If any Product(sedan) is associated to a specific ObjLevel (Toyota) then the same Product cannot be associated to another specific ObjLevel (Lexus)
After I apply the check constraint on the table, any insert containing ObjLevel "toyota" or "lexus" fails..
create table ObjLevel(
OLID int identity,
Name varchar(50) not null
)
insert into ObjLevel values('Ford')
insert into ObjLevel values('Toyota')
insert into ObjLevel values('Lexus')
insert into ObjLevel values('GM')
insert into ObjLevel values('Infiniti')
create table ObjInstance(
OLIID int identity (20,1),
OLID int
)
insert into ObjInstance values(1)
insert into ObjInstance values(2)
insert into ObjInstance values(3)
insert into ObjInstance values(4)
insert into ObjInstance values(5)
create table Product(
PID int identity(50,1),
Name varchar(20)
)
insert into Product values ('sedan')
insert into Product values ('coupe')
insert into Product values ('hatchback')
create table ObjInstanceProd(
OLIID int,
PID int
)
create FUNCTION [dbo].[fnObjProd] (#Pid int) RETURNS bit WITH EXECUTE AS CALLER AS
BEGIN
DECLARE #rv bit
DECLARE #cnt int
SET #cnt = 0
SET #rv = 0
SET #cnt=
(Select Count(*) from ObjInstanceProd olip
join ObjInstance oli
on olip.OLIID = oli.OLIID
join ObjLevel ol
on ol.OLID = oli.OLID
where ol.Name in ('Toyota','Lexus')
and PID = #Pid)
if(#cnt>0)
SET #rv = 1
RETURN #rv
END
ALTER TABLE [dbo].[ObjInstanceProd] WITH CHECK ADD CONSTRAINT [CK_OLIP] CHECK ([dbo].[fnObjProd]([PID])=0)
--Insert Statement
insert into ObjInstanceProd(OLIID,PID) values (22,51)
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the CHECK constraint "CK_OLIP". The conflict occurred in database "tmp", table "dbo.ObjInstanceProd", column 'PID'.
The statement has been terminated.
--Execute Function
select [dbo].[fnObjProd] (51)
0
Initially the Table ObjInstanceProd is empty.. So, no matter what value I put in the table, as long as the function in the constraint returns a 0, it should accept it.. But it does not..
The function is correctly returning a 0 (when executed independently), but for some reason, the check constraint returns a 1
When the CHECK constraint fires, the row is already in the table. Therefore, the function is called, and since there is a row returned by the query, the function returns 1, not 0. Try this. Drop the constraint, insert your row successfully, and then run this query:
SELECT OLIID, PID, dbo.fnObjProd([PID]) FROM dbo.ObjInstanceProd;
It should return 1 for every value of PID. Try to add the constraint now. It will fail for the same reason.
Have you considered using a trigger for this? If you use a check constraint, this will turn any multi-row insert or update into a cursor behind the scenes. This can absolutely kill performance and concurrency depending on how you touch your tables. Here is a simple INSTEAD OF INSERT trigger to prevent bad values going in with a single operation, even for a multi-row insert:
CREATE TRIGGER dbo.trObjProd
ON dbo.ObjInstanceProd
INSTEAD OF INSERT AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS
(
SELECT 1 FROM inserted
WHERE EXISTS
(
SELECT 1
FROM dbo.ObjInstanceProd AS olip
INNER JOIN dbo.ObjInstance AS oli
ON olip.OLIID = oli.OLIID
INNER JOIN dbo.ObjLevel AS ol
ON ol.OLID = oli.OLID
WHERE
ol.Name in ('Toyota','Lexus')
AND olip.PID = inserted.PID
)
)
BEGIN
INSERT ObjInstanceProd(OLIID, PID)
SELECT OLIID, PID FROM inserted;
END
ELSE
BEGIN
RAISERROR('At least one value was not good.', 11, 1);
SELECT OLIID, PID FROM inserted;
END
END
GO
If you're going to stick with a function, this is a much more efficient approach, however you need to define a way to determine that the current row being inserted is excluded from the check - I couldn't determine how to do that because there are no constraints on dbo.ObjInstanceProd. Is OLIID, PID unique?
ALTER FUNCTION [dbo].[fnObjProd]
(
#Pid INT
)
RETURNS BIT
WITH EXECUTE AS CALLER
AS
BEGIN
RETURN
(
SELECT CASE WHEN EXISTS
(
SELECT 1
FROM dbo.ObjInstanceProd AS olip
INNER JOIN dbo.ObjInstance AS oli
ON olip.OLIID = oli.OLIID
INNER JOIN dbo.ObjLevel AS ol
ON ol.OLID = oli.OLID
WHERE
ol.Name in ('Toyota','Lexus')
AND olip.PID = #Pid
) THEN 1 ELSE 0 END
);
END
GO

Resources