I have a trigger in "Contracts" and I also have a table called "Audits" (self explanatory).
Everything is working fine. If I insert, edit or delete, a row is inserted into Audits table by the trigger...
The problem here is that Trigger does not accept parameters... and I have a table column called "TriggeredBy" inside of the Audits table... which is supposed to have the User's ID (whoever did the insert, delete or UPDATE).
Is there a workaround that I can use so I can pass that value to that trigger?
If you have the db connection opened for the duration of the application, you can keep track of who is associated with the current db session by having a table with session if, user id.
SessionId int,
UserId varchar(20)
At login time, use ##SPID to store the session ID and associated user.
The trigger can then use ##SPID and retrieve the user ID from the table and insert it into the log table.
Option 2:
Use an application role. Allow users to connect to SQL server database using Windows Integrated Security. Call sp_setapprole to set the role. Users should be given no access to any table. The app role should have insert update delete.
You can now determine the user in your trigger.
If the desktop application used Windows authentication, you could simply use ORIGINAL_LOGIN() or SUSER_SNAME() to get the end user account name in trigger code.
With a shared SQL login, one method is to store the end user name in SQL session context for use by the trigger. Session context allows you to store name/value pairs using the sp_set_session_context procedure and read current session values with the SESSION_CONTEXT function. Call sp_set_session_context with the current user name after opening a new SQL connection so that it can be used by triggers to identify the end user.
Example T-SQL code below. Also, see this answer for other methods to set/use session level values.
CREATE TRIGGER TR_YourTable
ON dbo.YourTable
FOR INSERT, UPDATE, DELETE
AS
DECLARE #TriggeredBy sysname = COALESCE(CAST(SESSION_CONTEXT(N'end-user-name') AS sysname), N'unknown');
IF EXISTS(SELECT 1 FROM inserted) AND EXISTS(SELECT 1 FROM deleted)
BEGIN
INSERT INTO dbo.YourAuditTable (Action, SomeColumn, TriggeredBy)
SELECT 'updated', SomeColumn, #TriggeredBy
FROM deleted;
END
ELSE
BEGIN
IF EXISTS(SELECT 1 FROM inserted)
BEGIN
INSERT INTO dbo.YourAuditTable (Action, SomeColumn, TriggeredBy)
SELECT 'inserted', SomeColumn, #TriggeredBy
FROM inserted;
END
ELSE
BEGIN
INSERT INTO dbo.YourAuditTable (Action, SomeColumn, TriggeredBy)
SELECT 'deleted', SomeColumn, #TriggeredBy
FROM deleted;
END;
END;
GO
--Example T-SQL usage. Queries should be parameterized in application code.
EXEC sp_set_session_context N'end-user-name', N'me';
INSERT INTO dbo.YourTable (SomeColumn) VALUES('example');
GO
Related
My user name has changed from jdoe to john.doe (for example). The reference in the [dbo].[Users] table shows my old user name, jdoe.
However, with my new user name, john.doe, the subscriptions fail to run and throw the error:
Failure sending mail: The user or group name 'ABCDE\jdoe' is not
recognized.Mail will not be resent.
The [dbo].[Subscriptions].[OwnerID] value references the [dbo].[Users].[UserID] for jdoe.
Can I simply change the [dbo].[Users].[UserName] value to my new username, leaving the [dbo].[Users].[UserID] and [dbo].[Users].[Sid] columns as is?
On your SQL Server, go to Security -> Logins. Then, rename the old username with the new username (just right click on it and select "Rename").
Next, go to the "Report Server" database, select the "Users" table, and update the "UserName" column for your old user.
This script should do the work:
UPDATE [ReportServer].[dbo].[Users] SET UserName='domain\newUser' WHERE UserName='domain\OldUser'
The SSRS Subscriptions and other SSRS features should now work properly.
I would use a MERGE statement for this. Check the transaction on a rollback to test with first. You'll also need UPDATE permission on the dbo.Users table.
SET XACT_ABORT ON
BEGIN TRANSACTION;
;WITH
users_list
AS
(
SELECT users_list.* FROM (VALUES
('DOMAIN\OldUser1', 'DOMAIN\New.User1')
, ('DOMAIN\OldUser2', 'DOMAIN\New.User2')
) users_list ([UserName], [NewUserName])
)
,
users_source -- don't add users that already exist to prevent duplicates
AS
(
SELECT
[UserName]
, [NewUserName]
FROM
users_list
WHERE
1=1
AND [NewUserName] NOT IN(SELECT [UserName] FROM [ReportServer].[dbo].[Users])
)
MERGE [ReportServer].[dbo].[Users] AS T
USING users_source AS S
ON T.[UserName] = S.[UserName]
WHEN MATCHED
THEN UPDATE SET
T.[UserName] = S.[NewUserName]
OUTPUT ##SERVERNAME AS [Server Name], DB_NAME() AS [Database Name], $action, inserted.*, deleted.*;
ROLLBACK TRANSACTION;
--COMMIT TRANSACTION;
GO
I have tried renaming the dbo.Users.UserName to the new DOMAIN/JDoe, but that still produces errors.
However, renaming the SQL Server's Security Username from DOMAIN/OldName to DOMAIN/NewName did the trick.
Using SQL Server 2008R2
I am working on a LOGON trigger to capture users who have logged in. I have created a trigger, but it is capturing the SQL Agent account and is inserting it like mad. I would like to exclude it from the list, but I'm not sure how to modify the trigger to make it so. The different attempts locked me out and I had to use DAC to drop the trigger.
The original code:
USE B_DBA;
GO
CREATE TRIGGER LogonTrigger ON ALL SERVER FOR LOGON
AS
BEGIN
IF SUSER_SNAME() <> 'sa'
INSERT INTO B_DBA.dbo.LogonAudit (UserName, LogonDate, spid)
VALUES (SUSER_SNAME(), GETDATE(), ##SPID)
END;
GO
ENABLE TRIGGER LogonTrigger ON ALL SERVER;
What I'd like to add is basically a where clause, but I haven't been able to add it successfully and it would not work on the IF section.
Where not in (Select service_account from sys.dm_server_services)
Any help would be appreciated.
Untested, but it seems like you should be able to modify your IF statement to check sys.dm_server_services:
USE B_DBA;
GO
CREATE TRIGGER LogonTrigger ON ALL SERVER FOR LOGON
AS
BEGIN
IF SUSER_SNAME() <> 'sa'
AND NOT EXISTS (SELECT service_account FROM sys.dm_server_services WHERE service_account = SUSER_SNAME())
INSERT INTO B_DBA.dbo.LogonAudit (UserName, LogonDate, spid)
VALUES (SUSER_SNAME(), GETDATE(), ##SPID)
END;
GO
ENABLE TRIGGER LogonTrigger ON ALL SERVER;
I have a below DDL Trigger on my server
CREATE TRIGGER [DDLForLogin] ON ALL SERVER
FOR LOGON
AS BEGIN
DECLARE #data XML
SET #data = EVENTDATA()
IF EXISTS(SELECT * FROM sys.Databases WHERE NAME = 'DatabaseMaintenance') Begin
INSERT INTO DatabaseMaintenance.dbo.MyTable (UserName, HostName, ApplicationName, EventDataValue)
VALUES (CURRENT_USER, HOST_Name(), APP_NAME(),#Data)
END
END;
a login as Windows Authentication insert one row on MyTable but a login as SQL Server Authentication raised below error :
Logon failed for login 'xxx' due to trigger execution.
Changed database context to 'master'.
Changed language setting to us_english. (Microsofr SQL Server, Error: 17892)
EDIT
I GRANT INSERT on my table to PUBLIC.
but no change on raised error.
EDIT2
I change my trigger and add With Execute AS 'sa' on the trigger
but no change on raised error.
Try adding an appropriate EXECUTE AS clause to your trigger - the default is CALLER, so unless that user has permissions to insert into your audit table1, the trigger will fail.
Also, then use ORIGINAL_LOGIN() in the trigger to get the correct login information
1 Which you normally don't want - because otherwise every user can forge entries alleging that other users have logged in.
Must change trigger to below code:
CREATE TRIGGER [DDLForLogin] ON ALL SERVER
WITH EXECUTE AS 'sa'
FOR LOGON
AS BEGIN
DECLARE #data XML
SET #data = EVENTDATA()
DECLARE #IsPooled int
SET #IsPooled = #data.value('(/EVENT_INSTANCE/IsPooled)[1]', 'int')
IF EXISTS(SELECT * FROM sys.Databases WHERE NAME = 'DatabaseMaintenance')AND (#IsPooled=0) Begin
insert into DatabaseMaintenance.dbo.Login (UserName, HostName, ApplicationName, EventDataValue)
values (ORIGINAL_LOGIN(), HOST_Name(), APP_NAME(),#Data)
END
END;
Any idea how to create a after trigger where each where each a user put in their data once. Eg. if someone put in an entry once, then it will be executed. But 2nd time i would like to say 'error message'
The tables I have a table of users, the date and their response. TABLE name is userresponse
This is my coding so far
CREATE TRIGGER [dbo].[prevent_multiple_entry] ON [dbo].[userresponse]
FOR INSERT
AS
IF EXISTS (SELECT Users FROM userresponse)
BEGIN
PRINT 'Error message'
RAISERROR('Each user can only submit to the same question once ',16,1)
ROLLBACK
END
try this one :
CREATE TRIGGER [dbo].[prevent_multiple_entry] ON [dbo].[userresponse]
FOR INSERT
AS
BEGIN
IF EXISTS(SELECT * FROM userresponse AS U, INSERTED AS I WHERE U.User = I.User)
BEGIN
RAISERROR('Each user can only submit to the same question once ',16,1)
ROLLBACK TRANSACTION
END
END
Make sure that the 'nested triggers' and recursive_triggers settings are approriate and think about the fact that allready existing data in that table won't be checked by a trigger.
And SET TRANSACTION ISOLATION LEVEL SNAPSHOT sets a loophole, be carefull.
You also could define a CONSTRAINT...
I need to create a trigger for my table that will contain millions of inserts.
That trigger will send an email (already done) to me saying that table is being powered.
That email should be send only once when the insert starts (one a day).
In other words, I wanna do this :
Create trigger on table T1 on FIRST INSERT; EXEC my procedure to send email
Not being a developer I really don't know how to write this...
Thank you for you help.
You cannot easily do a ON FIRST INSERT trigger.
What you could do is this:
create a normal ON INSERT trigger that sends you the email
at the end of that trigger, disable that trigger:
ALTER TABLE dbo.YourTable DISABLE TRIGGER YourTriggerNameHere
This would prevent the trigger from firing over and over again and sending you tons of e-mail.
Then, you'd also need a SQL Agent job that would at night enable that trigger again - so that it could fire again for the first insert of the next day.
Update: OK, so in your case it would be:
CREATE TRIGGER sendMail
ON MyTable AFTER INSERT
AS
EXEC master.dbo.MyProcedure
ALTER TABLE dbo.MyTable DISABLE TRIGGER sendMail
and in your SQL Agent job at night, you'd need:
ALTER TABLE dbo.MyTable ENABLE TRIGGER sendMail
Why not have a table that you also update in your trigger with the last time that an email was sent out? Then you could query this table in the trigger and decide whether you want to send out another on
create trigger tr
on tab after insert
as
begin
declare #today datetime;
set #today = cast(convert(varchar(32), getdate(), 112) as datetime);
if not exists(select * from logtab where logdate = #today)
begin
-- Send the email
exec sendMail
-- Update Log Table
update logtab
set logdate = #today
end
end
GO
You cant directly fire a trigger on first update.
You can however have a table that log when you send emails and for what table, and in the trigger you can ask if there is a record there for that day dont send it, if its not, insert in that table and call your sp.
Your sp could do this check also.