Call stored proc from after insert trigger - tsql

Perhaps a stupid question!
If I call a stored proc from an After Insert trigger (T-SQL) - then how do I get the values of the "just inserted" data?
e.g.
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
EXEC createAuditSproc 'I NEED VALUES HERE!'
I don't have any identity columns to worry about - I just want to use some of the "just inserted" values to pass into my sproc.
Edit: For clarification - I need this to call a sproc and not do a direct insert to the table, since the sproc does more than one thing. I'm working with some legacy tables I can't currently amend to do things 'properly' (time/resource/legacy code), so I have to work with what I have :(

You get to the newly 'changed' data by using the INSERTED and DELETED pseudo-tables:
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
INSERT INTO myTableAudit(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
END
Given the example tables
create table myTable
(
ID INT identity(1,1),
Name varchar(10)
)
GO
create table myTableAudit
(
ID INT,
Name varchar(10),
TimeChanged datetime default CURRENT_TIMESTAMP
)
GO
Edit : Apologies, I didn't address the bit about calling a Stored Proc. As per marc_s's comment, note that inserted / deleted can contain multiple rows, which complicates matters with a SPROC. Personally, I would leave the trigger inserting directly into the audit table without the encapsulation of a SPROC. However, if you have SQL 2008, you can use table valued parameters, like so:
CREATE TYPE MyTableType AS TABLE
(
ID INT,
Name varchar(10)
);
GO
CREATE PROC dbo.MyAuditProc #MyTableTypeTVP MyTableType READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO myTableAudit(ID, Name)
SELECT mtt.ID, mtt.Name
FROM #MyTableTypeTVP mtt;
END
GO
And then your trigger would be altered as like so:
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MyTableTypeTVP AS MyTableType;
INSERT INTO #MyTableTypeTVP(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
EXEC dbo.MyAuditProc #MyTableTypeTVP;
END
you can then test that this works for both a single and multiple inserts
insert into dbo.MyTable values ('single');
insert into dbo.MyTable
select 'double'
union
select 'insert';
However, if you are using SQL 2005 or lower, you would probably need to use a cursor to loop through inserted passing rows to your SPROC, something too horrible to contemplate.
As a side note, if you have SQL 2008, you might look at Change Data Capture
Edit #2 : Since you need to call the proc, and if you are certain that you only insert one row ...
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SomeInt INT;
DECLARE #SomeName VARCHAR(10);
SELECT TOP 1 #SomeInt = i.ID, #SomeName = i.Name
FROM INSERTED i;
EXEC dbo.MyAuditProc #SomeInt, #SomeName;
END;

Related

Trigger that will build one row from several rows

I mean:
INSERT INTO test VALUES(1, 'message'), (2, 'message'), (3, 'message);
triggering will cause the result in the table to look like this:
1, E'message\nmessage\nmessage'
How to forbid inserting rows and then continue operations on the transferred data in the insert?
I am using postresgql.
In Postgres 10+ you can use a transition table in an AFTER trigger, see Example 43.7. Auditing with Transition Tables. Assuming that id is a primary key (or unique):
create table my_table(id int primary key, message text);
you can update one and delete the remaining inserted rows:
create or replace function after_insert_on_my_table()
returns trigger language plpgsql as $$
declare r record;
begin
select
array_agg(id) as ids,
array_to_string(array_agg(message), e'\n') as message
from new_table
into r;
update my_table
set message = r.message
where id = r.ids[1];
delete from my_table
where id = any(r.ids[2:]);
return null;
end $$;
In a trigger definition declare a transition table (as new_table):
create trigger after_insert_on_my_table
after insert on my_table
referencing new table as new_table
for each statement
execute procedure after_insert_on_my_table();
In earlier versions of Postgres you can simulate a transition table introduced in Postgres 10.
Test it in db<>fiddle.

How to retrieve the input parameter values of a stored procedure

I want to create a auditing procedure which will be called in the catch block of all the procedure in my Database.
I wanted to store the list of all the input parameters and its values in this auditing DB.
Please suggest me, how to achieve this in SQL Server
I am not aware of programatically retrieving the list of parameters and their values for a stored proc(Possibly would involve n number of system tables and things like that). Without going into that level of complexity AND if altering the current procedures is a possibility, you could do something on the lines of below.
ALTER the existing stored procs to add a small functionality wherein you populate a table variable with
the parameters in a set string format('#paramname = paramvalue') and their values in the current proc
and then fire the Auditing proc if the control reaches the CATCH block.
--Add this code bit on top of the proc from where you want the Audit Proc to be fired
--Declare and insert into a table variable
Declare #ParamValues TABLE (params varchar(400))
insert into #ParamValues
select '#id = '+ #id UNION
select '#name = '+ #name UNION
select '#date = '+ #date
GO
...
....
END TRY
begin catch --Auditing proc code below
exec AuditDB.dbo.AuditProc #ParamValues,
OBJECT_NAME(##PROCID) --this returns the name of current proc
end catch
-------
-------Create the requisite SQL objects
-------
CREATE TABLE AuditDB.dbo.AuditTable
(
AuditMessage varchar(400),
ProcName varchar(200),
DateTimeStamp DateTime
);
GO
CREATE TYPE AuditDB.dbo.ParamValuesType AS TABLE
(
params varchar(400)
);
GO
CREATE PROCEDURE AuditDB.dbo.AuditProc
#ParamValues dbo.ParamValuesType READONLY
,#ProcName varchar(200)
AS
BEGIN --Add whaterver lines of code required, this is just a basic version.
INSERT INTO AuditDB.dbo.AuditTable
SELECT params, #ProcName, cast(getdate() as datetime) FROM #ParamValues
END;

Entity framework stored procedure issue

I have following stored procedure defined:
USE [BcmMetrice]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[ActivityAdd_proc] #Name nvarchar(max),#Description nvarchar(max) =null ,#Users nvarchar(max),#Object_id nvarchar (15) =null, #Source nvarchar(10) =null, #TemplateId bigint =null, #UserID bigint =null
AS
DECLARE activityUsers_cursor CURSOR FOR
select s from dbo.SplitString(#Users, ';')
DECLARE
#new_ActivityId bigint,
#new_CommentId bigint,
#activityUser_l bigint
BEGIN TRY
INSERT INTO [BcmMetrice].[dbo].[Activity]
([Name]
,[Description]
,[Type]
,[Created])
VALUES
(#Name
,#Description
,ISNULL(#TemplateId,0)
,GETDATE())
SET #new_ActivityId = (SELECT SCOPE_IDENTITY())
INSERT INTO [BcmMetrice].[dbo].[Comment] ([UserID],[CommentText],[Timestamp])
VALUES (ISNULL(#UserID,151),'Activity has been created',GETDATE())
SET #new_CommentId = (SELECT SCOPE_IDENTITY())
INSERT INTO [BcmMetrice].[dbo].[ActivityComment] ([ActivityID],[CommentID])
VALUES (#new_ActivityId, #new_CommentId)
INSERT INTO [BcmMetrice].[dbo].[Log]([Timestamp],[Type],[Data],[StackTrace]) VALUES (GETDATE(),'SQL.ActivityAdd_proc','users='+ISNULL(CAST(#Users as varchar(max)),'empty'),null)
OPEN activityUsers_cursor
FETCH NEXT FROM activityUsers_cursor INTO #activityUser_l
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO [BcmMetrice].[dbo].[Log]([Timestamp],[Type],[Data],[StackTrace]) VALUES (GETDATE(),'SQL.ActivityAdd_proc','Inserting users='+ISNULL(CAST(#activityUser_l as varchar(max)),'empty'),null)
INSERT INTO [BcmMetrice].[dbo].[ActivityUser]
([ActivityId]
,[UserId]
,[Role])
VALUES
(#new_ActivityId
,#activityUser_l
,1)
FETCH NEXT FROM activityUsers_cursor INTO #activityUser_l
END
CLOSE activityUsers_cursor
DEALLOCATE activityUsers_cursor
END TRY
BEGIN CATCH
PRINT 'ERROR'
INSERT INTO [BcmMetrice].[dbo].[Log]([Timestamp],[Type],[Data],[StackTrace]) VALUES (GETDATE(),'SQL.ActivityAdd_proc','ERROR CATCHED!'+ERROR_MESSAGE(),null)
END CATCH
select #new_ActivityId
The thing I would like to do is to return from the procedure the id of a newly added activity. That is why at the very and I use line:
select #new_ActivityId
When testing this procedure in SQL Management Studio everything seems to be working fine. Problem starts when I try to use this procedure in my .NET project. I updated my edmx model form database, but when I execute this procedure the return value is invalid.
Procedure execution looks like this:
int ret = dc.Db.ActivityAdd_proc(name, description, users, object_id, source, templateId, userId);
Does anyone have an idea what I might be doing wrong?
I found solution to my problem. When you try to return data from stored procedure via select statement you must create Function Import in model browser of edmx. In a wizard you choose a type of returned collection (in my case it was scalar of type int64).

postgresql: nested insert

I have two tables. Lets say tblA and tblB.
I need to insert a row in tblA and use the returned id as a value to be inserted as one of the columns in tblB.
I tried finding out this in documentation but could not get it. Well, is it possible to write a statement (intended to be used in prepared) like
INSERT INTO tblB VALUES
(DEFAULT, (INSERT INTO tblA (DEFAULT, 'x') RETURNING id), 'y')
like we do for SELECT?
Or should I do this by creating a Stored Procedure?. I'm not sure if I can create a prepared statement out of a Stored Procedure.
Please advise.
Regards,
Mayank
You'll need to wait for PostgreSQL 9.1 for this:
with
ids as (
insert ...
returning id
)
insert ...
from ids;
In the meanwhile, you need to use plpgsql, a temporary table, or some extra logic in your app...
This is possible with 9.0 and the new DO for anonymous blocks:
do $$
declare
new_id integer;
begin
insert into foo1 (id) values (default) returning id into new_id;
insert into foo2 (id) values (new_id);
end$$;
This can be executed as a single statement. I haven't tried creating a PreparedStatement out of that though.
Edit
Another approach would be to simply do it in two steps, first run the insert into tableA using the returning clause, get the generated value through JDBC, then fire the second insert, something like this:
PreparedStatement stmt_1 = con.prepareStatement("INSERT INTO tblA VALUES (DEFAULT, ?) returning id");
stmt_1.setString(1, "x");
stmt_1.execute(); // important! Do not use executeUpdate()!
ResultSet rs = stmt_1.getResult();
long newId = -1;
if (rs.next()) {
newId = rs.getLong(1);
}
PreparedStatement stmt_2 = con.prepareStatement("INSERT INTO tblB VALUES (default,?,?)");
stmt_2.setLong(1, newId);
stmt_2.setString(2, "y");
stmt_2.executeUpdate();
You can do this in two inserts, using currval() to retrieve the foreign key (provided that key is serial):
create temporary table tb1a (id serial primary key, t text);
create temporary table tb1b (id serial primary key,
tb1a_id int references tb1a(id),
t text);
begin;
insert into tb1a values (DEFAULT, 'x');
insert into tb1b values (DEFAULT, currval('tb1a_id_seq'), 'y');
commit;
The result:
select * from tb1a;
id | t
----+---
3 | x
(1 row)
select * from tb1b;
id | tb1a_id | t
----+---------+---
2 | 3 | y
(1 row)
Using currval in this way is safe whether in or outside of a transaction. From the Postgresql 8.4 documentation:
currval
Return the value most recently
obtained by nextval for this sequence
in the current session. (An error is
reported if nextval has never been
called for this sequence in this
session.) Because this is returning a
session-local value, it gives a
predictable answer whether or not
other sessions have executed nextval
since the current session did.
You may want to use AFTER INSERT trigger for that. Something along the lines of:
create function dostuff() returns trigger as $$
begin
insert into table_b(field_1, field_2) values ('foo', NEW.id);
return new; --values returned by after triggers are ignored, anyway
end;
$$ language 'plpgsql';
create trigger trdostuff after insert on table_name for each row execute procedure dostuff();
after insert is needed because you need to have the id to reference it. Hope this helps.
Edit
A trigger will be called in the same "block" as the command that triggered it, even if not using transactions - in other words, it becomes somewhat part of that command.. Therefore, there is no risk of something changing the referenced id between inserts.

Array-like access to variables in T-SQL

In my stored procedure I have multiple similar variables #V1, #V2 ... #V20 (let's say 20 of them) FETCHED from a record. How would I use dynamic SQL to make 20 calls to another stored procedure using those variables as parameters?
Of course #V[i] syntax is incorrect, but it expresses the intent
fetch next from maincursor into #status, #V1, #V2, ...
while #i<21
begin
-- ??? execute sp_executesql 'SecondSP', '#myParam int', #myParam=#V[i]
-- or
-- ??? execute SecondSP #V[i]
set #i = #i+1
end
As others have said, set up a temporary table, insert the values that you need into it. Then "iterate" through it executing the necessary SQL from those values. This will allow you to have 0 to MANY values to be executed, so you don't have to set up a variable for each.
The following is a complete sample of how you may go about doing that without cursors.
SET NOCOUNT ON
DECLARE #dict TABLE (
id INT IDENTITY(1,1), -- a unique identity column for reference later
value VARCHAR(50), -- your parameter value to be passed into the procedure
executed BIT -- BIT to mark a record as being executed later
)
-- INSERT YOUR VALUES INTO #dict HERE
-- Set executed to 0 (so that the execution process will pick it up later)
-- This may be a SELECT statement into another table in your database to load the values into #dict
INSERT #dict
SELECT 'V1Value', 0 UNION ALL
SELECT 'V2Value', 0
DECLARE #currentid INT
DECLARE #currentvalue VARCHAR(50)
WHILE EXISTS(SELECT * FROM #dict WHERE executed = 0)
BEGIN
-- Get the next record to execute
SELECT
TOP 1 #currentid = id
FROM #dict
WHERE executed = 0
-- Get the parameter value
SELECT #currentvalue = value
FROM #dict
WHERE id = #currentid
-- EXECUTE THE SQL HERE
--sp_executesql 'SecondSP', '#myParam int', #myParam =
PRINT 'SecondSP ' + '#myParam int ' + '#myParam = ' + #currentvalue
-- Mark record as having been executed
UPDATE d
SET executed = 1
FROM #dict d
WHERE id = #currentid
END
Use a #TempTable
if you are at SQL Server 2005 you can create a #TempTable in the parent stored procedure, and it is available in the child stored procedure that it calls.
CREATE TABLE #TempTable
(col1 datatype
,col2 datatype
,col3 datatype
)
INSERT INTO #TempTable
(col1, col2, col3)
SELECT
col1, col2, col3
FROM ...
EXEC #ReturnCode=YourOtherProcedure
within the other procedure, you have access to #TempTable to select, delete, etc...
make that child procedure work on a set of data not on one element at a time
remember, in SQL, loops suck performance away!
Why not just use the table variable instead, and then just loop through the table getting each value.
Basically treat each row in a table as your array cell, with a table that has one column.
Just a thought. :)
This seems like an odd request - will you always have a fixed set of variables? What if the number changes from 20 to 21, and so on, are you constantly going to have to be declaring new variables?
Is it possible, instead of retrieving the values into separate variables, to return them each as individual rows and just loop through them in a cursor?
If not, and you have to use the individual variables as explained, here's one solution:
declare #V1 nvarchar(100)
set #V1 = 'hi'
declare #V2 nvarchar(100)
set #V2 = 'bye'
declare #V3 nvarchar(100)
set #V3 = 'test3'
declare #V4 nvarchar(100)
set #V4 = 'test4'
declare #V5 nvarchar(100)
set #V5 = 'end'
declare aCursor cursor for
select #V1
union select #V2 union select #V3
union select #V4 union select #V5
open aCursor
declare #V nvarchar(100)
fetch next from aCursor into #V
while ##FETCH_STATUS = 0
begin
exec TestParam #V
fetch next from aCursor into #V
end
close aCursor
deallocate aCursor
I don't really like this solution, it seems messy and unscalable. Also, as a side note - the way you phrased your question seems to be asking if there are arrays in T-SQL. By default there aren't, although a quick search on google can point you in the direction of workarounds for this if you absolutely need them.