I am new to triggers and cursors and would like to understand what a particular trigger is doing. Here is the Trigger.
CREATE TRIGGER [dbo].[trg_XOnUpdate]
ON TableX
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #XID INT, #XKey NVARCHAR(33)
DECLARE updated_cursor CURSOR FOR
SELECT XID, XKey
FROM INSERTED
WHERE XStatus
IN ('AA', 'BB', 'CC')
OPEN updated_cursor
FETCH NEXT FROM updated_cursor INTO #XID, #XKey
WHILE ##FETCH_STATUS = 0
BEGIN
EXECUTE usp_UpdateXData #XID, #XKey
FETCH NEXT FROM updated_cursor INTO #XID, #XKey
END
CLOSE updated_cursor
DEALLOCATE updated_cursor
END
Understanding the Basic of Triggers
A trigger is an operation that is executed when some kind of event occurs to the database. It can be a data or object change. Listed below are the different types of tiggers:
Types of Triggers
DML(data manipulation language) triggers (SQL Server 2000- 80.0)
DDL(data definition language) triggers (SQL Server 2005- 90.0)
SQLCLR triggers (SQL Server 2005- 90.0)
Rules of Triggers
cannot create or modify Database objects using triggers
cannot perform any administrative tasks
cannot pass any kind of parameters
cannot directly call triggers
Advantage of Triggers
Triggers are useful for auditing data changes or auditing database as well as managing business rules. Below are some examples:
Triggers can be used to enforce referential integrity (For example you may not be able to apply foreign keys)
Can access both new values and old values in the database when going to do any insert, update or delete
Drill down further about triggers...
Understanding the Basic of Triggers
Understanding SQL Server inserted and deleted tables for DML triggers
Related
I am confused that what is the on database in the below trigger what is the purpose of using that please let me know what is the use of that
create trigger trmyfirsttrigger
on database
for create_table,alter_table,drop_table
as
begin
rollback
print 'you can not create ,alter,drop table.'
end
The trigger is defined at the database level and is intended to prevent any create, alter and drop statements on any table. That means you cannot create a new table, alter or delete existing tables on the database. This is generally used by DBAs to lock the database from any changes during maintenance & patching.
I have a stored procedure to run after a set of tables, all belonging to the same schema [DATA_Countries], is dropped and then re-inserted.
The operation is performed by another application, which drops and recreates the target table, over which I have no control.
Since the table is dropped and recreated each time, I can not use triggers on each target table.
Is there a way to get a trigger for each time a table is inserted into a specific schema, to return the name of such table and launch a parametrized stored procedure?
Thanks!
Yes, You can create DDL Triggers on SQL Server to track the DDL Changes. For example, If I want to track the changes in Stored Procedures on my Database AdventureWorks, I can create a trigger like this
CREATE TRIGGER td_ProcTrack
ON AdventureWorks
FOR CREATE_PROCEDURE, ALTER_PROCEDURE, DROP_PROCEDURE
AS
BEGIN
<my code>
END
Refer this Article for more detailed examples
In MS SQL Server, I'm using a global temp table to store session related information passed by the client and then I use that information inside triggers.
Since the same global temp table can be used in different sessions and it may or may not exist when I want to write into it (depending on whether all the previous sessions which used it before are closed), I'm doing a check for the global temp table existence based on which I create before I write into it.
IF OBJECT_ID('tempdb..##VTT_CONTEXT_INFO_USER_TASK') IS NULL
CREATE TABLE ##VTT_CONTEXT_INFO_USER_TASK (
session_id smallint,
login_time datetime,
HstryUserName VDT_USERNAME,
HstryTaskName VDT_TASKNAME,
)
MERGE ##VTT_CONTEXT_INFO_USER_TASK As target
USING (SELECT ##SPID, #HstryUserName, #HstryTaskName) as source (session_id, HstryUserName, HstryTaskName)
ON (target.session_id = source.session_id)
WHEN MATCHED THEN
UPDATE SET HstryUserName = source.HstryUserName, HstryTaskName = source.HstryTaskName
WHEN NOT MATCHED THEN
INSERT VALUES (##SPID, #LoginTime, source.HstryUserName, source.HstryTaskName);
The problem is that between my check for the table existence and the MERGE statement, SQL Server may drop the temp table if all the sessions which were using it before happen to close in that exact instance (this actually happened in my tests).
Is there a best practice on how to avoid this kind of concurrency issues, that a table is not dropped between the check for its existence and its subsequent use?
The notion of "global temporary table" and "trigger" just do not click. Tables are permanent data stores, as are their attributes -- including triggers. Temporary tables are dropped when the server is re-started. Why would anyone design a system where a permanent block of code (trigger) depends on a temporary shared storage mechanism? It seems like a recipe for failure.
Instead of a global temporary table, use a real table. If you like, put a helpful prefix such as temp_ in front of the name. If the table is being shared by databases, then put it in a database where all code has access.
Create the table once and leave it there (deleting the rows is fine) so the trigger code can access it.
I'll start by saying that, on the long term, I will follow Gordon's advice, i.e. I will take the necessary steps to introduce a normal table in the database to store client application information which needs to be accessible in the triggers.
But since this was not really possible now because of time constrains (it takes weeks to get the necessary formal approvals for a new normal table), I came up with a solution for preventing SQL Server from dropping the global temp table between the check for its existence and the MERGE statement.
There is some information out there about when a global temp table is dropped by SQL Server; my personal tests showed that SQL Server drops a global temp table the moment the session which created it is closed and any other transactions started in other sessions which changed data in that table are finished.
My solution was to fake data changes on the global temp table even before I check for its existence. If the table exists at that moment, SQL Server will then know that it needs to keep it until the current transaction finishes, and it cannot be dropped anymore after the check for its existence. The code looks now like this (properly commented, since it is kind of a hack):
-- Faking a delete on the table ensures that SQL Server will keep the table until the end of the transaction
-- Since ##VTT_CONTEXT_INFO_USER_TASK may actually not exist, we need to fake the delete inside TRY .. CATCH
-- FUTURE 2016, Feb 03: A cleaner solution would use a real table instead of a global temp table.
BEGIN TRY
-- Because schema errors are checked during compile, they cannot be caught using TRY, this can be done by wrapping the query in sp_executesql
DECLARE #QueryText NVARCHAR(100) = 'DELETE ##VTT_CONTEXT_INFO_USER_TASK WHERE 0 = 1'
EXEC sp_executesql #QueryText
END TRY
BEGIN CATCH
-- nothing to do here (see comment above)
END CATCH
IF OBJECT_ID('tempdb..##VTT_CONTEXT_INFO_USER_TASK') IS NULL
CREATE TABLE ##VTT_CONTEXT_INFO_USER_TASK (
session_id smallint,
login_time datetime,
HstryUserName VDT_USERNAME,
HstryTaskName VDT_TASKNAME,
)
MERGE ##VTT_CONTEXT_INFO_USER_TASK As target
USING (SELECT ##SPID, #HstryUserName, #HstryTaskName) as source (session_id, HstryUserName, HstryTaskName)
ON (target.session_id = source.session_id)
WHEN MATCHED THEN
UPDATE SET HstryUserName = source.HstryUserName, HstryTaskName = source.HstryTaskName
WHEN NOT MATCHED THEN
INSERT VALUES (##SPID, #LoginTime, source.HstryUserName, source.HstryTaskName);
Although I would call it a "use it at your own risk" solution, it does prevent that the use of the global temp table in other sessions affects its use in the current one, which was the concern that made me start this thread.
Thanks all for your time! (from text formatting edits to replies)
I've just created a trigger (AFTER INSERT, UPDATE) that is designed to strip the time from a date field. I just figured that a trigger would be easier to implement than having to fix it in the application that inserts/updates the table. The latter would involve re-compiles, and kicking folks out of the app for updates.
But I'm not sure if I'm being lazy or clever, to be honest.
CREATE TRIGGER [dbo].[StripCastDateTime] ON [dbo].[PileInventory]
AFTER INSERT, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE PileInventory
SET CastDate =
cast(CONVERT(varchar(11), Inserted.CastDate, 101) AS datetime)
FROM INSERTED
INNER JOIN [dbo].[PileInventory] AS PI
ON [PI].[JobNumber] = INSERTED.JobNumber
AND [PI].[MarkNumber] = INSERTED.MarkNumber
WHERE NOT Inserted.CastDate IS NULL
END
GO
It works perfectly, but is this a proper use of triggers?
Thanks.
Marshall
If your updated tables are big then this would hurt your DB. I would use a trigger "INSTEAD OF INSERT" to change the data and after that insert it. There is no point in doing it after it is in the DB.
Other then that .. it's a valid usage of triggers.
Read more here:
http://technet.microsoft.com/en-us/library/ms175089(v=sql.105).aspx
I can put a business rule in the business layer. Lazy ones needs to work twice is a old said here and if you can kick people off the application in a maintenance window do it. If you do a a lot of inserts that trigger can slow down your app, also triggers are quick to grow complex and add some maintenance pain.
I'd like to turn trigger recursion on/off in my SQL CLR trigger. According to http://www.devx.com/tips/Tip/30031, I have to call
EXEC sp_dboption '<name of db>', 'recursive triggers', 'true'/'false'
Is there a way to get to know what the current DB name is? When creating trigger, I ask users to choose one, but I don't want to write it in a table.
Regards,
There is a very simple way to find the name of the database in which the SQLCLR Trigger is being fired: just make a connection to the Context Connection and get the Database property. You don't even need to execute a query :-).
The following should work in all SQLCLR object types (Stored Procedure, Function, User-Defined Aggregate, User-Defined Type, and Trigger):
string _DatabaseName;
using (SqlConnection _Connection = new SqlConnection("Context Connection = true;"))
{
_Connection.Open();
_DatabaseName = _Connection.Database;
}
That's it! I just tried it in a SQLCLR Trigger and it works great.
Another thing to keep in mind for limiting Triggers firing other Triggers is the TRIGGER_NESTLEVEL function. This works better in T-SQL Triggers where the value of ##PROCID is available and contains the [object_id] of the Trigger. So in T-SQL Triggers you can limit the recursion of each trigger individually but still allow Triggers to fire other Triggers on other Tables.
In SQLCLR it can still be used, but without the name of the Trigger you can only limit all Triggers. Meaning, you can prevent any Trigger from firing any other Trigger on any Table, including on the same Table, but there is no way to limit the firing of only that same Trigger while allowing Triggers on other tables that might be modified by the Trigger in question. Just use a Context Connection and run SELECT TRIGGER_NESTLEVEL(); via SqlCommand.ExecuteScalar().
You know what the database is when you create the trigger...
CREATE TRIGGER etc
....
GO
DECLARE #db varchar(100)
SET #db = DB_NAME()
EXEC sp_dboption #db, 'recursive triggers', 'true'/'false'
I've found a better solution.
I have to avoid calling EXEC sp_dboption at all. Instead, I have to create a temp table as a flag "no recursion", then check existing of the table at the beginning of the trigger and exit, if the table exists.
Why temporary table?
It's being killed at the end of the session. No need to reset the flag (in exceptional situation), which is necessary otherwise to avoid trigger being off permanently.
AFAIK, it's being created and killed independently for every connection. So, if the user changes data the same time, there will be no conflict (which is inevitable for EXEC sp_dboption).