I am using SQL Server 2012 and I have the following procedure
CREATE PROCEDURE A
AS
BEGIN
SELECT * FROM table1
DECLARE #v1 INT = 1
SELECT #v1
END
If select * from table1 command takes more than 3 seconds, I want to ignore it and stop the command and move to execute next command in the procedure.
How to handle that? I mean putting specific period for command execution
Have a look at ##LOCK_TIMEOUT - see: ##LOCK_TIMEOUT (Transact-SQL)
Related
I am working on a mutation test framework for SQL Server, for this I need to be able to calculate what lines of a stored procedure, function or trigger are executed when I execute a certain stored procedure.
The difficult part is that I want to know the exact lines or statements being executed from the stored procedure I call.
With a query like this I can see what stored procedures/triggers/functions are being executed, since I know when I call the stored procedure I can use the time to see if it was executed.
SELECT d.object_id, d.database_id,
OBJECT_NAME(object_id, database_id) AS proc_name,
MAX( d.last_execution_time) as last_execution_time,
OBJECT_DEFINITION(object_id) as definition
FROM sys.dm_exec_procedure_stats AS d
WHERE d.database_id = DB_ID()
GROUP BY d.object_id, d.database_id,
OBJECT_NAME(object_id, database_id)
How would I find the lines/statements that have been executed, I also have to know inside what stored procedure/trigger/function the lines/statements exists and in which shema this is. I have to take into account that a IF/ELSE statement may be used.
With this data I can do 2 important things:
generate a code coverage report
optimize what lines to mutate, since I dont have to mutate uncovered lines.
A possible, but not a very nice, solution would be to automaticly change stored procedures to add a line that inserts the previous line into a table, but this will require splitting up the procedure into statements, which I don't know how to do.
Please note that I cannot change the code users want to test with my framework. I can search for patterns and replace but manually changing procedures is NOT a option.
EDIT:
Lets redifine this question: How to split a stored procedure definition into its different statements in a way that does not depend on code style?
and How to add a new statement in between found statements?
EDIT: in the SO post SQL Server: How to parse code into its different statements I have found a way to trace statement execution, but I can't filter it yet.
So the extended events are the solution, this is how I have done it:
IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name='testMSSQLTrace')
DROP EVENT SESSION testMSSQLTrace ON SERVER;
DECLARE #cmd VARCHAR(MAX) = '';
SELECT #cmd = 'CREATE EVENT SESSION testMSSQLTrace
ON SERVER
ADD EVENT sqlserver.sp_statement_completed
(WHERE (sqlserver.database_name = N''' + DB_NAME() + '''))
ADD TARGET package0.ring_buffer
WITH (
MAX_MEMORY = 2048 KB,
EVENT_RETENTION_MODE = NO_EVENT_LOSS,
MAX_DISPATCH_LATENCY = 3 SECONDS,
MAX_EVENT_SIZE = 0 KB,
MEMORY_PARTITION_MODE = NONE,
TRACK_CAUSALITY = OFF,
STARTUP_STATE = OFF
);'
EXEC (#cmd)
This creates an event that can be fired after every statement completion, this is done dynamicly to filter on the database
Then I have 3 procedures that make controlling this event easy
/*******************************************************************************************
Starts the statement trace
*******************************************************************************************/
CREATE OR ALTER PROC testMSSQL.Private_StartTrace
AS
BEGIN
ALTER EVENT SESSION testMSSQLTrace
ON SERVER
STATE = START;
END
GO
/*******************************************************************************************
Ends the statement trace, this also clears the trace
*******************************************************************************************/
CREATE OR ALTER PROC testMSSQL.Private_StopTrace
AS
BEGIN
ALTER EVENT SESSION testMSSQLTrace
ON SERVER
STATE = STOP;
END
GO
/*******************************************************************************************
Saves the statements trace
*******************************************************************************************/
CREATE OR ALTER PROC testMSSQL.Private_SaveTrace
AS
BEGIN
DECLARE #xml XML;
SELECT #xml = CAST(xet.target_data AS xml)
FROM sys.dm_xe_session_targets AS xet INNER JOIN sys.dm_xe_sessions AS xe ON (xe.address = xet.event_session_address)
WHERE xe.name = 'testMSSQLTrace'
INSERT INTO testMSSQL.StatementInvocations (testProcedure, procedureName, lineNumber, statement)
SELECT testMSSQL.GetCurrentTest(),
OBJECT_NAME(T.c.value('(data[#name="object_id"]/value)[1]', 'int')),
T.c.value('(data[#name="line_number"]/value)[1]', 'int'),
T.c.value('(data[#name="statement"]/value)[1]', 'VARCHAR(900)')
FROM #xml.nodes('RingBufferTarget/event') T(c)
WHERE T.c.value('(data[#name="nest_level"]/value)[1]', 'int') > 3
END
GO
These procedures respectivly start and stop the trace and the last one stores the result in a table where it filters on the nest level so my own code is not traced.
Finally I use it a bit like this:
start trace
start tran/savepoint
run SetUp (users code)
run test (users code)
save trace
save trace to variable
rollback tran (also catch errors and stuff like that)
save variable back to table so the trace is not rolled back
Special thanks to #Jeroen Mosterd for originally coming up with a proposal for this solution in this SQL Server: How to parse code into its different statements SO post
You can either:
Add a #DEBUG parameter to each stored procedure you call, or
Log everything you want, or
Only log when you want.
With the #Debug parameter, you can default it to OFF, then call it with ON when you want to trace your statements, with the following code:
IF (#Debug = 1) PRINT 'your tracing information goes here';
If you want to log everything, create a log table and insert a row into it wherever you need to know which statement was executed, such as:
DECLARE #log AS TABLE (msg VARCHAR(MAX));
and
INSERT INTO #log VALUES('your tracing information goes here');
Or you can combine them:
IF (#Debug = 1) INSERT INTO #log VALUES('your tracing information goes here');
Of course these will affect performance even when you don't output/log.
I want to do something like this:
CREATE OR ALTER PROC myPROC
#myInput NVARCHAR(MAX)
AS
BEGIN
SELECT *
FROM myTABLE
-- I want this next command to run not to be stored for later execution.
EXEC RegisterProcSomewhere
END
GO
Is there a preferred way to do this? Essentially I want to collect metadata on my procedure when I CREATE/ALTER them.
Call it outside the proc to execute it immediately.
CREATE OR ALTER PROC myPROC
#myInput NVARCHAR(MAX)
AS
BEGIN
SELECT * FROM myTABLE
-------------------------------
END
GO
--I want this next command to run not to be stored for later execution.
EXEC RegisterProcSomewhere
(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
This is probably a simple question, but I need the act of running a report to have a "pre-event" of triggering a stored procedure. I am NOT returning data from the procedure, it is updating 2 tables in a data warehouse by doing a BULK INSERT from .csv files that have been exported from an ISAM database. The report itself uses a separate query to pull from the SQL Server tables, but the imported data is ultimately used by multiple reports so the tables need to be actually updated.
The stored procedure will run nightly as part of a regular routine, but the data affecting this particular report will be updated by users and a new .csv extract created immediately before running the report, so the report needs to fire the stored procedure to update the tables before it queries those tables itself.
I've tried searching but all the references I find seem to focus on using a stored procedure as the report query, and that's not what I'm trying to accomplish. I have a separate query for pulling data, I need to run the stored procedure in-addition-to and preceding the report query, if that makes sense.
Does anybody know how to trigger a stored procedure as the opening line(s) of my report query?
Thanks in advance for any ideas. I'm not a SQL programmer (or any kind of programmer, really) so please be fairly specific with your advice... high-level concepts that assume any existing base of knowledge on my part will probably be lost on me.
This is the stored procedure (dbo.KCSI.DataUpdate) I wrote if that helps...
--To run as a script (query) the following 2 lines should be un-commented (there are 3 of these 'run-as-a-script' comments to find)
--USE KCSI
--Go
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- To run as a script (query) the following 3 lines should all be commented out
CREATE PROCEDURE DataUpdate
AS
BEGIN
SET NOCOUNT ON
-- Declare all the needed variables.
DECLARE #CustFile varchar(255)
DECLARE #CustFile_Exists int
DECLARE #HistFile varchar(255)
DECLARE #HistFile_Exists int
DECLARE #dt varchar(30)
DECLARE #NewCustName varchar(250)
DECLARE #NewHistName varchar(250)
-- Sets Boolean value for whether or not each file exists, using T-SQL extended (i.e. DOS Shell) command
SELECT #CustFile='C:\transfer\ecallcust.csv'
EXEC Master.dbo.xp_fileexist #CustFile, #CustFile_Exists OUT
SELECT #HistFile='C:\transfer\ecallhist.csv'
EXEC Master.dbo.xp_fileexist #HistFile, #HistFile_Exists OUT
-- Sets a date variable to append to the final file name
SELECT #dt = REPLACE(Convert(varchar(30),getdate(),120),':','_')
-- Sets a variable to hold the final name. Variable use required because of the hybrid nature of the name (dos shell command + SQL variable)
SET #NewCustName = 'RENAME C:\transfer\history\ecallcust2.csv "ecallcust_'+#dt+'.csv"'
SET #NewHistName = 'RENAME C:\transfer\history\ecallhist2.csv "ecallhist_'+#dt+'.csv"'
-- Subroutine runs only if ecallcust.csv is present
IF #CustFile_Exists = 1
BEGIN
--Zaps the table
TRUNCATE TABLE custextract
-- Initially renames the file, using T-SQL extended (i.e. DOS Shell) command
EXEC master.dbo.xp_cmdshell 'RENAME C:\transfer\ecallcust.csv ecallcust2.csv'
-- Update table from CSV file
BULK INSERT custextract
FROM 'c:\transfer\ecallcust2.csv'
WITH (
ROWTERMINATOR='\n'
)
-- Move file to the history directory and rename it to include the date-time stamp using T-SQL extended (i.e. DOS Shell) command
EXEC master.dbo.xp_cmdshell 'MOVE C:\transfer\ecallcust2.csv C:\transfer\history\'
EXEC master.dbo.xp_cmdshell #NewCustName
END
-- Subroutine runs only if ecallhist.csv is present
IF #HistFile_Exists = 1
BEGIN
--Zaps the table
TRUNCATE TABLE histextract
-- Initially renames the file, using T-SQL extended (i.e. DOS Shell) command
EXEC master.dbo.xp_cmdshell 'RENAME C:\transfer\ecallhist.csv ecallhist2.csv'
-- Update table from CSV file
BULK INSERT histextract
FROM 'c:\transfer\ecallhist2.csv'
WITH (
ROWTERMINATOR='\n'
)
-- Move file to the history directory and rename it to include the date-time stamp using T-SQL extended (i.e. DOS Shell) command
EXEC master.dbo.xp_cmdshell 'MOVE C:\transfer\ecallhist2.csv C:\transfer\history\'
EXEC master.dbo.xp_cmdshell #NewHistName
END
-- To run as a script (query) the following line should be commented out
END
GO
and the report query...
WITH OrderedYTD AS
(
SELECT custextract.*, histextract.*,
ROW_NUMBER () OVER (PARTITION BY custextract.custcustno ORDER BY histextract.salesytd desc) AS RowNumber
FROM custextract
INNER JOIN histextract
ON custextract.custcustno = histextract.histcustno
WHERE (custextract.ecall = 'Y')
)
SELECT OrderedYTD.*
FROM OrderedYTD
WHERE RowNumber <= 10;
Create one stored procedure, that first updates the data and then returns the refreshed data to be loaded by the report...
CREATE PROCEDURE DataSelect
AS
BEGIN
-- Refresh Data Here
EXEC DataUpdate
-- Select Data for Report
WITH OrderedYTD AS
(
SELECT custextract.*, histextract.*,
ROW_NUMBER () OVER (PARTITION BY custextract.custcustno ORDER BY histextract.salesytd desc) AS RowNumber
FROM custextract
INNER JOIN histextract
ON custextract.custcustno = histextract.histcustno
WHERE (custextract.ecall = 'Y')
)
SELECT OrderedYTD.*
FROM OrderedYTD
WHERE RowNumber <= 10;
END
Consider this stored procedure. The actual content of procedure doesn't matter, I am using it only for the sake of the example:
CREATE PROCEDURE [dbo].[temp]
#value1 varchar(50),
#value2 varchar(50),
#value3 varchar(50)
as
begin
Select *
From valuesTable (nolock)
inner join valuesTable2 RL (nolock)
on (ValuesTable.ID = RL.RuleId and RL.Type = 'Something')
where #value1 = ValuesTable.RuleVal02
and cast(#value2 as float) > cast(ValuesTable.RuleVal03 as float) and cast(#value2 as float) < cast(ValuesTable.RuleVal04 as float)
--and (#value3 = ValuesTable.RuleVal05)
and (#value3 = ValuesTable.RuleVal05 or ValuesTable.RuleVal05 = -1)
end
Now imagine that this (not very complex function) isn't working. I already know how to debug it both from Visual Studio and from SQL Server Management Studio, however, both cases are lacking:
The main failing point of this function is the big query that it executes. What I'd like to do then, is to take this query, copy it to a new query window and start executing it and debug it by modifying its various parts.
The basic way to debug that query would be copy it to a new query window, get the parameters from the executing code and then manually replace all the #variables with their actual value. That works, but it seems like a very unnecessary work to me and ideally I'd like to get the query, as it is executed on the server - With literal values instead of the parameters, e.g:
where 'actualValue' = ValuesTable.RuleVal02
and cast(4.2 as float) > cast(ValuesTable.RuleVal03 as float) and cast(4.2 as float) < cast(ValuesTable.RuleVal04 as float)...
Since it sounded like something I can only achieve from a profiler, I launched it. Then I configured the events to capture SP:StmtStarting event, to see the statements executed from stored procedures. To my surprise, however, the statements that I see in the profiler, still show with their parameter and not the actual literal value.
Is a way I could easily copy the actual query that is executed in a stored procedure with the parameters replaced to the literal value of the parameter? Is there a flag in the profiler to do so? Or is there another way?
Try this simple workflow (requires 5 seconds):
Run SSMS.
Find your stored procedure in Object Explorer window.
Right click on it to display context menu and select 'Execute Stored Procedure...' from it.
Set ALL parameters values in the form displayed.
SSMS generates the script for you in new query tab (see script at the end of this answer).
Save this script for further use.
Run Debug -> Start Debugging this script and then Step Into EXEC statement.
Change parameter values and run next debug.
DECLARE #return_value int
EXEC #return_value = [dbo].[temp]
#value1 = N'1',
#value2 = N'2',
#value3 = N'3'
SELECT 'Return Value' = #return_value
GO
you could write your query in a variable, with placeholders instead of actual values, replace the placeholders at runtime and execute it with sp_executesql
obviously it's just a thing you should do for debug purposes and not in production ;)
here's an example: http://sqlfiddle.com/#!3/c8c43/5
there you have your query filled with actual content, inside the variable