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;
Related
I've different different tables to categorically store data and a log table where all the transactions log are recorded
e.g. 1) VoucherNO, Add, ...
2) VoucherNO, Delete, ..
After I backup the database and restore in another server for my Reporting Purpose. That time I want to ensure all the log data and transaction are available in TestDB if not then I remove log from 'AUD_USER_ACTIVITY'.
To find the transaction exist or not, I create a dynamic sql select statement and check whether record is exist or not.
Basis on #RecExist Value I do the action like if records is not available in TestDB the log will be remove, if record exist immediately break this loop and going for next procedure
But #RecExist variable is not updating in Dynamic SQL Execution. Please guide me
declare #MvDocNo varchar(50)
DECLARE #SCtr as DECIMAL(10,0)
declare #LocationCode varchar(4)
declare #UName Nvarchar(40)
declare #toe varchar(30)
declare #QryTxt as nvarchar(MAX);
Declare #RecExist as INT =0;
SET #RecExist=0
WHILE #RecExist=0
BEGIN
select top 1 #MvDocNo=DOCNO, #SCtr=SrlNo,#LocationCode =DMLTYPE,#UName=TABLENAME
FROM R_AUDDB..AUD_USER_ACTIVITY
WHERE DBNAME='TestDB' and DMLTYPE not in ('AD','D','PD') ORDER BY SRLNO DESC;
select top 1 #toe=docno from TestDB..M_TYPEOFENTRY where TBLNAME=#UName;
set #QryTxt='Select #RecExist=1 From R_TestDB..'+#UName+ ' Where '+#toe+'='''+#MvDocNo+''''
exec (#QryTxt)
IF #RecExist=0
BEGIN
DELETE R_AUDDB..AUD_USER_ACTIVITY WHERE SRLNO=#SCtr
END
END
The following code sample demonstrates how to check for a row in a table with a specific column and value using dynamic SQL. You ought to be able to change the values of the first three variables to reference a table and column in your database for testing.
Note that SQL injection is still possible: there is no validation of the table or column names.
-- Define the table to check and the target column name and value.
declare #TableName as SysName = 'Things';
declare #ColumnName as SysName = 'ThingName';
declare #TestValue as NVarChar(32) = 'Beth';
-- Create a SQL statement to check for a row in the target table with the specified column name and value.
declare #SQL as NVarChar(1024);
declare #Result as Bit;
-- Note that only object names are substituted into the statement at this point and QuoteName() is used to reduce problems.
set #SQL = N'select #iResult = case when exists ( select 42 from dbo.' + QuoteName( #TableName ) +
N' where ' + QuoteName( #ColumnName ) + N' = #iTestValue ) then 1 else 0 end;'
select #SQL as SQL;
-- Execute the SQL statement.
-- Note that parameters are used for all values, i.e. the target value and return value.
execute sp_executesql #stmt = #SQL,
#params = N'#iTestValue NVarChar(32), #iResult Bit output',
#iTestValue = #TestValue, #iResult = #Result output
-- Display the result.
select #Result as Result;
I have a SP with an Output parameter that looks like:
ALTER PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT as ...
I call that procedure from vb.net to get the value for calculations. My problem is: I have 8 SP's with the following structure:
CREATE PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT as ...
CREATE TABLE #TempTable
Begin
Select ...
End
SET #VarName = Result
But the TempTable is always the same. No I am looking for a way to get all 8 values with only one stored procedure. My idea:
CREATE PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT as ...
CREATE TABLE #TempTable
---Get first value
Begin
Select ...
End
SET #VarName1 = Result
---Get second value
Begin
Select ...
End
SET #VarName2 = Result
...
How do i have to rewrite the line: ALTER PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT ir can I even work with an array?
You can use a single stored procedure with all your queries in it. Following will return a single row result set with eight fields and you can grab them from your code using the specific filed name or index.
CREATE PROCEDURE [dbo].[SP_Name]
#VarName decimal(18,2)
AS
BEGIN
DECLARE #VarName1 Datatype, #VarName2 Datatype, ...#VarName8 Datatype
SELECT #VarName1 = yourCol
FROM --First query
SELECT #VarName2 = yourCol
FROM --Second query
...
SELECT #VarName8 = yourCol
FROM --Eighth query
--Finally Select all the variables
SELECT #VarName1 Col1, #VarName2 Col2, ...,#VarName8 Col8
END
OR if you are looking to return results of your all 8 queries, that is also possible. Simply do your select queries in a single stored procedure and grab the DATASET from your code and you can access individual table using zero based Index (ex DataTable1 = YourDataSet.Tables[0])
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).
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;
I would like to do the following. Basically have a stored procedure call another stored procedure that returns a table. How is this done?
ALTER PROC [GETSomeStuff]
AS
BEGIN
#table = exec CB_GetLedgerView #accountId, #fromDate, #toDate, #pageSize, #pageNumber, #filter, #status, #sortExpression, #sortOrder, #virtualCount OUTPUT
Select * from #table
--Do some other stuff here
END
The target of a stored procedure has to be a temp or actual table so you can
Insert into #table exec CB_GetLedgerView #accountId, #fromDate,
#toDate, #pageSize, #pageNumber,
#filter, #status, #sortExpression,
#sortOrder, #virtualCount OUTPUT
If the output result set of the stored procedure does not match the ordinal positions and count of the rows in the target table, specify a column list.
The temporary-table approach, at least as expressed above, didn't work for me. You can use a variable, just as easily.
DECLARE #return_value INT
DECLARE #tblOutputTable TABLE(Col1 BIT NOT NULL, Col2 INT NOT NULL)
INSERT INTO #tblOutputTable EXEC #return_value = [dbo].[SomeSp] #Param1 = 15, #Param2 = 2
Maybe your example isn't really representative, but the first question I'd have have is, do you really need to make this two procedures, at the cost of greater complexity? Decomposition like this is somewhat of an antipattern with SQL. (Although some will disagree, but I've seen this discussed with majority agreement here on SO.)