Run a Script Each time a table is modified - sql

I have created a batch script which prints information on the table into a label and saves it into pdf. At the moment, I am giving the script a number, which is the ItemCode and it prints out the rest of the information in the table.
Well now I'm going much further, my goal is to run the script each time the table is modified, or a new row is added or even if a single field is modified. When this happens it would check which row has been modified and It would run the script with the ItemCode which has been modified.
Been looking for something similar to this but couldn't find anything precise enough, so any help would be nice!

The code below is part of a trigger I am using. It writes old values and new values into a special table to track all important changes. The updated_idx is send with the SQL command to tell, what user is doing it.:
USE [Demo] -- database
GO
/****** Object: Trigger [dbo].[update_Address] Script Date: 26.07.2018 11:16:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[update_Address] ON [dbo].[Address] for UPDATE AS
DECLARE #fieldname varchar(128) = '- empty -'
DECLARE #newValue varchar(2048) = '- empty -'
DECLARE #oldValue varchar(2048)= '- empty -'
DECLARE #Updated int = datediff(s,'01/01/1970',SYSUTCDATETIME())
DECLARE #Updated_IDX int = -1
DECLARE #ID int = -1
DECLARE db_cursor CURSOR FOR SELECT Address_id, Updated_IDX FROM inserted
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #ID, #Updated_IDX -- this takes the current id
WHILE ##FETCH_STATUS = 0
BEGIN
SET NOCOUNT ON
If UPDATE([ZipCode])
BEGIN
SELECT #OldValue=b.ZipCode, #NewValue=a.ZipCode FROM inserted a, deleted b
IF #NewValue <> #OldValue
BEGIN
INSERT INTO TransactionLog ([ID],[TableName],[Type],[FieldName],[newValue],[oldValue],[Updated],[Updated_IDX]) values (#ID,'Address','U','ZipCode',#newValue,#oldValue,#Updated,#Updated_IDX);
END
END
If UPDATE([City])
BEGIN
SELECT #OldValue=b.City, #NewValue=a.City FROM inserted a, deleted b
IF #NewValue <> #OldValue
BEGIN
INSERT INTO TransactionLog ([ID],[TableName],[Type],[FieldName],[newValue],[oldValue],[Updated],[Updated_IDX]) values (#ID,'Address','U','City',#newValue,#oldValue,#Updated,#Updated_IDX);
END
END
FETCH NEXT FROM db_cursor INTO #ID, #Updated_IDX
End
CLOSE db_cursor
DEALLOCATE db_cursor

Related

Transaction context in use by another session - SQL SERVER

Good afternoon,
I'm creating a trigger to be present on 3 databases, one of which is on a linkedserver.
When inserting data in one of the databases, the objective is to replicate the information in the remaining databases.
Whenever I do an insert, I get the error 'Transaction context in use by another session.'
Anyone knows why this is happening? And is there a way to surprass this?
DROP TRIGGER Insertdata
USE A
GO
SET ANSI_NULLS ON
SET ANSI_WARNINGS ON
SET QUOTED_IDENTIFIER OFF
GO
CREATE TRIGGER Insertdata ON DATA WITH ENCRYPTION FOR INSERT AS
SET NOCOUNT ON
if(Upper((SELECT DB_NAME()))=UPPER('A')) Begin
----------------------------------------------------------------------------------------------
--------------------------------------------- Insert Data ------------------------------------
----------------------------------------------------------------------------------------------
--Create variable for database name and query variable
DECLARE #DB_Name VARCHAR(100) -- database name
DECLARE #Local VARCHAR(1) -- database name
DECLARE #query VARCHAR(4000) -- query variable
--Declare the cursor
DECLARE db_cursor CURSOR LOCAL FOR
-- Populate the cursor with the selected database name
select t.name,t.islocal from (
Select name, '1' as islocal from master.sys.databases WHERE name in ('A','B','C')
union all
select name, '0' as islocal from LinkedServer.master.sys.databases WHERE name in ('A','B','C')
)t
OPEN db_cursor
--Moves the cursor to the first point and put that in variable name
FETCH NEXT FROM db_cursor INTO #DB_Name,#Local;
-- while loop to go through all the DB selected
WHILE ##FETCH_STATUS = 0
BEGIN
if(#Local ='0') begin
Set #DB_Name = 'LinkedServer.' + #DB_Name
end
-- Check if received data exists
SET #query = N'Select id from ' + #DB_Name +'.dbo.data where data.id='''+(Select inserted.id from inserted)+''''
EXEC(#query)
--If doesnt exists, then insert data
if ##ROWCOUNT < 1 begin
SET #query = N'INSERT INTO ' + #DB_Name +'.dbo.data(id,name,surname)
values('+''''+(select inserted.id from inserted)+''''
+','+''''+(select convert(varchar,inserted.name) from inserted)+''''
+','+''''+(select convert(varchar,inserted.surname) from inserted)+''''
+')'
EXEC (#query)
--Fetch the next record from the cursor
end
FETCH NEXT FROM db_cursor INTO #DB_Name,#Local
END
--Close and deallocate cursor
CLOSE db_cursor
DEALLOCATE db_cursor
End
GO
SET ANSI_NULLS OFF
SET ANSI_WARNINGS OFF
SET QUOTED_IDENTIFIER OFF
GO
EDIT:
I think the problem here is the fact, that my trigger is being executed on different databases. For example, the current database is A, so the trigger should execute on B and C, but I realized that when A calls B, B executes the trigger aswell. C doesn't executes because gives the error on B. Thought db_name() would fix this, but guess it doesn't

Fetch SQL Server stored procedure output results into table

I have tried some solutions from the internet but they are not working for me .
My task is to get the out put of stored procedure into a table.The data is being inserted inside a cursor by loop . I am creating temporary table to store and display the data.
My code:
ALTER procedure [dbo].[usp_Test]
AS
SET NOCOUNT ON;
declare #caseId int;
declare #CHG_ID int;
declare #HEAR_ID int;
SET #CHG_ID = 1
set #testid = 1;
DECLARE db_cursor CURSOR FOR
SELECT C_CASE_ID
FROM table1 // tHERE WILL BE MULTIPLE CASEIDS
-- here I am trying to delete the temporary table, but it does not work
IF OBJECT_ID('tempdb..##test_temp_table') IS NOT NULL
TRUNCATE TABLE ##test_temp_table
ELSE
CREATE TABLE test_temp_table(HEAR_ID int)
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #caseId
WHILE ##FETCH_STATUS = 0
BEGIN
insert into test_temp_table
EXEC STOREDPROCTEST2 #caseId, 1, #HEAR_ID OUTPUT;
-- LOOP THROUGH THE CURSOR TO GET ALL CASE IDS
FETCH NEXT FROM db_cursor INTO #caseId
SELECT HEAR_ID FROM test_temp_table;
END
CLOSE db_cursor
DEALLOCATE db_cursor;
I have two issues:
I cannot delete the temporary table
I am not seeing any output from the temporary table
[##test_temp_table] and [test_temp_table] are two different tables. First one is a global temp table, second one is a user table. I believe you want to replace the user table with the global temp table, i.e. replace object [test_temp_table] with [##test_temp_table]. or vice versa. In the end, you have to ensure you are querying the correct table.

Trigger Failing when calling Stored Procedure

I am truly hoping someone can help me out...
I have a trigger to handle the insert of a new record to a table. This trigger, as you will see below, inserts a record into another table, which in turns executes a trigger on that table, that calls a stored procedure (I tried to do it within the trigger itself, but it failed and was difficult to test where it was failing, so I moved it into its own little unit.)
Within the stored procedure, there is a call to extract information from the Active Directory database (ADSI) and update the newly inserted record. However, this is where it fails when called by the trigger. When I call it by simply executing it, and passing along the record to be updated, it works great... Can anyone point me in the right direction? Please!!!
Trigger #1 in YYY
USE [YYY]
GO
/****** Object: Trigger [dbo].[NewCustodian] Script Date: 08/04/2014 09:38:11 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[NewCustodian]
ON [YYY].[dbo].[Custodians]
AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
DECLARE #CaseID varchar(20);
DECLARE DBcursor CURSOR FOR
SELECT [XXX].[dbo].[tblCase].CaseID from [XXX].[dbo].[tblCase] Where [XXX].[dbo].[tblCase].SQLSVR_Case_ID = 'YYY';
Open DBcursor; FETCH DBCursor into #CaseID;
CLOSE DBcursor; DEALLOCATE DBcursor;
DECLARE #NAME varchar(255);
DECLARE #TAG varchar(255);
SELECT #NAME = name FROM inserted;
SELECT #TAG = tag FROM inserted;
IF NOT EXISTS (Select eID from [XXX].[dbo].[tblNames]
WHERE eID = #TAG and CaseID = #CaseID)
BEGIN
INSERT INTO [XXX].[dbo].[tblNames] (CaseID, Name, eID)
Values (#CaseID, #NAME, #Tag);
END
END
Trigger #2 in XXX
USE [XXX]
GO
/****** Object: Trigger [dbo].[tblNames_New] Script Date: 08/04/2014 08:56:43 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:
-- Create date:
-- Description:
-- =============================================
ALTER TRIGGER [dbo].[tblNames_New]
ON [XXX].[dbo].[tblNames]
AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
DECLARE #NamesID varchar(10)
DECLARE #TAG varchar(10);
DECLARE #return_value int
SELECT #NamesID = namesID FROM inserted
EXEC dbo.UpdateNames #NamesID;
End
Stored procedure:
USE [XXX]
GO
/****** Object: StoredProcedure [dbo].[UpdateNames] Script Date: 08/04/2014 08:14:52 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:
-- Create date:
-- Description:
-- =============================================
ALTER PROCEDURE [dbo].[UpdateNames]
#NamesID int
AS
BEGIN
SET FMTONLY OFF;
SET NOCOUNT ON;
DECLARE #eID varchar(10);
DECLARE #TAG varchar(10);
DECLARE #SQL nvarchar(555);
DECLARE #DBresults as table (
eID nvarchar(100),
mobile nvarchar(100),
mail nvarchar(100),
phone nvarchar(100),
name nvarchar(50),
legacyExchangeDN nvarchar(100),
Title nvarchar(100),
homeDirectory nvarchar(100));
DECLARE #mobile nvarchar(100)
DECLARE #mail nvarchar(100)
DECLARE #phone nvarchar(100) = 'Error'
DECLARE #name nvarchar(100)
DECLARE #legacyExchangeDN nvarchar(100)
DECLARE #Title nvarchar(100) = 'Error'
DECLARE #homeDirectory nvarchar(100)
SET #eID = (Select eID from [XXX].[dbo].[tblNames] Where NamesID = #NamesID)
SET #SQL = N'SELECT * FROM OpenQuery ( ADSI, ''SELECT homeDirectory,Title,legacyExchangeDN,displayName, telephoneNumber, mail, mobile,samAccountName
FROM ''''LDAP://domain.com''''
WHERE objectClass = ''''User'''' and samAccountName = ''''' + #eID+ ''''''') As tblADSI'
INSERT INTO #DBresults
EXEC sp_executesql #SQL
DECLARE DBcursor CURSOR FOR
SELECT * from #DBresults;
Open DBcursor; FETCH DBCursor into #eID, #mobile, #mail, #phone, #Name, #legacyExchangeDN, #Title, #homeDirectory;
CLOSE DBcursor; DEALLOCATE DBcursor;
UPDATE XXX.dbo.tblNames
SET Job_Title = #Title,
Phone = #Phone
Where NamesID = #NamesID;
END
As I said in my comment - a trigger should be extremely small, nimble, lean - do not do any extensive and time-consuming processing inside a trigger, and avoid anything that would cause performance bottlenecks, especially cursors!
The reason for this is the fact that a trigger will be triggered whenever an INSERT operation happens, you have no control over when and how many times it gets called. The main app will wait and hang while the trigger is at work - so therefore, don't make this a long time - return very quickly from your trigger to go on with your main app.
My approach would be:
create a new separate table where you insert some key pieces of information into from your first original trigger
CREATE TABLE NewCustodianInserted
(
ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
CaseID VARCHAR(20),
Tag VARCHAR(255),
Handled BIT DEFAULT (0)
);
change your original trigger on the Custodians table to just insert those key pieces of information into your new "command" table:
CREATE TRIGGER [dbo].[NewCustodian]
ON [YYY].[dbo].[Custodians]
AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
-- insert key pieces about the new custodian into "command" table
INSERT INTO dbo.NewCustodianInserted (CaseID, Tag)
SELECT i.CaseId, i.Tag
FROM Inserted i
WHERE NOT EXISTS (SELECT * FROM [XXX].[dbo].[tblNames] WHERE eID = i.Tag AND CaseID = i.CaseID)
END
in a separate process, e.g. a SQL Server Agent job that is scheduled to run every 5 mînutes (or whatever makes sense for your application), read the "command" table, get the new custodians to handle, call that long-running stored procedure updating Active Directory from it. Here, since this runs asynchronously from your main application, it's ok to use a cursor which you almost have to since you want to call a stored procedure for every row in your new table.
CREATE PROCEDURE HandleNewCustodians
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CaseID VARCHAR(20);
DECLARE #Tag VARCHAR(255);
DECLARE #NamesID varchar(10);
DECLARE CustodianCursor CURSOR FAST_FORWARD
FOR
SELECT CaseID, Tag FROM dbo.NewCustodianInserted WHERE Handled = 0
OPEN CustodianCursor
FETCH NEXT FROM CustodianCursor INTO #CaseID, #Tag;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #NamesID = NameID
FROM [XXX].[dbo].[tblNames] WHERE eID = #Tag AND CaseID = #CaseID
EXEC dbo.UpdateNames #NamesID;
FETCH NEXT FROM CustodianCursor INTO #CaseID, #Tag;
END
CLOSE CustodianCursor;
DEALLOCATE CustodianCursor;
END

Trigger that triggers when a single column (boolean) is updated from TRUE to FALSE (not vice versa)

I have limited experience creating trigger. Here is one I created recently:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[NameTrigger]
ON [dbo].[dbUSNs]
after INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
declare #URNs varchar(100)
declare #Forenames varchar(200)
declare #Surname varchar(100)
declare #Reference int
set #URNs = ''
set #Forenames = ''
set #Surname = ''
set #Reference = 0
declare PersonCursor cursor FOR
select
Replace(Replace(Replace(Forenames,char(39),''),'-',''),' ',''),
Replace(Replace(Replace(Surname,char(39),''),'-',''),' ',''),
Reference,USN from inserted
open PersonCursor
FETCH NEXT FROM PersonCursor INTO #Forenames, #Surname, #Reference, #URNs
WHILE ##FETCH_STATUS = 0
BEGIN
update Person set ForenamesCleansed = #Forenames, SurnameCleansed= #Surname, URNsCleansed=#URNs
where reference = #Reference
FETCH NEXT FROM PersonCursor INTO #Forenames, #Surname, #Reference,#URNs
END
CLOSE PersonCursor
DEALLOCATE PersonCursor
END
I want to created a trigger that is triggered when a specific column (called update) changes from 0 to 1. Is this possible?
You're using Sql Server so:
you have two special tables:
INSERTED and DELETED.
These tables works in this way:
When you apply an INSERT statement in the trigger you'll find full the table INSERTED
When you apply an UPDATE statement in the trigger you'll find full the table INSERTED and DELETED
When you apply an DELETE statement in the trigger you'll find full the table DELETED
In the INSERTED table you'll find all new value, in the DELETED, you'll find the old value, so when you write a trigger you can check the old and the new value of your boolean field, so when the condition is satisfacted you can apply the other changes you want.
You just need an extra IF within your cursor logic:
FETCH NEXT FROM PersonCursor INTO #Forenames, #Surname, #Reference, #URNs
WHILE ##FETCH_STATUS = 0
BEGIN
IF (inserted.[Update] = 0 AND deleted.[Update] = 1) --Added
BEGIN
update Person set ForenamesCleansed = #Forenames, SurnameCleansed= #Surname, URNsCleansed=#URNs
where reference = #Reference
FETCH NEXT FROM PersonCursor INTO #Forenames, #Surname, #Reference,#URNs
END
END
END
You may want to invert the logic, there is a mismatch between the question and its title regarding the change direction.

If statement in AFTER DELETE trigger not working

I have a trigger which I would like to do something if a certain condition is met.
If a user causes the system to remove a row from a security table, and that row happens to fit particular criteria, I want to run a stored proc. The problem that I'm running into is that the trigger behaves as though the if statement's criteria is not met. I know that the criteria is being met, because I tried piping the variable in question into a table, and the value I came up with was correct.
If I remove the IF statement, the procedure runs (though indiscriminately...it runs as expected, regardless of the value of the personorgroup field in the deleted table, which is not what I want it to do.)
Here is the trigger:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
alter TRIGGER dbo.SecurityPersonRemoval
ON dbo.[security]
AFTER delete
AS
BEGIN
SET NOCOUNT ON;
declare #person int
declare #thing int
select #person = personorgroup from deleted
select #thing = thing from deleted
if #person = '37671721'
exec docsadm.ethicalwall #thing
END
GO
Ideas?
Your statement probably fails because "deleted" can contain multiple records. Assigning the value of the select to a scalar will only return the first personID and the rest will be ignored. If you must call docsamd.ehticalwall for each person deleted, you could use a cursor:
declare #personId int,
#thingId int,
#fetchStatus int
declare myDeletedPeople cursor for select personorgroup, thing from deleted readonly
open myDeletedPeople
fetch myDeletedPeople into #personId, #thingId
select #fetchStatus = ##FETCH_STATUS
while (#fetchStatus=0)
begin
if(#personId = '37671721')
begin
exec docsadm.ethicalwall #thingId
end
fetch myDeletedPeople into #personId, #thingId
select #fetchStatus = ##FETCH_STATUS
end
close myDeletedPeople
deallocate myDeletedPeople