SQL Trigger not updating field - sql-server

When i select a value in one table, the same value must populate the other table.
For example:
Please see image attached:
The problem i am having is when i update the first field stage (left hand side), to say Stage2, the uStage field stays the same.. It should update to Stage2 as well..
The Ustage table has the same stage values as the Stage table so I know it needs to select the corresponding Value however i have tried all sorts to get this to work but it doesnt want to update:
Here is my Trigger code:
USE [SKY]
GO
/****** Object: Trigger [dbo].[SetIT] Script Date: 2018/10/04 19:52:06 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[SetIT] ON [dbo].[AMGR_Opportunity_Tbl]
AFTER INSERT
AS
DECLARE #RecordId int
DECLARE #Stage varchar(750)
Declare #ID int
begin
--check to see if we have any records in the inserted set
IF EXISTS( SELECT * FROM inserted ) BEGIN
--set up the cursor that we use to iterate over the recordset
DECLARE I CURSOR LOCAL FAST_FORWARD FOR
SELECT Record_Id FROM Inserted;
OPEN I
FETCH NEXT FROM I INTO #RecordId;
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #Stage = STAGE FROM STAGE WHERE ID = #ID;
--insert the UDF value
INSERT INTO O_Ustage(Client_id,Contact_Number,O_Ustage)
SELECT Opp_Id, 0, #Stage
FROM inserted WHERE Record_Id = #RecordId AND Opp_Type = 0;
--update the current stage
UPDATE stage set stage=#stage WHERE
ID=#ID;
FETCH NEXT FROM I INTO #RecordId;
END
CLOSE I
DEALLOCATE I
END
END
GO
Please assist in helping me figure out how to update the USTAGE field.
Below is the data that is in the Stage table
If i change the field 'Stage' below, then 'Ustage' also needs to change.. At the moment it is not doing it:
The opportunity table has 50 columns and stage table 2 columns.
When I change the stage on the opportunity table to Stage2 then the Ustage value must also change to Stage2.. Both values need to correspond

Your trigger code has a lot of problems, but here's one that will definitely cause it not to work:
SELECT #Stage = STAGE FROM STAGE WHERE ID = #ID;
Prior to that line of code, you never populate #ID, so it's value will be NULL. Therefore you will get NULL for #Stage, which is what you later insert into the O_UStage column.
Maybe you meant to use #RecordId on that line instead of #ID?

Related

SQL Server stored procedure inserts second row when it was supposed to read what it just inserted when called with the same parameter

I have a SQL Server stored procedure that is executed by passing CustNum and is supposed to return Cust_Key. If the CustNum exists, then it should return the existing Cust_Key, else it should insert the CustNum into the table and returns the newly created Cust_Key.
In the table, Cust_Key_Initial is an identity and Cust_Key takes the value of Cust_Key_Initial initially. Table is designed like so because Cust_Key could change but Cust_Key_Initial will not change.
Now the issue I am having is, once is a while, the stored procedure inserts a CustNum to the table twice in a split second difference instead of reading Cust_Key for the CustNum that was just inserted. I don't understand how this happens, please help me with this. The definition of the procedure is below. Also there is no speed issue here, it usually returns in about 100 milliseconds.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE usp_GetCustKey
#CustNum Varchar(10) NULL
AS
SET NOCOUNT ON;
BEGIN
IF NOT EXISTS (SELECT 1 FROM dbo.value_Customer WITH (NOLOCK)
WHERE CustNum= #CustNum)
BEGIN
INSERT INTO dbo.value_Customer (Cust_Key, CustNum, CustNum_Initial)
SELECT -1, #CustNum, #CustNum
UPDATE dbo.value_Customer
SET Cust_Key = Cust_Key_Initial
WHERE Cust_Key_Initial = SCOPE_IDENTITY()
END
-- Return Value
SELECT TOP 1 Cust_Key
FROM dbo.value_Customer WITH (NOLOCK)
WHERE CustNum = #CustNum
END
This is (imo) a good approach for avoiding race conditions, which it seems is the issue you're having here. The procedure first attempts to find the Cust_Key_Initial. If it's found, i.e. ##rowcount!=0, then it's returned. If it's not found then it's created and used to update the table to set the Cust_Key to equal the Cust_Key_Initial.
ALTER PROCEDURE usp_GetPersonKey
#CustNum Varchar(10) NULL
AS
set nocount on;
declare
#Cust_Key_Initial int;
select #Cust_Key_Initial=Cust_Key
from dbo.value_Customer /*WITH (NOLOCK)*/
where CustNum = #CustNum;
if ##rowcount=0
begin
insert into dbo.value_Customer (Cust_Key, CustNum, CustNum_Initial)
select -1, #CustNum, #CustNum
select #Cust_Key_Initial=scope_identity();
update dbo.value_Customer
set Cust_Key=Cust_Key_Initial
where Cust_Key_Initial = #Cust_Key_Initial;
end
select #Cust_Key_Initial;
go
It seems to me that Cust_key_Initial is not an identity specified column because normally you cannot insert a row determining its identity so SCOPE_IDENTITY returns you either NULL or the value identity on your table which is different from #CustNum

Trigger AFTER INSERT, UPDATE, DELETE to call stored procedure with table name and primary key

For a sync process, my SQL Server database should record a list items that have changed - table name and primary key.
The DB already has a table and stored procedure to do this:
EXEC #ErrCode = dbo.SyncQueueItem "tableName", 1234;
I'd like to add triggers to a table to call this stored procedure on INSERT, UPDATE, DELETE. How do I get the key? What's the simplest thing that could possibly work?
CREATE TABLE new_employees
(
id_num INT IDENTITY(1,1),
fname VARCHAR(20),
minit CHAR(1),
lname VARCHAR(30)
);
GO
IF OBJECT_ID ('dbo.sync_new_employees','TR') IS NOT NULL
DROP TRIGGER sync_new_employees;
GO
CREATE TRIGGER sync_new_employees
ON new_employees
AFTER INSERT, UPDATE, DELETE
AS
DECLARE #Key Int;
DECLARE #ErrCode Int;
-- How to get the key???
SELECT #Key = 12345;
EXEC #ErrCode = dbo.SyncQueueItem "new_employees", #key;
GO
The way to access the records changed by the operation is by using the Inserted and Deleted pseudo-tables that are provided to you by SQL Server.
Inserted contains any inserted records, or any updated records with their new values.
Deleted contains any deleted records, or any updated records with their old values.
More Info
When writing a trigger, to be safe, one should always code for the case when multiple records are acted upon. Unfortunately if you need to call a SP that means a loop - which isn't ideal.
The following code shows how this could be done for your example, and includes a method of detecting whether the operation is an Insert/Update/Delete.
declare #Key int, #ErrCode int, #Action varchar(6);
declare #Keys table (id int, [Action] varchar(6));
insert into #Keys (id, [Action])
select coalesce(I.id, D.id_num)
, case when I.id is not null and D.id is not null then 'Update' when I.id is not null then 'Insert' else 'Delete' end
from Inserted I
full join Deleted D on I.id_num = D.id_num;
while exists (select 1 from #Keys) begin
select top 1 #Key = id, #Action = [Action] from #Keys;
exec #ErrCode = dbo.SyncQueueItem 'new_employees', #key;
delete from #Keys where id = #Key;
end
Further: In addition to solving your specified problem its worth noting a couple of points regarding the bigger picture.
As #Damien_The_Unbeliever points out there are built in mechanisms to accomplish change tracking which will perform much better.
If you still wish to handle your own change tracking, it would perform better if you could arrange it such that you handle the entire recordset in one go as opposed to carrying out a row-by-row operation. There are 2 ways to accomplish this a) Move your change tracking code inside the trigger and don't use a SP. b) Use a "User Defined Table Type" to pass the record-set of changes to the SP.
You should use the Magic Table to get the data.
Usually, inserted and deleted tables are called Magic Tables in the context of a trigger. There are Inserted and Deleted magic tables in SQL Server. These tables are automatically created and managed by SQL Server internally to hold recently inserted, deleted and updated values during DML operations (Insert, Update and Delete) on a database table.
Inserted magic table
The Inserted table holds the recently inserted values, in other words, new data values. Hence recently added records are inserted into the Inserted table.
Deleted magic table
The Deleted table holds the recently deleted or updated values, in other words, old data values. Hence the old updated and deleted records are inserted into the Deleted table.
**You can use the inserted and deleted magic table to get the value of id_num **
SELECT top 1 #Key = id_num from inserted
Note: This code sample will only work for a single record for insert scenario. For Bulk insert/update scenarios you need to fetch records from inserted and deleted table stored in the temp table or variable and then loop through it to pass to your procedure or you can pass a table variable to your procedure and handle the multiple records there.
A DML trigger should operate set data else only one row will be processed. It can be something like this. And of course use magic tables inserted and deleted.
CREATE TRIGGER dbo.tr_employees
ON dbo.employees --the table from Northwind database
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
declare #tbl table (id int identity(1,1),delId int,insId int)
--Use "magic tables" inserted and deleted
insert #tbl(delId, insId)
select d.EmployeeID, i.EmployeeID
from inserted i --empty when "delete"
full join deleted d --empty when "insert"
on i.EmployeeID=d.EmployeeID
declare #id int,#key int,#action char
select top 1 #id=id, #key=isnull(delId, insId),
#action=case
when delId is null then 'I'
when insId is null then 'D'
else 'U' end --just in case you need the operation executed
from #tbl
--do something for each row
while #id is not null --instead of cursor
begin
--do the main action
--exec dbo.sync 'employees', #key, #action
--remove processed row
delete #tbl where id=#id
--refill #variables
select top 1 #id=id, #key=isnull(delId, insId),
#action=case
when delId is null then 'I'
when insId is null then 'D'
else 'U' end --just in case you need the operation executed
from #tbl
end
END
Not the best solution, but just a direct answer on the question:
SELECT #Key = COALESCE(deleted.id_num,inserted.id_num);
Also not the best way (if not the worst) (do not try this at home), but at least it will help with multiple values:
DECLARE #Key INT;
DECLARE triggerCursor CURSOR LOCAL FAST_FORWARD READ_ONLY
FOR SELECT COALESCE(i.id_num,d.id_num) AS [id_num]
FROM inserted i
FULL JOIN deleted d ON d.id_num = i.id_num
WHERE (
COALESCE(i.fname,'')<>COALESCE(d.fname,'')
OR COALESCE(i.minit,'')<>COALESCE(d.minit,'')
OR COALESCE(i.lname,'')<>COALESCE(d.lname,'')
)
;
OPEN triggerCursor;
FETCH NEXT FROM triggerCursor INTO #Key;
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC #ErrCode = dbo.SyncQueueItem 'new_employees', #key;
FETCH NEXT FROM triggerCursor INTO #Key;
END
CLOSE triggerCursor;
DEALLOCATE triggerCursor;
Better way to use trigger based "value-change-tracker":
INSERT INTO [YourTableHistoryName] (id_num, fname, minit, lname, WhenHappened)
SELECT COALESCE(i.id_num,d.id_num) AS [id_num]
,i.fname,i.minit,i.lname,CURRENT_TIMESTAMP AS [WhenHeppened]
FROM inserted i
FULL JOIN deleted d ON d.id_num = i.id_num
WHERE ( COALESCE(i.fname,'')<>COALESCE(d.fname,'')
OR COALESCE(i.minit,'')<>COALESCE(d.minit,'')
OR COALESCE(i.lname,'')<>COALESCE(d.lname,'')
)
;
The best (in my opinion) way to track changes is to use Temporal tables (SQL Server 2016+)
inserted/deleted in triggers will generate as many rows as touched and calling a stored proc per key would require a cursor or similar approach per row.
You should check timestamp/rowversion in SQL Server. You could add that to the all tables in question (not null, auto increment, unique within database for each table/row etc).
You could add a unique index on that column to all tables you added the column.
##DBTS is the current timestamp, you can store today's ##DBTS and tomorrow you will scan all tables from that to current ##DBTS. timestamp/rowversion will be incremented for all updates and inserts but for deletes it won't track, for deletes you can have a delete only trigger and insert keys into a different table.
Change data capture or change tracking could do this easier, but if there is heavy volumes on the server or large number of data loads, partition switches scanning the transaction log becomes a bottleneck and in some cases you will have to remove change data capture to save the transaction log from growing indefinetely.

T-SQL Trigger INSERT UPDATE

I'm fairly new in T-SQL but i have a question concerning triggers. I have written a trigger that is used in my stored procedure witch inserts and updates data.
Now the trigger works when any row is updated in table [dbo].[users]
I don't know how to modify this trigger so that if data is inserted into [dbo].[users] table activate the trigger (this works now), but how to acheive an update scenario on the same trigger that if only a specific row is UPDATED in [dbo].[users] table that only then the trigger should be activated.
For example
If a new user is inserted and all rows are inserted in this table - activate trigger
If an old trigger is updated but only a specific field in this table is updated (working_state is the name of a column) then only should the trigger should be activated.
Source code what I have is shown here:
ALTER TRIGGER [dbo].[t_temp_triger_name]
ON [dbo].[users]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE #working_state char(1),
#user_code char(11)
DECLARE zm CURSOR FOR
SELECT
working_state,
user_code
FROM
inserted
-- Added update statement that activates the trigger only when a specific -
-- column update is executed
UPDATE [dbo].[users]
SET working_state = 1
FROM [dbo].[users] U
INNER JOIN DELETED D ON U.user_code= D.user_code
WHERE U.working_state<> D.working_state
OPEN zm
FETCH NEXT FROM zm INTO #working_state, #user_code
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC [FNF].[dbo].[NamedProcedure]
#working_state, #user_code
FETCH NEXT FROM zm INTO #working_state, #user_code
END
CLOSE zm
DEALLOCATE zm
END
I have came up with a solution
Added this to my code after declaration
Everything works fine.
IF EXISTS (SELECT 1 FROM deleted)
BEGIN
DECLARE zm CURSOR FOR
SELECT
u.working_state,
u.user_code
FROM
inserted U
LEFT JOIN DELETED D ON U.user_code= D.user_code
WHERE U.working_state<> D.working_state
END
ELSE
BEGIN
DECLARE zm CURSOR FOR
SELECT
working_state,
user_code
FROM
inserted
END

Updating the table in a trigger written in sql server

I am trying to create a trigger which is triggered when an insert, delete happens in a table 'Abschaetzung_has_Varianten' and updates a table called 'Flag'. I need to select an ID from the same table to update the Flag table. Is the syntax wrong in writing the SELECT of the ID? I don't seem to get the #abschID from the select. Could anyone help me in this. Thank you.
CREATE TRIGGER trig_update_flag on [Abschaetzung_has_Varianten]
after insert, delete
As
Begin
DECLARE #x INT;
DECLARE #abschID INT;
DECLARE #value INT;
SELECT #value = 1;
SELECT #abschID = (SELECT TOP 1 Abschaetzung_ID FROM Abschaetzung_has_Varianten ORDER BY Abschaetzung_ID DESC);
SELECT #x = Count(*) FROM Flag WHERE AbschaetzID = #abschID
If #x > 0
Begin
UPDATE Flag Set [Flag] = #value WHERE AbschaetzID = #abschID;
end
end
Your code should be more like this:
CREATE TRIGGER trig_update_flag on [Abschaetzung_has_Varianten]
after insert, delete
as
begin
UPDATE Flag Set [Flag] = 1
WHERE AbschaetzID IN (SELECT DISTINCT Abschaetzung_ID FROM INSERTED)
UPDATE Flag Set [Flag] = 1
WHERE AbschaetzID IN (SELECT DISTINCT Abschaetzung_ID FROM DELETED)
end
INSERTED is a special trigger pseudo table that contains all of the updated or inserted records.
DELETED is a special trigger pseudo table that contains all of the deleted records.
This table may contain many records for one invocation of the trigger.
The code above is not the most efficient and may not suit your exact requirements but hopefully you get the idea.

How to write a sql trigger correctly

I want to add a trigger and I never wrote one.
When I update or add a new values to my table, I want to check that some value is allowed.
I started the trigger but I don't really know how to end it:
CREATE TRIGGER isAguide
ON tblDiving
AFTER INSERT, UPDATE AS
BEGIN
DECLARE #Found bit
select #Found = count(*)
from tblAuthorized
WHERE diver_number = #id
and level_name = 'guide'
I want to allow adding ONLY if #found=1.
#id is the value that the user is trying to add to the table.
So first, how do I get the value the user entered in this 'id' Column?
And second, How do I continue the trigger I wrote?
Is it ok I chose AFTER INSERT?
I want to add something like:
if(#found=1) THEN allow adding/updating
else don't allow.
Ty
If you're using an AFTER trigger you can throw an error and roll back the transaction if the id is not found.
CREATE TRIGGER isAguide
ON tblDiving AFTER INSERT, UPDATE AS
BEGIN
DECLARE #Found bit, #id int;
SET #id = SELECT id FROM inserted;
SET #Found =
(
SELECT count(*)
FROM tblAuthorized
WHERE diver_number = #id
AND level_name = 'guide'
);
IF #Found = 0
THEN
RAISERROR ('The ID entered is not valid', 16, 1);
ROLLBACK TRANSACTION;
RETURN
END;
END;
Instead of writing a trigger I'd recommend to create a separate table "Guides" with the diver number in there as the only column (being a primary key).
Add a foreign key reference to the related Id column of your tblDiving table.
Not only will this be compliant with a normalized data structure, it'll also be faster.
Finally you'll need to consider the scenario of changing the level_name of a diver_number from 'guide' to something different after there are some rows inserted into the tblDiving table. How do you want the app to react?

Resources