SQL Server delete trigger not capturing username (SUSER_SNAME) - sql

I am using web application to insert, update and delete records in a SQL Server table. The audit for this table is captured in another table when any insert, update, delete happens from GUI.
The insert, update is capturing the username correctly but the delete from GUi capture the SQL account not the web username, who deleted the records.
Please suggest a solution.
The trigger code for delete is as below:
-- DELETE
if exists (select 1 from deleted) and not exists (select 1 from inserted)
BEGIN
DECLARE #id varbinary(85)
DECLARE #who varchar(50)
SELECT #id = [Transaction SID]
FROM fn_dblog(NULL, NULL)
WHERE [Operation] = 'LOP_BEGIN_XACT'
AND [Transaction ID] = (SELECT TOP 1 [Transaction ID]
FROM fn_dblog(NULL, NULL)
WHERE Operation = 'LOP_DELETE_ROWS'
AND AllocUnitName like 'dbo.sampletable%'
ORDER BY [Transaction ID] DESC)
SET #who = SUSER_SNAME(#id)
INSERT sampletableAudit
SELECT
'D' as Operation, Id, Type, #who, getdate()
FROM
deleted
END

On an active machine this query takes forever. Who knows what happens to this query if, and when, the transaction log is backed up. On my machine (which has a busy database) this has been running for 7 minutes with no results so I stopped it.
If you are running SQL Server 2008 and on you can get the same results using SQL Server Audit (with much, much less of an impact). Otherwise why can't you just use user_name?

Related

SQL Stored Procedure Simultaneously Call Issue

I have stored procedure in the sql server 2008, my stored procedure calculate and get the last number "not primary key" from column from table B and add one ( +1 ) to this number to use it on the next statement on the same stored procedure.
My issue that i have a duplicate number some times, i think this happened when multiple users call the stored procedure on the same time. is this the issue and how can i solve it
my code is like the below:-
DECLARE #ID AS NVARCHAR(10)
SET #ID = (
SELECT TOP 1 MyNo
FROM Employee
WHERE (
(TypeID = #TypeID) AND
(Year = #Year)
)
ORDER BY ID DESC
)
SET #ID = ISNULL(#ID,0) + 1
INSERT INTO Employee (name,lname,MyNo) VALUES (#name,#lname,#MyNo)
You can lock a table for the duration of a transaction with the WITH (TABLOCKX, HOLDLOCK) syntax:
BEGIN TRANSACTION
DECLARE #ID AS NVARCHAR(10)
SET #ID = (
SELECT TOP 1 MyNo
FROM Employee WITH (TABLOCKX, HOLDLOCK)
WHERE (
(TypeID = #TypeID) AND
(Year = #Year)
)
ORDER BY ID DESC
)
SET #ID = ISNULL(#ID,0) + 1
INSERT INTO Employee (name,lname,MyNo) VALUES (#name,#lname,#MyNo)
COMMIT TRANSACTION
You can find more information about TABLOCK and TABLOCKX here:
https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table
Per discussion, the best lock to use in this case would be:
(UPDLOCK,HOLDLOCK)
If you cannot use Identity column or the Table lock, another alternative is to use sp_getapplock
The advantage with this mechanism is that this kind of lock can be used across multiple stored procedures that should not run concurrently or for operations that span multiple tables. It also allows for handling timeout and other kinds of behavior if the lock is not available.
You have to be careful when using this feature and ensure you acquire and release locks properly or you will create more problems than you solve.

SQL While Loop takes way too long

After days of searching the internet for an answer and trying to improve this myself I have finally decided to ask for help.
I receive a flat file each day from a client that contains about 1.1 million rows of data. I import this data into a staging database with SSIS (SQL Server 2012). This takes only a few seconds. The data is basically appointment information.
There are several fields in the flat file but the ones I have to use to synchronize the reporting table are called:
UpdateType - contains either INSERT, UPDATE or DELETE.
ChangeDate - Date time-stamp of when the row changed.
UniqueKey - UniqueKey + ChangeDate create a unique key for the row
The requirements from the client are that I either INSERT, UPDATE or DELETE the row from the reporting database in the order of the ChangeDate by UniqueKey. I could not figure out how to do this in a set so I created a while loop which takes over 20 hours to run which is way too long.
Here is an example of the flat file data I receive:
UpdateType UniqueKey ChangeDate MoreDate
INSERT 27244595 2013-09-24 08:51:48.367 synchronize data follows
DELETE 27244595 2013-09-25 10:15:08.433 synchronize data follows
INSERT 27244595 2013-09-25 10:15:09.990 synchronize data follows
DELETE 27244595 2013-09-25 15:02:36.287 synchronize data follows
INSERT 27244595 2013-09-25 15:02:36.610 synchronize data follows
As you can see the same record was inserted then deleted many times but this isn't always the case. In this example data, only the last record should appear in the reporting database table, 1 appointment is scheduled.
Here is another example from the same flat file:
UpdateType UniqueKey ChangeDate MoreDate
INSERT 28243572 2013-09-25 10:15:08.610 synchronize data follows
INSERT 28243572 2013-09-25 10:15:09.880 synchronize data follows
DELETE 28243572 2013-09-25 14:01:36.210 synchronize data follows
INSERT 28243572 2013-09-25 14:02:37.287 synchronize data follows
In this example the first and last record should appear in the reporting database table. There are 2 appointments scheduled. There are other times when an update in in the mix.
I don't create the reports and have no idea what they look like.
Here is the code I wrote to synchronize the reporting database from the staging database. If you have any suggestions on how to improve this process I welcome them and appreciate the help.
DECLARE --DECLARE SOME VARIABLES TO USE IN THE LOOP
#UPDATETYPE VARCHAR(6) --THIS WILL BE INSERT, DELETE OR UPDATE
,#KEY INTEGER --THIS IS THE UNIQUEKEY
,#CHANGEDATE DATETIME --THIS IS THE CHANGE DATE FROM THE FLATFILE
--START A WHILE LOOP TO GO ROW BY ROW
WHILE (SELECT COUNT(*) FROM STAGEDB.DBO.APPIONTMENTCHANGE) > 0
BEGIN
SELECT #UPDATETYPE = (SELECT TOP 1 [UPDATETYPE] FROM STAGEDB.DBO.APPIONTMENTCHANGE
ORDER BY [UNIQUEKEY], [CHANGEDATE]) --GET THE UPDATE TYPE FOR THE IF STATEMENTS
SELECT #KEY = (SELECT TOP 1 [UNIQUEKEY] FROM STAGEDB.DBO.APPIONTMENTCHANGE
ORDER BY [UNIQUEKEY], [CHANGEDATE]) --GET THE KEY
SELECT #CHANGEDATE = (SELECT TOP 1 [CHANGEDATE] FROM STAGEDB.DBO.APPIONTMENTCHANGE
ORDER BY [UNIQUEKEY], [CHANGEDATE]) --GET THE CHANGEDATE
--IF THIS ROW IS AN INSERT THEN COMPLETE THIS ON THE REPORT DATABASE
IF #UPDATETYPE = 'INSERT'
BEGIN
INSERT INTO [REPORTDB].[DBO].[APPOINTMENT]
([REPORTDB].[DBO].[APPOINTMENT].[UNIQUEKEY]
,[REPORTDB].[DBO].[APPOINTMENT].[APPOINTMENT]
,[REPORTDB].[DBO].[APPOINTMENT].[CLIENTLEADBK]
,[REPORTDB].[DBO].[APPOINTMENT].[FIRSTNAME]
,[REPORTDB].[DBO].[APPOINTMENT].[LASTNAME]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER2]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER3]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER4]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSSTREET]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSCITY]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSSTATE]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSZIP]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSCOUNTRY])
SELECT TOP 1 [UNIQUEKEY]
,[APPOINTMENT]
,[CLIENTLEADBK]
,[FIRSTNAME]
,[LASTNAME]
,[PHONENUMBER]
,[PHONENUMBER2]
,[PHONENUMBER3]
,[PHONENUMBER4]
,[ADDRESSSTREET]
,[ADDRESSCITY]
,[ADDRESSSTATE]
,[ADDRESSZIP]
,[ADDRESSCOUNTRY]
FROM [STAGEDB].[DBO].[APPIONTMENTCHANGE]
ORDER BY [UNIQUEKEY], [CHANGEDATE];
--ONCE THE INSERT IS COMPLETED THEN DELETE THE ALREADY WORKED RECORD FROM THE STAGING DATABASE
DELETE FROM [STAGEDB].[DBO].[APPIONTMENTCHANGE]
WHERE [UNIQUEKEY] = #KEY AND [CHANGEDATE] = #CHANGEDATE;
END
--IF THE ROW IS A DELETE REQUEST THEN COMPLETE THIS ON THE REPORT DATABASE
IF #UPDATETYPE = 'DELETE'
BEGIN
DELETE FROM [REPORTDB].[DBO].[APPOINTMENT]
WHERE [UNIQUEKEY] = #KEY AND [CHANGEDATE] = #CHANGEDATE;
--ONCE THE DELETE IS COMPLETED THEN DELETE THE ALREADY WORKED RECORD FROM THE STAGING DATABASE
DELETE FROM [STAGEDB].[DBO].[APPIONTMENTCHANGE]
WHERE [UNIQUEKEY] = #KEY AND [CHANGEDATE] = #CHANGEDATE;
END
--IF THE ROW IS A UPDATE REQUEST DO THAT
IF #UPDATETYPE = 'UPDATE'
BEGIN
UPDATE [REPORTDB].[DBO].[APPOINTMENT]
SET [REPORTDB].[DBO].[APPOINTMENT].[APPOINTMENT] = B.[APPOINTMENT]
,[REPORTDB].[DBO].[APPOINTMENT].[CLIENTLEADBK] = B.[CLIENTLEADBK]
,[REPORTDB].[DBO].[APPOINTMENT].[FIRSTNAME] = B.[FIRSTNAME]
,[REPORTDB].[DBO].[APPOINTMENT].[LASTNAME] = B.[LASTNAME]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER] = B.[PHONENUMBER]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER2] = B.[PHONENUMBER2]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER3] = B.[PHONENUMBER3]
,[REPORTDB].[DBO].[APPOINTMENT].[PHONENUMBER4] = B.[PHONENUMBER4]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSSTREET] = B.[ADDRESSSTREET]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSCITY] = B.[ADDRESSCITY]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSSTATE] = B.[ADDRESSSTATE]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSZIP] = B.[ADDRESSZIP]
,[REPORTDB].[DBO].[APPOINTMENT].[ADDRESSCOUNTRY] = B.[ADDRESSCOUNTRY]
FROM [REPORTDB].[DBO].[APPOINTMENT]
INNER JOIN [STAGEDB].[DBO].[APPIONTMENTCHANGE] B
ON [REPORTDB].[DBO].[APPOINTMENT].[UNIQUEKEY] = B.[UNIQUEKEY]
WHERE [REPORTDB].[DBO].[APPOINTMENT].[UNIQUEKEY] = #KEY;
--ONCE THE UPDATE IS COMPLETED THEN DELETE THE ALREADY WORKED RECORD FROM THE STAGING DATABASE
DELETE FROM [STAGEDB].[DBO].[APPIONTMENTCHANGE]
WHERE [UNIQUEKEY] = #KEY AND [CHANGEDATE] = #CHANGEDATE;
END
END
I would much rather have all the files that need inserted, all that need deleted and all that should be updated not every record change that occurred but this is what I have to work with right now.
All serious ideas for improvements are appreciated. Please provide as much explanation as you can.
Use normal sql commands. First the insert
insert into realtable
(field1, field2, etc)
select field1, field2, etc
from stagingtable
where idfield in
(select idfield
from stagingtable
except
select idfield
from realtable)
updates
update r
set field1 = s.field1
, etc
from realtable r join stagingtable s on something
where whatever
deletions
delete from reatable
where idfield in
(select idfield
from staging table
where you want the record deleted from the real table)

SQL Server : Remote Table insert fails

Here is my setting: within my schema I have a stored procedure (shown below) which will insert a dummy row (for testing) into a remote table. The table MY_REMOTE_TABLE is a synonym pointing to the correct remote table.
I can successfully query it like
SELECT * FROM MY_REMOTE_TABLE;
on my local system. This does work.
So for testing I created the procedure with a dummy test value in it, which should be inserted into the remote table if the row is new to the remote table. Hint: the remote table is empty at the time of insertion, so it really should perform the insert.
But it always fails giving me a return value of -6. I have no clue what -6 stands for or even what the error could be. All I have is -6 and I know that nothing will be inserted into the remote table. Interestingly when I copy the insert statement to the remote server, replace the synonym with the real table name on the remote machine and execute, it works all fine.
So I'm really lost here seeking for your help!
CREATE PROCEDURE [dbo].[my_Procedure]
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN DISTRIBUTED TRANSACTION
-- LEFT JOIN AND WHERE NULL WILL ONLY FIND NEW RECORDS -> THEREFORE INSERT INTO TARGET REMOTE TABLE
INSERT INTO MY_REMOTE_TABLE
(--id is not needed because it's an IDENTITY column
user_id,
customer_id,
my_value, year,
Import_Date, Import_By, Change_Date, Change_By)
SELECT
Source.user_id,
Source.customer_id,
Source.my_value,
Source.year,
Source.Import_Date,
Source.Import_By,
Source.Change_Date,
Source.Change_By
FROM
(SELECT
null as id,
126616 as user_id,
17 as customer_id,
0 as my_value,
2012 as year,
GETDATE() AS Import_Date,
'test' AS Import_By,
GETDATE() AS Change_Date,
'test' AS Change_By) AS Source
LEFT JOIN
MY_REMOTE_TABLE AS Target ON Target.id = Source.id
AND Target.user_id = Source.user_id
AND Target.customer_id = Source.customer_id
AND Target.year = Source.year
WHERE
Target.id IS NULL; -- BECAUSE OF LEFT JOIN NEW RECORDS WILL BE NULL, SO WE ONLY SELECT THEM FOR THE INSERT !!!
IF (##TRANCOUNT > 0 AND XACT_STATE() = 1)
BEGIN
COMMIT TRANSACTION
END
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0AND XACT_STATE() = -1)
ROLLBACK TRANSACTION
END CATCH;
END
another question related to this one. if my insert would violate an FK constraint on my remote table, how could I manage to promote the error message from the remote DB server to my local procedure to capture it?
Look here: http://msdn.microsoft.com/de-de/library/ms188792.aspx
Short version:
XACT_ABORT must be set ON for data modification statements in an
implicit or explicit transaction against most OLE DB providers,
including SQL Server. The only case where this option is not required
is if the provider supports nested transactions.
So insert a SET XACT_ABORT ON at the start of the stored procedure.

Deleted Rows Daily

I have a database table from which same data under a certain condition are lost at a specific time daily as if such statement is performed:
delete * from table where category=1
I'd like to list all delete actions on this table through a SQL script to know how records are deleted exactly and by which statement, user and time of deletion.
Does anyone have such script? Or did anyone have similar case and can advise?
The SQL version is Server 2008 Enterprise Edition.
If this is just a short-term debugging issue, the easiest way to address this is probably to run SQL Server Profiler, with filters set to capture the data you're interested in. No code changes that way.
For best performance, try to run SQL Profiler on a machine other than the DB server, if you can.
Use AFTER DELETE trigger on the table to log deletions in another table with user and time it was performed.
Using some advanced tricks you can extract the query text which deleted the rows, but I'm not sure that it is possible inside a trigger.
The trigger might look like this
CREATE TABLE YourLogTable
(
ID int identity primary key,
Date datetime NOT NULL DEFAULT GETDATE(),
[User] nvarchar(128) NOT NULL DEFAULT suser_sname(),
[SqlText] NVARCHAR(MAX),
[any other interesting columns from deleted rows]
)
GO
CREATE TRIGGER [TR.AD#YourTable]
ON YourTable
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sqlText NVARCHAR(MAX)
SELECT #sqlText = txt.Text
FROM sys.dm_exec_connections c
CROSS APPLY sys.dm_exec_sql_text(c.most_recent_sql_handle) txt
WHERE session_id = ##SPID
INSERT YourLogTable([SqlText], [any other interesting columns from deleted rows])
SELECT #SqlText, [any other interesting columns from deleted rows]
FROM DELETED
END

SQL Delete Statement Didnt Delete

Just want to get some views/possible leads on an issue I have.
I have a stored procedure that updates/deletes a record from a table in my database, the table it deletes from is a live table, that temporary holds the data, and also updates records on a archive table. (for reporting etc..) it works normally and havent had an issues.
However recently I had worked on a windows service to monitor our system (running 24/7), which uses a HTTP call to initiate a program, and once this program has finished it then runs the mention stored procedure to delete out redundant data. Basically the service just runs the program quickly to make sure its functioning correctly.
I have noticed recently that the data isnt always being deleted. Looking through logs I see no errors being reported. And Even see the record in the database has been updated correctly. But just doesnt get deleted.
This unfortunately has a knock on effect with the monitoring service, as this continously runs, and sends out alerts because the data cant be duplicated in the live table, hence why it needs to delete out the data.
Currently I have in place a procedure to clear out any old data. (3 hours).
Result has the value - Rejected.
Below is the stored procedure:
DECLARE #PostponeUntil DATETIME;
DECLARE #Attempts INT;
DECLARE #InitialTarget VARCHAR(8);
DECLARE #MaxAttempts INT;
DECLARE #APIDate DATETIME;
--UPDATE tCallbacks SET Result = #Result WHERE CallbackID = #CallbackID AND UPPER(Result) = 'PENDING';
UPDATE tCallbacks SET Result = #Result WHERE ID = (SELECT TOP 1 ID FROM tCallbacks WHERE CallbackID = #CallbackID ORDER BY ID DESC)
SELECT #InitialTarget = C.InitialTarget, #Attempts = LCB.Attempts, #MaxAttempts = C.CallAttempts
FROM tConfigurations C WITH (NOLOCK)
LEFT JOIN tLiveCallbacks LCB ON LCB.ID = #CallbackID
WHERE C.ID = LCB.ConfigurationID;
IF ((UPPER(#Result) <> 'SUCCESSFUL') AND (UPPER(#Result) <> 'MAXATTEMPTS') AND (UPPER(#Result) <> 'DESTBAR') AND (UPPER(#Result) <> 'REJECTED')) BEGIN
--INSERT A NEW RECORD FOR RTNR/BUSY/UNSUCCESSFUL/REJECT
--Create Callback Archive Record
SELECT #APIDate = CallbackRequestDate FROM tCallbacks WHERE Attempts = 0 AND CallbackID = #CallbackID;
BEGIN TRANSACTION
INSERT INTO tCallbacks (CallbackID, ConfigurationID, InitialTarget, Agent, AgentPresentedCLI, Callee, CalleePresentedCLI, CallbackRequestDate, Attempts, Result, CBRType, ExternalID, ASR, SessionID)
SELECT ID, ConfigurationID, #InitialTarget, Agent, AgentPresentedCLI, Callee, CalleePresentedCLI, #APIDate, #Attempts + 1, 'PENDING', CBRType, ExternalID, ASR, SessionID
FROM tLiveCallbacks
WHERE ID = #CallbackID;
UPDATE LCB
SET PostponeUntil = DATEADD(second, C.CallRetryPeriod, GETDATE()),
Pending = 0,
Attempts = #Attempts + 1
FROM tLiveCallbacks LCB
LEFT JOIN tConfigurations C ON C.ID = LCB.ConfigurationID
WHERE LCB.ID = #CallbackID;
COMMIT TRANSACTION
END
ELSE BEGIN
-- Update the Callbacks archive, when Successful or Max Attempts or DestBar.
IF EXISTS (SELECT ID FROM tLiveCallbacks WHERE ID = #CallbackID) BEGIN
BEGIN TRANSACTION
UPDATE tCallbacks
SET Attempts = #Attempts
WHERE ID IN (SELECT TOP (1) ID
FROM tCallbacks
WHERE CallbackID = #CallbackID
ORDER BY Attempts DESC);
-- The live callback should no longer be active now. As its either been answered or reach the max attempts.
DELETE FROM tLiveCallbacks WHERE ID = #CallbackID;
COMMIT
END
END
You need to fix your transaction processing. What is happening is that one statement is failing but since you don't have a try-catch block all changes are not getting rolled back only the statement that failed.
You should never have a begin tran without a try catch block and a rollback on error. I personally also prefer in something like this to put the errors and associated data into a table variable (which will not rollback) and then insert then to an exception table after the rollback. This way the data retains integrity and you can look up what the problem was.