N S

Novick Software Management • Design • Programming • Training • Consulting

   

 

 

SQL Server T-SQL User-Defined Function of the Week

A Quick Peak at the New System UDF fn_get_sql

Volume 1 Number 14     February 18, 2003

Since this newsletter issue was published I've written a more 
extensive article, Find OUt What They're Doing with fn_get_sql,
published by Database Journal.  I've also created an interesting
video to accompany the article, which you might also enjoy.

Sign up for this newsletter at:
http://www.novicksoftware.com/UdfOfWeek/UDFofWeekSignup.htm
SQL Server 2000 Service Pack 3 (SP3)introduces a new system user-
defined function, fn_get_sql.  It's meant to make it easier for
developers and diagnostic tools to retrieve the text of the SQL 
begin run by currently active SQL processes.  This is a technique
commonly used when diagnosing a deadlock.

Before you go to far, you should be aware that there is an 
updated Books Online for SP3.  It has the new documentation for
fn_get_sql.  You can get it without installing SP3 at this link:
http://www.microsoft.com/sql/techinfo/productdoc/2000/books.asp
If you're not installing SP3, be careful as you install the
updated documentation. If you let it, the installer 
will overwrite your existing Books Online.  However, 
if you make the right selection, it can be 
installed side-by-side with the old BOL.

Prior to SP3 the only way to get the SQL being used by a 
SQL process is to use the DBCC INPUTBUFFER command.  It takes a
SPID as its argument and returns a rowset consisting of:

Column Name  Data type       Description
------------ ------------- -------------------------------------
EventType    nvarchar(30)  Event type such as "Language Event"
                           or "No Event" or "RPC"
Parameters   int           0 = text  or 1-n=parameters
EventInfo    nvarchar(255) For an RPC it contains the procedure
                           For a Language Event it contains the 
                           text of the SQL being executed.
                           
This query sort of gives you the idea:

DBCC INPUTBUFFER (@@SPID)
GO
(Results)
EventType      Parameters EventInfo                   
-------------- ---------- --------------------------- 
Language Event          0 DBCC INPUTBUFFER (@@SPID)
(End of results)

The data type of the EventInfo column is nvarchar(255).  This has
proven to be an annoying limitation because it restricts the 
results to the first 255 characters of any SQL Statement.  While
that might be enough when the statement is executing a stored
procedure it's often insufficient when a complex SELECT or 
UPDATE Is involved.

fn_get_sql overcomes the length restriction by returning the SQL
being executed as a text column.  Here's the layout of its
result set:

Column Name  Data type       Description
------------ ------------- -------------------------------------
dbid         smallint      Database ID
objectid     int           ID of the database object. NUll for 
                           ad hoc statements
number       smallint      The number of the group, if the 
                           procedures are grouped.
encrypted    bit           1=Encrypted   0=Not encrypted
text         Text          Text of the statement. NULL if the
                           object is encrypted.
                           
fn_get_sql takes a sql_handle as its parameter. This is a change
from DBCC INPUTBUFFER.  sql_handle is a BINARY(20) column in 
sysprocesses and it can be retrieved using the SPID as in:

/---------- Copy from below this line --------------------------\
DECLARE @handle binary(20)

SELECT @handle = sql_handle
    FROM master..sysprocesses
    WHERE spid = @@SPID

SELECT [text] 
    FROM ::fn_get_sql(@handle)
GO
\---------- Stop copying above this line -----------------------/
(Results)
text   
---------------------------------------------------------------
-- Retrieve the sql of this connection
DECLARE @handle binary(20)

SELECT @handle = sql_handle
    FROM master..sysprocesses
    WHERE spid = @@SPID

SELECT [text] 
    FROM ::fn_get_sql(@handle)
(End of results)

Don't get confused by the fact that the output is identical to
the query.  It's suppose to be the same. 

I was trying to figure out how to show fn_get_sql in a real world
scenario.  That would involve creating a deadlock and then using
sp_who do find out which processes are blocked and then using
fn_get_sql to retrieve the text of the SQL that they were 
executing.  The thought of sending out code that deliberately 
created a deadlock some how struck me as pretty risky.  I don't
want to be responsible for hanging anyone's database, so I've 
decided on to use a less than real world example.  I'm working
on a technique to show the real world example without the risk.
I'll update you as soon as it's ready.

The example below uses two Query Analyzer windows to run two 
scripts, A and B.  The instructions for how to run them is 
included in the text of the script.  You should run each batch 
of the Script A by itself by selecting the text of the batch up
to the GO command.  Once you've changed Script B with the SPID
from Script A, you can run it all at once.

/---------- Copy from below this line --------------------------\
-- Script A
-- This is script A in a two script demonstration of how the new
-- system user-defined function fn_get_sql works.  It's intended
-- to be used at the same time as its companion script B.

-- Start by dropping the Example procedure if it already exists.
if exists (select * 
              from information_schema.routines 
              where specific_name = 'usp_Example_Delayed_Query')
     BEGIN
     PRINT 'Dropping procedure usp_Example_Delayed_Query'
     drop proc usp_Example_Delayed_Query
     END 
ELSE BEGIN
     PRINT 'Procedure usp_Example_Delayed_Query does not exist.'
END -- endif
GO

-- Now replace the procedure.
CREATE PROC usp_Example_Delayed_Query
AS

WAITFOR DELAY '00:00:30'

SELECT a.au_lname, au_fname, t.title 
  FROM pubs..authors a
     INNER JOIN pubs..titleauthor ta
        ON a.au_id = ta.au_id
     INNER JOIN pubs..titles t
        ON ta.title_id = t.title_id
GO

-- Next, find out which SPID is used for this connection:
SELECT @@SPID as [my SPID]
GO


-- Stop here.

-- Get Script B opened in another Query Analyzer window.
-- put the SPID that was just selected into the place in 
-- Script B where it says to <- Replace SPID here
-- When it's ready to run, but not yet running, come back here 
-- and run the following query.  

EXEC usp_Example_Delayed_Query
GO
-- Once this query is running, go to the other window with 
-- script B and execute all of script B.
-- What you should see in the results window for script B is 
-- the text of the create procedure script for 
-- usp_Example_Delayed_Query

-- This is the end of Script A
\---------- Stop copying above this line -----------------------/

Now in a different Query Analyzer window copy script B.

/---------- Copy from below this line --------------------------\
-- Script B
--
-- This is script B in a two script demonstration of how the new
-- system user-defined function fn_get_sql works.  It's intended
-- to be used at the same time as its companion script A.

-- Start with script A and run each batch (up to each GO) until
-- you get to the one that selects the SPID.  Use the SPID from
-- script A to replace the number before  <- Replace SPID here

-- Retrieve the sql that Script A is running
DECLARE @sql_handle binary(20)

SELECT @sql_handle = sql_handle
    FROM master..sysprocesses
    WHERE spid = 51 <- Replace 51 with your SPID here
    -- When you've replaced the SPID it should be something
    -- like 51

SELECT [text] 
    FROM ::fn_get_sql(@sql_handle)
go -- The results should show the script being run in 
   -- Script A
   
-- This is the end of Script B
\---------- Stop copying above this line -----------------------/

The result of running Script B should be the text of the 
CREATE PROC script for usp_Example_Delayed_Query.  That's the 
SQL that Script A is running at the time that Script B is run.

I hope this all works for you.  I'm never sure that I've 
described how to go back and forth between the two scripts.  If
you're having a problem executing it, drop me and e-mail and 
I'll see if I can suggest something.
+--------------------------------------------------------------+
Do you have a great UDF that you'd like to share?  Or maybe you
have a T-SQL problem that you think could be solved by a UDF
but you don't know how? Send them to:

UDF@NovickSoftware.com 

and they might be published in this newsletter.  I try and 
respond to every request that I get.  

Thanks,  
Andrew Novick
+--------------------------------------------------------------+


RSS as HTML

Personal Blog

 
New Tips:

Loading SQL Profiler trace (.trc) files with fn_trace_gettrace

Upcoming
Presentations:

Big Data:
Working with Terabytes
in SQL Server

July 9 '08

Nov 19-21
SQL Pass 2008


Full Schedule