Insert query as string in database on insert trigger - sql-server

I am trying to create a trigger that will fire every time a new row is created in a table. When that happens, an insert query will be prepared that reflects how the row was inserted. This query will be stored to a table in another database.
To illustrate:
Database A has table products (id, name, duration, isavailable).
Database B has a table mysql_query (id, query)
When a new row (1001, 'test product', 365, 1) is inserted into A.products, the trigger should insert the following into B.mysql_query:
INSERT INTO products (id, name, duration, value, isavailable)
VALUES (1001, test product, 365, 1, 1)
Value by default will always be 1 for the moment.
The code for the trigger so far is:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER c_products
ON migrimi_test.dbo.products
AFTER INSERT
AS
DECLARE #c_id INT;
SET #c_id = (SELECT id from inserted);
DECLARE #c_name INT;
SET #c_name = (SELECT name from inserted);
DECLARE #c_duration INT;
SET #c_duration = (SELECT duration from inserted);
DECLARE #c_isavailable INT;
SET #c_isavailable = (SELECT isvisible from inserted);
BEGIN
SET NOCOUNT ON
INSERT INTO migrimi_temp.dbo.mysql_query (id, mysql_query)
VALUES (DEFAULT, 'INSERT INTO products (id, name, duration, value, isavailable) values ('+CAST(#c_id as nvarchar(50))+', '+'"'+#c_name'+'"'+, '+#c_duration+', 1, '+#c_isavailable+')' )
END
GO
I run the trigger and latter perform an insert. There is an error saying:
Conversion failed when converting the nvarchar value 'test product' to data type int.
I understand that it refers to the substitution of variable #c_name but this is the first trigger I ever write, and I can't tell what exactly is wrong.
A and B are SQL databases.

Ok, there were 3 problems with the trigger:
1- As WEI_DBA pointed out, #c_name was declared int, even though it was in fact an varchar.
2- Apparently every int variable should be cast as a varchar or it will throw the following error:
sql trigger Conversion failed when converting the varchar value to
datatype int
I still don't have this figured out 100% - this is the solution but I am googling the reason why. So don't take my word for granted and feel free to provide more reliable sources or alternative explanations / solutions. After all I am a trigger newbie.
3- While the trigger itself will work, the query inserted in the table mysql_query will be invalid unless the value #c_name (or any varchar value) is surrounded by double quotes.
The final result of a working trigger is the following:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER c_products
ON migrimi_test.dbo.products
AFTER INSERT
AS
DECLARE #c_id INT;
DECLARE #c_name nvarchar(100);
DECLARE #c_duration int;
DECLARE #c_isavailable INT;
Select #c_id = id, #c_name = name, #c_duration = duration, #c_isavailable = isvisible from inserted
BEGIN
SET NOCOUNT ON
INSERT INTO migrimi_temp.dbo.sql_query (query)
VALUES ('INSERT INTO products (id, name, duration, value, isavailable, createdAt, updatedAt) values ('+CAST(#c_id as nvarchar(50))+', '+'"'+#c_name+'"'+',
'+CAST(#c_duration as nvarchar(50))+', 1, '+CAST(#c_isavailable as nvarchar(50))+', Now(), Now());' )
END
GO
Ignore the new fields, there were modifications that I made but have nothing to do with the issue at hand.

Related

How does if update() statement work in update trigger in SQL server?

update:
I update changed values in a column specifically in the other SQL script. So IF UPDATE(column_name) statement should work. For example, When I changed LATTITUDE AND LONGITUDE column in the same row, the if update() can capture the value changes while other if update(column_name) will return false as I did not update those unchanged values. But the problem is, I wish column GROUP_ID to be the same SEQUENCE value as those changes are in same row. The SEQUENCE value should only goes up when hitting next row. However, in my script, there is no way to store current SEQUENCE value in a variable.
I want to group those value changes in columns into SAME SEQUENCE value if in one row otherwise increasing sequence value. However, SEQUENCE values always increases, is it because this update trigger fired more than once if multiple columns changed?
#COUNT_update is used to store current sequence value.
How do I fix it up then?
thanks
ALTER TRIGGER [dbo].[AUDIT_SITE_UPDATE] ON [dbo].[SITE]
AFTER UPDATE
AS
BEGIN
DECLARE #SITE_ID [INT]
DECLARE #SITE_DESCRIPTION [varchar](1000)
DECLARE #SOURCE_SITE_NUMBER [varchar](50)
DECLARE #LATTITUDE [numeric](38, 10)
DECLARE #LONGITUDE [numeric](38, 10)
DECLARE #INSERT_TIME datetime2
DECLARE #INSERT_USER [varchar](256)
DECLARE #seq_Next_Val INT=SELECT NEXT VALUE FOR Audit_Seq,
-- UPDATE ROW
if exists(select * from inserted) and exists(select * from deleted)
BEGIN
declare #COUNT_update int;
set #COUNT_update =convert(int, (select current_value FROM sys.sequences WHERE name = 'Audit_Seq')) ;
if UPDATE(SITE_DESCRIPTION)
BEGIN
SELECT #site_id=SITE_ID,
#SOURCE_SITE_NUMBER=SOURCE_SITE_NUMBER,
#SITE_DESCRIPTION=SITE_DESCRIPTION,
#LATTITUDE=LATTITUDE,
#LONGITUDE=LONGITUDE,
#INSERT_TIME=INSERT_TIME,
#INSERT_USER=INSERT_USER
FROM deleted;
INSERT INTO AUDIT_SITE(site_id,GROUP_ID,ACTIVITY,SOURCE_SITE_NUMBER,COLUMN_NAME,OLD_VALUE,NEW_VALUE,INSERT_TIME,INSERT_USER)
VALUES( #SITE_ID,#COUNT_update,'UPDATE',#SOURCE_SITE_NUMBER,'SITE_DESCRIPTION',(SELECT SITE_DESCRIPTION FROM deleted)
,(SELECT SITE_DESCRIPTION FROM inserted),#INSERT_TIME,#INSERT_USER)
END
if UPDATE(LATTITUDE)
BEGIN
SELECT #site_id=SITE_ID,
#SOURCE_SITE_NUMBER=SOURCE_SITE_NUMBER,
#SITE_DESCRIPTION=SITE_DESCRIPTION,
#LATTITUDE=LATTITUDE,
#LONGITUDE=LONGITUDE,
#INSERT_TIME=INSERT_TIME,
#INSERT_USER=INSERT_USER
FROM deleted;
INSERT INTO AUDIT_SITE(site_id,GROUP_ID,ACTIVITY,SOURCE_SITE_NUMBER,COLUMN_NAME,OLD_VALUE,NEW_VALUE,INSERT_TIME,INSERT_USER)
VALUES( #SITE_ID,#COUNT_update,'UPDATE',#SOURCE_SITE_NUMBER,'LATTITUDE',(SELECT LATTITUDE FROM deleted)
,(SELECT LATTITUDE FROM inserted),#INSERT_TIME,#INSERT_USER)
END
if UPDATE(LONGITUDE)
BEGIN
SELECT #site_id=SITE_ID,#SOURCE_SITE_NUMBER=SOURCE_SITE_NUMBER,#SITE_DESCRIPTION=SITE_DESCRIPTION
,#LATTITUDE=LATTITUDE,#LONGITUDE=LONGITUDE,#INSERT_TIME=INSERT_TIME,#INSERT_USER=INSERT_USER FROM deleted;
INSERT INTO AUDIT_SITE(site_id,GROUP_ID,ACTIVITY,SOURCE_SITE_NUMBER,COLUMN_NAME,OLD_VALUE,NEW_VALUE,INSERT_TIME,INSERT_USER)
VALUES( #SITE_ID,#COUNT_update,'UPDATE',#SOURCE_SITE_NUMBER,'LONGITUDE',(SELECT LONGITUDE FROM deleted)
,(SELECT LONGITUDE FROM inserted),#INSERT_TIME,#INSERT_USER)
END
END
END

How to get and use the value returned by a stored procedure to a INSERT INTO... SELECT... statement

I am just new in SQL language and still studying it. I'm having hard time looking for answer on how can I use the stored procedure and insert value into a table.
I have this stored procedure:
CREATE PROCEDURE TestID
AS
SET NOCOUNT ON;
BEGIN
DECLARE #NewID VARCHAR(30),
#GenID INT,
#BrgyCode VARCHAR(5) = '23548'
SET #GenID = (SELECT TOP (1) NextID
FROM dbo.RandomIDs
WHERE IsUsed = 0
ORDER BY RowNumber)
SET #NewID = #BrgyCode + '-' + CAST(#GenID AS VARCHAR (30))
UPDATE dbo.RandomIDs
SET dbo.RandomIDs.IsUsed = 1
WHERE dbo.RandomIDs.NextID = #GenID
SELECT #NewID
END;
and what I'm trying to do is this:
INSERT INTO dbo.Residents([ResidentID], NewResidentID, [ResLogdate],
...
SELECT
[ResidentID],
EXEC TestID ,
[ResLogdate],
....
FROM
source.dbo.Resident;
There is a table dbo.RandomIDs containing random 6 digit non repeating numbers where I'm pulling out the value via the stored procedure and updating the IsUsed column of the table to 1. I'm transferring data from one database to another database and doing some processing on the data while transferring. Part of the processing is generating a new ID with the required format.
But I can't get it to work Sad I've been searching the net for hours now but I'm not getting the information that I need and that the reason for my writing. I hope someone could help me with this.
Thanks,
Darren
your question is little bit confusing, because you have not explained what you want to do. As i got your question, you want to fetch random id from randomids table and after performed some processing on nextid you want to insert it into resident table [newresidentid] and end of the procedure you fetch data from resident table. if i get anything wrong feel free to ask me.
your procedure solution is following.
CREATE PROCEDURE [TestId]
AS
SET NOCOUNT ON;
BEGIN
DECLARE #NEWID NVARCHAR(30)
DECLARE #GENID BIGINT
DECLARE #BRGYCODE VARCHAR(5) = '23548'
DECLARE #COUNT INTEGER
DECLARE #ERR NVARCHAR(20) = 'NO IDS IN RANDOM ID'
SET #COUNT = (SELECT COUNT(NEXTID) FROM RandomIds WHERE [IsUsed] = 0)
SET #GENID = (SELECT TOP(1) [NEXTID] FROM RandomIds WHERE [IsUsed] = 0 ORDER BY [ID] ASC)
--SELECT #GENID AS ID
IF #COUNT = 0
BEGIN
SELECT #ERR AS ERROR
END
ELSE
BEGIN
SET #NEWID = #BRGYCODE + '-' + CAST(#GENID AS varchar(30))
UPDATE RandomIds SET [IsUsed] = 1 WHERE [NextId] = #GENID
INSERT INTO Residents ([NewResidentId] , [ResLogDate] ) VALUES (#NEWID , GETDATE())
SELECT * FROM Residents
END
END
this procedure will fetch data from your randomids table and perform some processing on nextid than after it directs insert it into resident table and if you want to insert some data through user you can use parameter after declaring procedure name
E.G
CREATE PROCEDURE [TESTID]
#PARAM1 DATATYPE,
#PARAM2 DATATYPE
AS
BEGIN
END
I'm not convinced that your requirement is a good one but here is a way to do it.
Bear in mind that concurrent sessions will not be able to read your update until it is committed so you have to kind of "lock" the update so you will get a block until you're going to commit or rollback. This is rubbish for concurrency, but that's a side effect of this requirement.
declare #cap table ( capturedValue int);
declare #GENID int;
update top (1) RandomIds set IsUsed=1
output inserted.NextID into #cap
where IsUsed=0;
set #GENID =(select max( capturedValue) from #cap )
A better way would be to use an IDENTITY or SEQUENCE to solve your problem. This would leave gaps but help concurrency.

Table-Valued Parameters not visible

For a reason unknown to me, I cannot access the contents of my TVP. The debugger says #_AccountList and #AccountTVP are tables, but I cannot view the contents, print them, nor will intellisense offer me auto completion for a column. I feel like probably declared something wrong or have an ambiguous definition. Any suggestions?
CREATE TYPE AccountList
AS TABLE
(
AccountNumber varchar(50),
AccountType varchar(50)
)
GO
CREATE PROCEDURE [dbo].[updateNumbers_ArchiveDB]
#_AccountList AccountList READONLY,
#padding int,
#proc_dateStart datetime,
#proc_dateEnd datetime
AS
DECLARE #AccountTVP AS AccountList;
BEGIN
SET NOCOUNT ON;
SELECT * FROM #AccountTVP;
UPDATE dbo.Archive2007001
SET LogicalAccount = #padding + AccountNumber
FROM dbo.Archive2007001 INNER JOIN #AccountTVP AS tvp
ON dbo.Archive2007001.LogicalAccount = tvp.AccountNumber
WHERE ProcessDate BETWEEN #proc_dateStart AND #proc_dateEnd
UPDATE dbo.DailyArchive
SET LogicalAccount = #padding + AccountNumber
FROM dbo.DailyArchive INNER JOIN #AccountTVP AS tvp
ON dbo.DailyArchive.LogicalAccount = tvp.AccountNumber
WHERE ProcessDate BETWEEN #proc_dateStart AND #proc_dateEnd
-- does not work PRINT N'tvp.AccountNumber is ' + #AccountTVP.AccountNumber
END
Here is how I am executing the procedure.
declare #p1 dbo.AccountList
insert into #p1 values(N'Account Number',N'Account Type')
insert into #p1 values(N'7463689',N'Basic')
insert into #p1 values(N'1317893',N'Premium')
insert into #p1 values(N'2806127',N'Basic')
exec updateNumbers_ArchiveDB
#_AccountList=#p1,
#padding=N'111',
#proc_dateStart='2008-01-04 11:24:46',
#proc_dateEnd='2008-01-04 11:24:46'
Answer: The data I was looking for was loaded in #_AccountList, not #AccountTVP.
#AccountTVP.AccountNumber represents a row of data, not a single value, so trying to print it will not work.
You should be able to see the incoming values by using a SELECT statement:
SELECT * FROM #_AccountList;
It seems like your SELECT statement from the top of the stored procedure should let you see the values, however, you have not actually set any values in #AccountTVP so it would be empty.
I am not sure but I suspect the debugger may not work because AccountList is a custom type.

Field level audit on SQL Server using Trigger

I am trying to write a trigger which would audit a table's every field - a row's old value and new value in a table. If any of the field has been modified, I need to save the fields old value and the new value along with field name in an audit table, as a new entry.
create trigger Trg_Institution_FieldAudit on Table1 AFTER UPDATE AS
DECLARE #OldName VARCHAR(30)
DECLARE #CurrentName VARCHAR(30)
DECLARE #OldId VARCHAR(30)
DECLARE #CurrentId VARCHAR(30)
DECLARE #modifiedBy VARCHAR(30)
If update(Name)
BEGIN
select #OldName = Name from deleted
select #CurrentName = Name from Inserted
select #OldId = ID from deleted
select #currentId = ID from Inserted
select #modifiedBy = modifiedBy from deleted
--INSERT statement for Name field alone
END;
This works fine for a small number of fields, but I have a lot of fields (more than 60), and I am not achieving the performance that is required, because of a lot of if conditions. Is there a better way of doing this? On top of this, there are concurrent updates that are happening to around 3 million records in this table, which makes a lot of things go wrong :(
EDIT: Only ONE row will get updated by an UPDATE statement
Oh my. Please avoid using a cursor whenever possible! You can easily use an insert statement with a select referencing the inserted and deleted tables. Below is a sample from one of my update triggers.
DECLARE #AuditTime DATETIME
SET #AuditTime = GetDate()
IF UPDATE([AccountManager])
INSERT INTO Audit.AuditHistory (AuditId, AuditDate, AuditTableName, EntityKey, AuditFieldName, OldValue, NewValue, FieldDisplayText, OldDisplayText, NewDisplayText, ModifiedBy)
SELECT NewId(),
#AuditTime,
'[tblOpportunity]',
cast(d.[GOTSID] AS varchar),
'[AccountManager]',
cast(d.[AccountManager] AS varchar(250)),
cast(i.[AccountManager] AS varchar(250)),
'Account Manager',
isnull(cast(d.[AccountManager] AS varchar(250)), ''),
isnull(cast(i.[AccountManager] AS varchar(250)), ''),
isnull(i.[ModifiedBy], '')
FROM deleted d
INNER JOIN inserted i ON d.GOTSID = i.GOTSID
WHERE d.[AccountManager] <> i.[AccountManager]
OR (d.[AccountManager] IS NOT NULL
AND i.AccountManager IS NULL)
OR (d.[AccountManager] IS NULL
AND i.AccountManager IS NOT NULL)
#marc_s is right, you have to re-construct your trigger and tables. here take example.
you need to put where condition in select #OldName = Name from deleted.
e.g.-
**
CREATE TRIGGER Trg_Institution_FieldAudit ON Table1 FOR UPDATE
AS
DECLARE #OldName VARCHAR(30)
DECLARE #CurrentName VARCHAR(30)
IF UPDATE (Name)
BEGIN
SET #OldName = Table1.Name FROM deleted
WHERE Table1.Name = deleted.Name;
SET #CurrentName = Table1.Name FROM inserted
WHERE Table1.Name = inserted.Name ;
--INSERT statement for old and new values.
END
GO**
After looking for an alternative for FOR EACH in SQL Server, I found that a CURSOR can be used. It serves the purpose, but need somebody to validate this.
CREATE TRIGGER Trg_Institution_FieldAudit_1 ON dbo.Institution FOR UPDATE as
-- DECLARE Variables
DECLARE institution_cursor CURSOR DYNAMIC FOR SELECT * FROM DELETED
OPEN institution_cursor FETCH NEXT FROM institution_cursor INTO -- #variables here
WHILE (##FETCH_STATUS = 0)
BEGIN
IF UPDATE(COL1)
BEGIN
INSERT INTO AuditTable VALUES (COL1, #prev, #next);
END;
FETCH NEXT FROM institution_cursor INTO -- #Variables here
END
CLOSE institution_cursor
DEALLOCATE institution_cursor

SQL Insert Procedure

Why isn't my insert procedure returning the ID of the newly inserted row? AND, when calling this procedure, why do I have to supply a value for #EventId? That column is a PK with IDENTITY.
IF OBJECT_ID ( 'vjsql.EventsINSERT', 'P') IS NOT NULL
DROP PROCEDURE EventsINSERT
GO
CREATE PROCEDURE EventsINSERT
#EventId int OUTPUT,
#EventDate datetime,
#Title varchar(100),
#IsActive bit
AS
BEGIN
INSERT INTO EventCalendar ( EventDate, Title, IsActive)
VALUES ( #EventDate, #Title, #IsActive)
SELECT #EventId = SCOPE_IDENTITY()
END
How are you making a call to the stored procedure?
This SP is returning the value of EventID by means of using OUTPUT parameters.
i.e. In programming terms, this is a procedure (not a function) that accepts an OUTPUT parameter which will be set with the value during the execution of the stored procedure.
For this, you will have to pass the variable for #EventID. The value of which will be set within the procedure and you will be able to read the value of it, once the procedure has finished.
See the example code below.
DECLARE #NewEventID INT
EXEC EventsINSERT
#EventId = #NewEventID OUTPUT,
#EventDate = '08/04/09',
#Title = 'Hello World',
#IsActive = 0
SELECT #NewEventID
Try adding some statement terminators:
BEGIN
INSERT INTO EventCalendar ( EventDate, Title, IsActive)
VALUES ( #EventDate, #Title, #IsActive);
SELECT #EventId = SCOPE_IDENTITY();
END
AND, when calling this procedure, why do I have to supply a value for #EventId? That column is a PK with IDENTITY.
You don't, but you do need to supply a variable of type int (or compatible with int) for the output value to be put into.
You don't need to specify a value for the OUTPUT parameter, you need to specify which local variable the output gets put into:
By default, SQL Management Studio names the parameter and the variable the same, which can be confusing. Here's an example of your SP being called:
DECLARE #InsertedEventId int
EXEC [dbo].[EventsINSERT]
#EventId = #InsertedEventId OUTPUT,
#EventDate = N'2009-08-05',
#Title = N'Some event',
#IsActive = 1
-- Display ID as result set
SELECT #InsertedEventId
Just to clarify: your stored procedure is fine. I used it as-is.
Why isn't my insert procedure
returning the ID of the newly inserted
row?
Your code should work. Try in the console instead of
SELECT #EventId = SCOPE_IDENTITY()
doing
SELECT SCOPE_IDENTITY()
and view what happens. Is possible that you are calling it the wrong way. You should store the value of the OUTPUT variable in a variable in the scope where you call this SP.
when calling this procedure, why do I
have to supply a value for #EventId?
Because you have to supply a value for every parameter you have. It doesn't matter if is a real value, it will be discarded, but you must call the stored procedure with a variable in this parameter to catch the returned value.
I'm pretty rusty with tsql, but don't you need to explicitly select ##identity to get that row id? That's where i'd go digging as I think scope_identity() may not return a value in the context of a user function/procedure.

Resources