Regarding SQL Server delete trigger - sql-server-2005

I want to capture the user's name who deletes a row, when delete action is taken then a stored procedure deletes a row from table. We are providing all required parameters to the stored procedure and also the user's name from the front end who is deleting data.
Basically I want to capture the user's name who is deleting from stored procedure from delete trigger. But it is not possible. We can do one thing that before deleting record we can put the user's name into a temp table and get that name from trigger. But there is one problem that at a time two users can delete two different rows. So what will be the best solution? I don't want use any shadow tables. please discuss. thanks

You are basically asking how to pass the parameter into the trigger?
You can use set CONTEXT_INFO inside the procedure and read this inside the trigger.
DECLARE #name VARBINARY(128)
SET #name = CAST('Martin' AS VARBINARY(128));
SET CONTEXT_INFO #name /*Set it*/
SELECT CAST(CONTEXT_INFO() AS VARCHAR(128)) /*Read it*/
SET CONTEXT_INFO 0x /*Reset it*/

If you've already got a stored procedure, why bother with triggers? Have the stored procedure log who deleted the row.

Related

What can I use instead of an update trigger?

I have an update trigger in SQL Server and I want to remove this trigger and make update operation with a stored procedure instead of the trigger. But I have UPDATE(end_date) control in update trigger.
What can I use instead of below UPDATE(end_date) control? How can I compare old and new end_dates in stored procedure efficiently?
Update trigger
ALTER TRIGGER [dbo].[trig_tbl_personnel_car_update]
ON [dbo].[tbl_personnel_cars]
FOR UPDATE
AS
IF (UPDATE(end_date))
UPDATE pc
SET pc.owner_changed = 1
FROM tbl_personnel_cars pc, inserted i
WHERE pc.pk_id = i.pk_id
Sample updated script in stored procedure
ALTER PROCEDURE [dbo].[personnel_car_update]
(#PkId INT)
UPDATE tbl_personnel_cars
SET end_date = GETDATE()
WHERE pk_id = #PkId
I update tbl_personnel_cars table inside many stored procedures like this. How can I update this table like trigger does instead of update trigger?
I tried below codes to get old and new end_dates but I can't.
Sample updated script in stored procedure:
ALTER PROCEDURE [dbo].[personnel_car_update]
(#PkId INT)
UPDATE tbl_personnel_cars
SET end_date = GETDATE()
WHERE pk_id = #PkId
EXEC update_operation_sp_instead_trigger #PkId
ALTER PROCEDURE [dbo].[update_operation_sp_instead_trigger]
(#PkId INT)
UPDATE pc
SET pc.owner_changed = 1
FROM tbl_personnel_cars pc
JOIN tbl_personnel_cars pc2 ON pc.pk_id = pc2.pk_id
WHERE pc.end_date <> pc2.end_date
And last question. Is it a correct choice to use stored procedure instead of trigger where the table is updated?
Firstly, I want to clarify a misunderstanding you appear to have about the UPDATE function in Triggers. UPDATE returns a boolean result based on if the column inside the function was assigned a value in the SET clause of the UPDATE statement. It does not check if that value changed. This is both documented feature, and is stated to be "by-design".
This means that if you had a TRIGGER with UPDATE(SomeColumn) the function would return TRUE for both of these statements, even though no data was changed:
UPDATE dbo.SomeTable
SET SomeColumn = SomeColumn;
UPDATE ST
SET SomeColumn = NULL
FROM dbo.SomeTable ST
WHERE SomeColumn IS NULL;
If, within a TRIGGER, you need to check if a value has changed you need to reference the inserted and deleted pseudo-tables. For non-NULLable columns equality (=) can be checked, however, for NULLable columns you'll also need to check if the column changed from/to NULL. In the latest version of the data engine (at time of writing) IS DISTINCT FROM makes this far easier.
Now onto the problem you are actually trying to solve. It looks like you are, in truth, overly complicated this. Firstly, you are setting the value to GETDATE so it is almost certainly impossible that the column will be set to be the same value it already set to; you have a 1/300 second window to do the same UPDATE twice, and if you add IO operations, connection timing, etc, that basically makes hitting that window twice impossible.
For what you want, just UPDATE both columns in your procedure's definition:
ALTER PROCEDURE [dbo].[personnel_car_update] #PkId int AS --This has a trailing comma, which is invalid syntax. The parathesis are also not needed; SP's aren't functions. You were also missing the AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.tbl_personnel_cars --Always schema qualify
SET end_date = GETDATE(),
owner_changed = 1
WHERE pk_id = #PkId;
END;
Larnu gave you a great answer about the stored procedure logic, so I want to answer your question about "Is it a correct choice to use stored procedure instead of trigger where the table is updated?"
The upsides of DML triggers are following in my opinion:
When you have a lot of places that manipulate a table, and there need to be some common logic performed together with this manipulation like audit / logging, trigger can solve it nicely because you don't have to repeat your code in a lot of places
Triggers can prevent "stupid" actions like: DELETEs / UPDATEs without WHERE by performing some specific validation etc. They can also make sure you are setting all mandatory fields (for example change date) when performing adhoc updates
They can simplify quick and dirty patches when you can concentrate your logic to one place instead of X number of procedures / compiled code.
Downside of triggers is performance in some cases, as well as some more specific problems like output clause not working on the triggered tables and more complicated maintenance.
It's seldom you can't solve these issues with other non-trigger solutions, so i'd say if your shop already uses triggers, fine, but if they don't, then there's seldom a really good reason to start either

Check number of stored procedures from a different database

I am trying to fix a stored procedure that we have created to count number of running procedures with a certain name.
What I am trying to do is get the number of running stored procedures that match a specific title and it works fine when the Stored Procedure executing the query is in the same Database as the procedures I am trying to count as running. But I don't understand how I can count the number of running procedures matching a name running in a different Database. I assume the dbid parameter has to be used in some way, but I don't understand how.
The database I want to count in is called "ScheduledJobs" rather than MySampleDB where I have to put the stored procedure (I can't move it to the other DB for different reasons).
Any suggestions are welcome. Here is my code:
USE [MySampleDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[sp_CheckRuns2]
#RowsAffected INT OUTPUT
AS
BEGIN
SELECT
object_name(st.objectid) as ProcName
FROM
sys.dm_exec_connections as qs
CROSS APPLY sys.dm_exec_sql_text(qs.most_recent_sql_handle) st
WHERE
object_name(st.objectid) is not null and OBJECT_NAME(st.objectid) like '%sp_UPDATER'
END
select #RowsAffected = ##rowcount
RETURN #RowsAffected
Just pass in dbid as an extra parameter
WHERE OBJECT_NAME(st.objectid, st.dbid) like '%sp_UPDATER'
Side note: You should remove RETURN #RowsAffected. It is not necessary, as you are anyway passing back an OUTPUT parameter. Note also that most client APIs will give the rowcount in any case.

Trigger to detect whether DELETE or UPDATE is called by stored proc

I have a scenario where certain users must have the rights to update or delete certain records in production. What I want to do put in a safeguard to make sure they do not accidentally update or delete a large section (or entirety) of the table, but only a few records as per their need. So I wrote a simple trigger to accomplish this.
CREATE TRIGGER delete_check
ON dbo.My_table
AFTER UPDATE,DELETE AS
BEGIN
IF (SELECT COUNT(*) FROM Deleted) > 15
BEGIN
RAISERROR ('Bulk deletes from this table are not allowed', 16, 1)
ROLLBACK
END
END --end trigger
But here is the problem. There is a stored procedure that can do bulk updates to the table. The users can and should be allowed to call the stored procedure, as it's scope is more constrained. So my trigger would unfortunately preclude them from calling the stored proc when they need to.
The only solution I have thought of is to run the stored proc as an impersonated user, then modify the trigger to exclude that user from the logic. But that will bring up other issues in my environment. Not unsurmountable, but annoying. Nevertheless, this seems the only viable option.
Am I thinking about this the right way, or is there a better approach?
You can add a check of ##NESTLEVEL in the trigger. The value will be 1 for an ad-hoc statement or 2 when called from the stored procedure.
CREATE TRIGGER delete_check
ON dbo.My_table
AFTER UPDATE,DELETE AS
BEGIN
IF (SELECT COUNT(*) FROM Deleted) > 15
AND ##NESTLEVEL = 1 --ad-hoc delete
BEGIN
RAISERROR ('Bulk deletes from this table are not allowed', 16, 1);
ROLLBACK;
END;
END;
I usually handle this with CONTEXT_INFO(). This gives you a better control than ##NESTLEVEL because you can actually identify the specific stored procedure doing the calling and handle them individually if required. You do this as follows:
Add the procedure name to CONTEXT_INFO() e.g.
-- START OF STORED PROCEDURE
-- Tell the trigger who we are, and that we can be trusted.
declare #OldContext char(128), #NewContext varbinary(128);
-- Get existing context_info()
set #OldContext = coalesce(convert(char(128), context_info()), '');
-- Add new info to context_info
set #NewContext = convert(varbinary(128),convert(char(128), 'dbo.MyProcedureName'));
-- Store new context info
set context_info #NewContext;
-- STORED PROCEDURE CONTENT
-- END OF STORED PROCEDURE
-- Restore context_info
set #NewContext = convert(varbinary(128), #OldContext);
set context_info #NewContext;
In the trigger return early if the CONTEXT_INFO() is from a trusted source e.g.
-- START OF TRIGGER
declare #NewContext char(128) = coalesce(convert(char(128),context_info()),'');
if #NewContext in ('dbo.MyProcedureName') begin
return;
end;
Another advantage of this approach (for other trigger uses) is you can avoid carrying out logic in a trigger when called from a SP. Because often you put logic in a trigger to ensure that it happens regardless of how the insert/update/delete happens. But when done in an SP you can ensure the required logic is carried out within the SP which avoids the need to do it in a trigger. Especially useful if you end up with performance issues due to too much logic in the trigger.
Note: For SQL Server 2016+ you can use SESSION_CONTEXT() in a similar way.
One way of doing this would be:
Create a stored procedure to perform the desired work
If the criteria varies greatly, create several procedures for each "kind" of work
Grant EXECUTE permissions to the desired users on those procedures ("kind of work") that they are permitted to do. (You are using Database Roles and Domain Groups, right?)
Revoke permissions to make ad hoc data modifications on the table
It can be fussy to set up, but it supports the "Principle of Least Permissions", permitting users to do only what they are supposed to do.

Is it possible for a trigger to find the name of the stored procedure that modified data?

There are a few stored procedures that routinely get called by a few different systems to do maintenance on a few tables in our database. Some are automated, some aren't.
One of the tables has a column where the number is sometimes off, and we don't know for sure when or why this is happening. I want to put a trigger on the table so I can see what is being changed and when, but it'd also be helpful to know which procedure initiated the modification.
Is it possible to get the name of the stored procedure from the trigger? If not, is there any other way to tell what caused something to be modified? (I'm not talking about the user either, the name of the user doesn't help in this case).
you can try: CONTEXT_INFO
here is a CONTEXT_INFO usage example:
in every procedure doing the insert/delete/update that you want to track, add this:
DECLARE #string varchar(128)
,#CONTEXT_INFO varbinary(128)
SET #string=ISNULL(OBJECT_NAME(##PROCID),'none')
SET #CONTEXT_INFO =cast('Procedure='+#string+REPLICATE(' ',128) as varbinary(128))
SET CONTEXT_INFO #CONTEXT_INFO
--do insert/delete/update that will fire the trigger
SET CONTEXT_INFO 0x0 --clears out the CONTEXT_INFO value
here is the portion of the trigger to retrieve the value:
DECLARE #string varchar(128)
,#sCONTEXT_INFO varchar(128)
SELECT #sCONTEXT_INFO=CAST(CONTEXT_INFO() AS VARCHAR) FROM master.dbo.SYSPROCESSES WHERE SPID=##SPID
IF LEFT(#sCONTEXT_INFO,9)='Procedure'
BEGIN
SET #string=RIGHT(RTRIM(#sCONTEXT_INFO),LEN(RTRIM(#sCONTEXT_INFO))-10)
END
ELSE
BEGIN --optional failure code
RAISERROR('string was not specified',16,1)
ROLLBACK TRAN
RETURN
END
..use the #string
Our system is already using the CONTEXT_INFO variable for another purpose so that is not available. I also tried the DBCC INPUTBUFFER solution which almost worked. The draw back to the inputbuffer is that it returns only the outside calling procedure. Ex: procA calls procB which fires a trigger. The trigger runs DBCC INPUTBUFFER which only shows procA. Since my trigger was looking for procB, this approach failed.
What I have done in the meantime is to create a staging table. Now procA calls procB. procB inserts a line in the staging table then fires the trigger. The trigger checks the staging table and finds the procB entry. Upon return procB deletes its entry from the staging table. It's a shell game but it works. I would be interested in any feedback on this.
I've not tried this but ##PROCID looks like it might return what you want.

Updating records from a XML

I need to provide 4 MySQL stored procedures for each table in a database. They are for get, update, insert and delete.
"Get", "delete" and "insert" are straightforward. The problem is "update", because I don't know which parameters will be set and which ones not. Some parameters could be set to NULL, and other simply won't change so they won't be provided.
As I'm already working with XML, after several search in Google I've found that is possible to use a function called UpdateXML, but the examples are too complex and some articles are from 2007. So I don't know if there is a better technique at this moment or something easier.
Any comment, documentation, link, article or whatever of something that you've used and you're happy with, will be well appreciated :D
Cheers.
Usually when you have data from a row in your database in the front-end, you should have all of the values that you might use to update that row in the database. You should pass all of those values into your update, regardless of whether or not they have actually changed. Otherwise, your database doesn't really know whether it's getting a NULL value for a column because that's what it's supposed to be or because you just didn't pass the real value along.
If you are going to have areas of the application where you don't need certain columns from a table, then it's possible to set up additional stored procedures that do not use those columns. It's often easier though to just retrieve all of the columns from the database when you fill your front-end object. The overhead of the extra columns is usually minimal and worth the saved maintenance of multiple update stored procedures.
Here's an example. It's MS SQL Server syntax, so you may have to alter it slightly, but hopefully it illustrates the idea:
CREATE PROCEDURE Update_My_Table
#my_table_id INT,
#name VARCHAR(40),
#description VARCHAR(500),
#some_other_col INT
AS
BEGIN
UPDATE
My_Table
SET
name = #name,
description = #description,
some_other_col = #some_other_col
WHERE
my_table_id = #my_table_id
END
CREATE PROCEDURE Update_My_Table_Limited
#my_table_id INT,
#name VARCHAR(40),
#description VARCHAR(500)
AS
BEGIN
UPDATE
My_Table
SET
name = #name,
description = #description
WHERE
my_table_id = #my_table_id
END
As you can see, just eliminate those columns that you're not updating from the UPDATE statement. Just don't go overboard and try to have a stored procedure for every possible combination of columns that you might want to update. It's much easier to just get the extra columns from the DB when you select from the table in the first place. You'll end up passing the same value back and your server will wind up updating the column with the same exact value, but that's not a big deal. You can code your front end to make sure that at least one column has changed before it will actually try to update anything in the database.