I want to run a job after a trigger each time a column changes (for each row in the table), I would like to run a job.
The job should wait for 5 minutes and then run a stored procedure. I have made something, but when running it, it looks like the whole database is being locked, and I don't want the database to be locked while there are thousand of requests at the same time.
CREATE TRIGGER AfterUPDATETrigger
ON [TmpTable]
FOR UPDATE
AS
DECLARE #EmpID INT, #EmpName VARCHAR(50),
SELECT #EmpID = ID FROM foo ;
SELECT #EmpName = Name FROM foo ;
IF UPDATE(TimeSpan)
BEGIN
EXEC io_sp_delete_reservation #EmpID
WAITFOR DELAY '00:05:00.000';
END
Related
Why not exists recompile option for trigger?
Suddenly the performance of one of our procedure (multiple SELECTs, multiple tables, insert into table) went from returning data in around 1 secs to 10-30secs.
After adding various debugging and logging we noticed that the performance would increase from the slow 10-30secs, back to sub-second speeds. (because alter trigger one of the table)
Just to clarify. The sequence of events:
Slow performance of Insert
Alter trigger table
Fast performance of Insert
I think slow performance associated with create wrong plan cash. because, before call insert command on the procedure, I write print datetime and the beginning of the the trigger command, add print datetime, so when call the procedure before alter trigger, The time difference between the first print and the second print is 20 sec, but when alter trigger, back to sub-second speeds. It should be noted that the commands in the trigger are not complicated
so, I need to add recompile option to trigger like procedure
it is trigger Script sample:
create trigger t_test on tbl AFTER insert
as
begin
begin try
declare #yearid int,
#id int
select #id = id,#yearid = yearid
from inserted
if exists(select * from FinancialYear where id = #yearid and flag = 0)
begin
raiserror('year not correct',16,1)
end
DECLARE #PublicNo BIGINT=(SELECT ISNULL(MAX(PublicNo),0)+1 FROM tbl)
update tbl
set PublicNo = #PublicNo
where #id
insert into tbl2
values (...)
end try
begin catch
print error_message()
end catch
end
I need to execute the rds_restore_database stored procedure from my own procedure, so I need to get the task_id just after execution to execute rds_task_status for awaiting the restoring is SUCCESS.
I don't know how to get the task_id in a variable after restoring:
exec msdb.dbo.rds_restore_database 'whatever', 'S3_bucket_bak'
and I don't know how to get the lifecycle column in other variable to check restoring status:
exec [msdb].[dbo].[rds_task_status] #task_id=#mytaskid
The only approach I tried is to insert into a temp table the result:
INSERT INTO #MyTempTable exec #task_id = msdb.dbo.rds_restore_database 'whatever', 'S3_bucket_bak'
But I have to drop and insert again whenever I want to check how is the restoring status.
There is any other solution?
The best workaround i've been able to find:
declare #my_task_id int
select top 1 #my_task_id = task_id from msdb.dbo.rds_fn_task_status(NULL,0) order by task_id desc
select #my_task_id task_id
Or, if what you want to do is the same as I, to halt code execution until all tasks are done, the following should work:
declare #sec int = 0;
WHILE ((SELECT top 1 lifecycle FROM msdb.dbo.rds_fn_task_status(NULL,0) order by task_id desc) <> 'SUCCESS')
BEGIN
WAITFOR DELAY '00:00:10';
set #sec = #sec + 10
if #sec >= 1800 RAISERROR ('Timout. Copying from S3 to RDS server took more than 30 minutes.',16,1)
END
It'd be a ton better if the stored procedures created by the rds team would just return the task_id. But they don't, so..
(The reason why I need the following are unimportant)
What I'd like to do is adjust the following so that it executes the stored procedure, which usually takes 30 minutes, but then the procedure stops after a set time of 60 seconds - effectively the same as if I am in SSMS running the procedure and press the cancel button after 60 seconds.
I don't want to reconfigure the whole db so that every other long running stored procedure times out after 30 seconds - only the specific procedure TESTexecuteLongRunningProc.
Here is the test procedure being called:
CREATE PROCEDURE [dbo].[TESTlongRunningProc]
AS
BEGIN
--placeholder that represents the long-running proc
WAITFOR DELAY '00:30:00';
END;
This is the proc I would like to adjust so it cancels itself after 60 seconds:
CREATE PROCEDURE [dbo].[TESTexecuteLongRunningProc]
AS
BEGIN
EXECUTE WH.dbo.TESTlongRunningProc;
-->>here I would like some code that cancels TESTexecuteLongRunningProc after 60 seconds
END;
Essentially you can create a separate process to watch the background for a specific tasks and metrics and kill if necessary. Lets start by implanting a tracking device into your code you wish to track. I used a comment block with a key phrase "Kill Me". You can place something similar in your original code
CREATE PROCEDURE TrackedToKill
-- EXEC TrackedToKill
/* Comment Block tracking device: Kill Me*/
AS
BEGIN
DECLARE #Counter bigint = 0
WHILE 1 = 1
BEGIN
SET #Counter = #Counter + 1
WAITFOR DELAY '00:00:30'
END
END
Then lets see if we can find the running sessions
SELECT session_id,
command,database_id,user_id,
wait_type,wait_resource,wait_time,
percent_complete,estimated_completion_time,
total_elapsed_time,reads,writes,text
FROM sys.dm_exec_requests
CROSS APPLY sys.dm_exec_sql_text (sys.dm_exec_requests.sql_handle)
WHERE text LIKE '%Kill Me%'
AND session_id <> ##SPID
OK Great, this should return sessions with your tracking device. We can then turn this into another stored procedure that will kill your processes based on the tracking device and any other criteria you might need. You can launch this manually or perhaps with the SQL agent at start up. Include as many additional criteria you need to make sure you limit the scope of what you're killing (ie; User, database, block or Processes that that haven't been rolled back already).
CREATE PROCEDURE HunterKiller
-- EXEC HunterKiller
AS
BEGIN
DECLARE #SessionToKill int
DECLARE #SQL nvarchar(3000)
WHILE 1=1
BEGIN
SET #SessionToKill = (SELECT TOP 1 session_id
FROM sys.dm_exec_requests
CROSS APPLY sys.dm_exec_sql_text (sys.dm_exec_requests.sql_handle)
WHERE session_id <> ##SPID
AND text LIKE '%Kill Me%'
AND total_elapsed_time >= 15000)
SET #SQL = 'KILL ' + CONVERT(nvarchar,#SessionToKill)
EXEC (#SQL)
WAITFOR DELAY '00:00:05'
END
END
Assuming you can use the SQL Server Agent, perhaps using the sp_start_job and sp_stop_job procedures could work for you.
This is untested and without any sort of warranty, and the parameters have been shortened for readability:
-- control procedure
declare #starttime DATETIME = SYSDATETIME()
exec msdb..sp_start_job 'Job' -- The job containing the target procedure that takes 30 minutes
while 1>0
BEGIN
-- Check to see if the job is still running and if it has been running long enough
IF EXISTS(
SELECT TOP 1 b.NAME
FROM msdb..sysjobactivity a
INNER JOIN msdb..sysjobs b
ON a.job_id = b.job_id
WHERE start_execution_date >= #starttime
AND stop_execution_date IS NULL
AND b.NAME in ('job')
and DATEDIFF(second,start_execution_date,SYSDATETIME()) >= 60
)
BEGIN
exec msdb..sp_stop_job 'Job'
END
waitfor delay '00:00:05';
END
I want to call stored procedure from a trigger,
how to execute that stored procedure after x minutes?
I'm looking for something other than WAITFOR DELAY
thanks
Have an SQL Agent job that runs regularly and pulls stored procedure parameters from a table - the rows should indicate also when their run of the stored procedure should occur, so the SQL Agent job will only pick rows that are due/slightly overdue. It should delete the rows or mark them after calling the stored procedure.
Then, in the trigger, just insert a new row into this same table.
You do not want to be putting anything in a trigger that will affect the execution of the original transaction in any way - you definitely don't want to be causing any delays, or interacting with anything outside of the same database.
E.g., if the stored procedure is
CREATE PROCEDURE DoMagic
#Name varchar(20),
#Thing int
AS
...
Then we'd create a table:
CREATE TABLE MagicDue (
MagicID int IDENTITY(1,1) not null, --May not be needed if other columns uniquely identify
Name varchar(20) not null,
Thing int not null,
DoMagicAt datetime not null
)
And the SQL Agent job would do:
WHILE EXISTS(SELECT * from MagicDue where DoMagicAt < CURRENT_TIMESTAMP)
BEGIN
DECLARE #Name varchar(20)
DECLARE #Thing int
DECLARE #MagicID int
SELECT TOP 1 #Name = Name,#Thing = Thing,#MagicID = MagicID from MagicDue where DoMagicAt < CURRENT_TIMESTAMP
EXEC DoMagic #Name,#Thing
DELETE FROM MagicDue where MagicID = #MagicID
END
And the trigger would just have:
CREATE TRIGGER Xyz ON TabY after insert
AS
/*Do stuff, maybe calculate some values, or just a direct insert?*/
insert into MagicDue (Name,Thing,DoMagicAt)
select YName,YThing+1,DATEADD(minute,30,CURRENT_TIMESTAMP) from inserted
If you're running in an edition that doesn't support agent, then you may have to fake it. What I've done in the past is to create a stored procedure that contains the "poor mans agent jobs", something like:
CREATE PROCEDURE DoBackgroundTask
AS
WHILE 1=1
BEGIN
/* Add whatever SQL you would have put in an agent job here */
WAITFOR DELAY '00:05:00'
END
Then, create a second stored procedure, this time in the master database, which waits 30 seconds and then calls the first procedure:
CREATE PROCEDURE BootstrapBackgroundTask
AS
WAITFOR DELAY '00:00:30'
EXEC YourDB..DoBackgroundTask
And then, mark this procedure as a startup procedure, using sp_procoption:
EXEC sp_procoption N'BootstrapBackgroundTask', 'startup', 'on'
And restart the service - you'll now have a continuously running query.
I had kind of a similar situation where before I processed the records inserted into the table with the trigger, I wanted to make sure all the relevant related data in relational tables was also there.
My solution was to create a scratch table which was populated by the insert trigger on the first table.
The scratch table had a updated flag, (default set to 0), and an insert get date() date field, and the relevant identifier from the main table.
I then created a scheduled process to loop over the scratch table and perform whatever process I wanted to perform against each record individually, and updating the 'updated flag' as each record was processed.
BUT, here is where I was a wee bit clever, in the loop over process looking for records in the scratch table that had a update flag = 0, I also added the AND clause of AND datediff(mi, Updated_Date, getdate())> 5. So the record would not actually be processed until 5 minutes AFTER it was inserted into the scratch table.
I wish to have a stored proc that is called every n seconds, is there a way to do this in SQL Server without depending on a separate process?
Use a timer and activation. No external process, continues to work after a clustering or mirroring failover, continues to work even after a restore on a different machine, and it works on Express too.
-- create a table to store the results of some dummy procedure
create table Activity (
InvokeTime datetime not null default getdate()
, data float not null);
go
-- create a dummy procedure
create procedure createSomeActivity
as
begin
insert into Activity (data) values (rand());
end
go
-- set up the queue for activation
create queue Timers;
create service Timers on queue Timers ([DEFAULT]);
go
-- the activated procedure
create procedure ActivatedTimers
as
begin
declare #mt sysname, #h uniqueidentifier;
begin transaction;
receive top (1)
#mt = message_type_name
, #h = conversation_handle
from Timers;
if ##rowcount = 0
begin
commit transaction;
return;
end
if #mt in (N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
, N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
begin
end conversation #h;
end
else if #mt = N'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer'
begin
exec createSomeActivity;
-- set a new timer after 2s
begin conversation timer (#h) timeout = 2;
end
commit
end
go
-- attach the activated procedure to the queue
alter queue Timers with activation (
status = on
, max_queue_readers = 1
, execute as owner
, procedure_name = ActivatedTimers);
go
-- seed a conversation to start activating every 2s
declare #h uniqueidentifier;
begin dialog conversation #h
from service [Timers]
to service N'Timers', N'current database'
with encryption = off;
begin conversation timer (#h) timeout = 1;
-- wait 15 seconds
waitfor delay '00:00:15';
-- end the conversation, will stop activating
end conversation #h;
go
-- check that the procedure executed
select * from Activity;
You can set up a SQL Agent job - that's probably the only way to go.
SQL Server Agent is a component of SQL Server - not available in the Express editions, however - which allows you to automate certain tasks, like database maintenance etc. but you can also use it to call stored procs every n seconds.
I once set up a stored procedure that ran continuously, uisng a loop with a WAITFOR at the end of it.
The WHILE condition depended upon the value read from a simple configuration table. If the value got set to 0, the loop would be exited and the procedure finished.
I put a WAITFOR DELAY at the end, so that however long it took to process a given iteration, it would wait XX seconds until it ran it again. (XX was also set in and read from the configuration table.)
If it must run at precise intervales (say, 0, 15, 30, and 45 seconds in the minute), you could calculate the appropriate WATIFOR TIME value at the end of the loop.
Lastly, I had the procedure called by a SQL Agent job once a minute. The job would always be "running" showing that the procedure was running. If the procedure was killed or crashed, the job would start it up in no more than 1 minute. If the procedure was "turned off", the procedure still gets run but the WHILE loop containing the processing does not get entered making the overhead nill.
I didn't much like having it in my database, but it fulfilled the business requirements.
WAITFOR
{
DELAY 'time_to_pass'
| TIME 'time_to_execute'
| [ ( receive_statement ) | ( get_conversation_group_statement ) ]
[ , TIMEOUT timeout ]
}
If you want to keep a SSMS query window open:
While 1=1
Begin
exec "Procedure name here" ;
waitfor delay '00:00:15';
End