SQL2012 microsoft trigger - sql-server

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...

Related

What's the workaround to pass parameters to triggers?

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

MS SQL Insert Trigger Checking for Correct Number Format in Column

I
need some code advice,
when printing out offers, our ERP Program generates an ID Number in the Table "Angebot" in the format AYYNNNNN, this mask is set in the administrative settings, but it also has an option to override this number and set a manual one, which causes lots of trouble, as people tend to break the id counter.
I'd like to create a trigger that sends a message when the id number is not in the correct format, so I have to check for that specific column to be correct.
The if statenent would check the following:
if (Angebotsnr NOT LIKE 'A'+RIGHT(DATEPART(yy,getdate())+'_____') then RAISEERROR
There is already an existing trigger in the database that checks for something else, so I only need to add the second check to ensure that it is right, but where would I put the if statement and how do I check it?
This is the code of the existing trigger:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[ANGEBOT_ITrig] ON [dbo].[Angebot] FOR INSERT AS
SET NOCOUNT ON
/* * KEINE EINFÜGUNG BEI FEHLEN EINES PASSENDEN SCHLÜSSELS IN 'ErlGrp' */
IF (SELECT COUNT(*) FROM inserted) !=
(SELECT COUNT(*) FROM ErlGrp, inserted WHERE (ErlGrp.ABTNR = inserted.ABTEILUNG))
BEGIN
RAISERROR ( 'Some error statement',0,0)
ROLLBACK TRANSACTION
END
The action would be the same, just with a different error message.
Can someone point me to the right direction.
Thanks.
I would handle this in the procedure that's doing the insert so it doesn't ever insert and fire off other triggers.
create proc myInsertProc (#ID char(8))
as
begin
--copied from you, but it's missing part of the right function
if (Angebotsnr NOT LIKE 'A'+RIGHT(DATEPART(yy,getdate())+'_____')
begin
raiserror('You provided a wrongly formatted ID',16,1) with log
return
end
...continue on with other code
end
This will raise the error, and write it to the SQL Server Error Log. You can remove with log if you don't want that. The return ends the batch. You can also wrap this in a try catch on insert if you'd.
I'd use this in the IF block personally.
if (Angebotsnr NOT LIKE 'A' + right(convert(varchar,DATEPART(year,getdate())),2) + '%' or len(Angebotsnr) != 8)

how to lock a field in table for update in sql

Need lock for update a field in a table or maybe pop up a message alter user when this field is update. But still need do be insert or delete a record. i simply try to use command
DENY UPDATE ON JobEmp (Job) TO public
It will not let me do any thing to Job Column, can not add, change or delete. Need some help. Thanks
Using Code
CREATE TRIGGER tr_No_Update_Job
ON dbo.JobEmp
FOR UPDATE
AS
BEGIN
IF UPDATE(Job)
BEGIN
RAISERROR('This column cannot be updated', 16,1)
RETURN;
END
END
But when insert a new record, it also throw the error message. How can i only lock for update?
You can not Grant, Deny or Revok permission on one column of the table you can either deny UPDATE permission on a table on sql server permissions level or you need to create a Trigger to control column level permission.
Table Level Permissions
DENY UPDATE ON OBJECT::[Schema].[TableName] TO [PrincipalName];
Column Level Update Control
CREATE TRIGGER tr_No_Update_Job
ON dbo.JobEmp
FOR UPDATE
AS
BEGIN
IF UPDATE(Job)
BEGIN
RAISERROR('This column cannot be updated', 16,1)
RETURN;
END
END
Do the rollback from after update trigger:
create trigger trJobEmpUpd
on JobEmp
after update
as
if update(Job)
rollback
Have a look to BEGIN/COMMIT TRAN and transaction isolation levels.
If your questions is about Oracle
There is a syntax "for update" that locks a record (not a field)
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4530093713805
-> For SQL server I think the "for update" syntax is "Select ... WITH (updlock)..."
http://technet.microsoft.com/en-us/library/aa213026(v=sql.80).aspx
Note that as been noted by #huMpty duMpty you have to be in a transaction for the lock to be held...
This syntax will issue a row lock on the data selected, which will be released only on commit.
other users will be able to query the data (depends on the DBMS) but not to modify it.
I am pretty sure most other DBMS has the same / similar syntax for locking selected query results, while selecting the data - which allow atomicity - what you got from the "select" will be locked, in the same command, and no one else can intervene in the middle.
If you want to permanently not allow access to a column (for select or update) you should use a different scheme, and allow permisions on a view of the data , or only select permission with stored procedures for editing the actualy data.

Force unique varchar(255) column value in a SQL Server table with Entity Framework

I have database records for some users on a Windows server. One of the columns is the user's SID (unique string for that user).
When I get a message for a user, I want to first perform a query for a record with the SID value for the SID column. If no record is returned, then create a record with that SID value. However, between the query and the add, another thread can get a message with the same user/SID. So I could end up adding it twice.
Is there a way to create a transaction where no other record can be added to the table until the transaction completes?
Or is there a better way as locking an entire table, because no matter how fast the query/add, that's a choke point. (If this is the best way, I can query with no transaction, return if it exists, and only in the rare case it does not, then transaction, query again, add.)
Is there a way to tell SQL Server that the column is unique? The properties for setting that are disabled in SQL Server Management Studio, I assume because it's a varchar.
thanks - dave
You can Create a Unique Index on the column to enforce uniquenss of the values on the column and the you can use the following Code tweak it to your database specifications,
Warring Since I have set Transaction Isolation Level to SERIALIZABLE it can slow down the query if multipule users try to ADD the data since it does not allow other user to change or add data into the table when this Transaction Isolation Level is set to SERIALIZABLE...
CREATE Procedure usp_InsertSID
#SID varchar(225)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
IF #SID IS NULL
RAISERROR('Validation Failed: SID is cannot be null', 16,1)
IF EXISTS (SELECT SID from tableName
WHERE SID= #SID)
RAISERROR('Validation Failed: SID already Exists',16,1)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
INSERT INTO tableName(ColumnNames)
VALUES (#SID)
COMMIT TRANSACTION
SELECT #ReturnMessage = 'Sucess! New SID is added.'
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
ROLLBACK TRAN
SELECT #ReturnMessage = ERROR_MESSAGE()
SELECT #ReturnCode AS ReturnCode, #ReturnMessage AS ReturnMessage,
ERROR_LINE() AS ErrorLine,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState
END CATCH
END
GO

Ignoring errors in Trigger

I have a stored procedure which is called inside a trigger on Insert/Update/Delete.
The problem is that there is a certain code block inside this SP which is not critical.
Hence I want to ignore any erros arising from this code block.
I inserted this code block inside a TRY CATCH block. But to my surprise I got the following error:
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Then I tried using SAVE & ROLLBACK TRANSACTION along with TRY CATCH, that too failed with the following error:
The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
My server version is: Microsoft SQL Server 2008 (SP2) - 10.0.4279.0 (X64)
Sample DDL:
IF OBJECT_ID('TestTrigger') IS NOT NULL
DROP TRIGGER TestTrigger
GO
IF OBJECT_ID('TestProcedure') IS NOT NULL
DROP PROCEDURE TestProcedure
GO
IF OBJECT_ID('TestTable') IS NOT NULL
DROP TABLE TestTable
GO
CREATE TABLE TestTable (Data VARCHAR(20))
GO
CREATE PROC TestProcedure
AS
BEGIN
SAVE TRANSACTION Fallback
BEGIN TRY
DECLARE #a INT = 1/0
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION Fallback
END CATCH
END
GO
CREATE TRIGGER TestTrigger
ON TestTable
FOR INSERT, UPDATE, DELETE
AS
BEGIN
EXEC TestProcedure
END
GO
Code to replicate the error:
BEGIN TRANSACTION
INSERT INTO TestTable VALUES('data')
IF ##ERROR > 0
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
GO
I was going through the same torment, and I just solved it!!!
Just add this single line at the very first step of your TRIGGER and you're going to be fine:
SET XACT_ABORT OFF;
In my case, I'm handling the error feeding a specific table with the batch that caused the error and the error variables from SQL.
Default value for XACT_ABORT is ON, so the entire transaction won't be commited even if you're handling the error inside a TRY CATCH block (just as I'm doing). Setting its value for OFF will cause the transaction to be commited even when an error occurs.
However, I didn't test it when the error is not handled...
For more info:
SET XACT_ABORT (Transact-SQL) | Microsoft Docs
I'd suggest re-architecting this so that you don't poison the original transaction - maybe have the transaction send a service broker message (or just insert relevant data into some form of queue table), so that the "non-critical" part can take place in a completely independent transaction.
E.g. your trigger becomes:
CREATE TRIGGER TestTrigger
ON TestTable
FOR INSERT, UPDATE, DELETE
AS
BEGIN
INSERT INTO QueueTable (Col1,Col2)
SELECT COALESCE(i.Col1,d.Col1),COALESCE(i.Col2,d.Col2) from inserted i,deleted d
END
GO
You shouldn't do anything inside a trigger that might fail, unless you do want to force the transaction that initiated the trigger action to also fail.
This is a very similar question to Why try catch does not suppress exception in trigger
Also see the answer here T-SQL try catch transaction in trigger
I don’t think you can use savepoints inside a trigger. I mean, you can but I googled about it and I saw a few people saying that they don’t work. If you replace your “save transaction” for a begin transaction, it compiles. Of course it is not necessary because you have the outer transaction control and the inner rollback would rollback everything.

Resources