I'm hoping to find a way to sniff out potentially inefficient T-SQL within stored procedures, in this case detecting not just cursors in stored procedures, but preferably nested cursors.
With the script below based on sys.dm_sql_referenced_entities, from a given starting stored procedure I can see a recursive downstream call stack, including a column indicating whether the text CURSOR was found within the procedure definition.
This is helpful, but it isn't capable of telling me:
whether more than one cursor exists within a procedure
whether nested cursors are used (and of course, the truly perfect solution for that would also have to detect a call to stored procedure containing a cursor, from within a cursor)
Being able to do this I think is probably beyond the abilities of querying sys tables, and involves parsing the SQL itself - does anyone know of a technique or tool that could accomplish this, or perhaps an entirely different approach that could tell me the same information.
DECLARE #procname varchar(30)
SET #procname='dbo.some_root_procedure_name'
;WITH CTE([DB],[OBJ],[INDENTED_OBJ],[SCH],[lvl],[indexof_cursor],[referenced_object_definition])
AS
(
SELECT referenced_database_name AS [DB],referenced_entity_name AS [OBJ],
cast(space(0) + referenced_entity_name as varchar(max)) AS [INDENTED_OBJ],
referenced_schema_name AS [SCH],0 AS [lvl]
,charindex('cursor',object_definition(referenced_id)) as indexof_cursor
,object_definition(referenced_id) as [referenced_object_definition]
FROM sys.dm_sql_referenced_entities(#procname, 'OBJECT')
INNER JOIN sys.objects as o on o.object_id=OBJECT_ID(referenced_entity_name)
WHERE o.type IN ('P','FN','IF','TF')
UNION ALL
SELECT referenced_database_name AS [DB],referenced_entity_name AS [OBJ],
cast(space(([lvl]+1)*2) + referenced_entity_name as varchar(max)) AS [INDENTED_OBJ],
referenced_schema_name AS [SCH],[lvl]+1 as [lvl]
,charindex('cursor',object_definition(referenced_id)) as indexof_cursor
,object_definition(referenced_id) as [referenced_object_definition]
FROM CTE as c CROSS APPLY
sys.dm_sql_referenced_entities(c.SCH+'.'+c.OBJ, 'OBJECT') as ref
INNER JOIN sys.objects as o on o.object_id=OBJECT_ID(referenced_entity_name)
WHERE o.type IN ('P','FN','IF','TF') and ref.referenced_entity_name NOT IN (c.OBJ) -- Exit Condition
)
SELECT
*
FROM CTE
EDIT: I am marking this as "solved" even though I think some improvements could be made to the below solution - I think it is "good enough" for most scenarios, but I think a fully recursive solution that can traverse an "infinitely" deep call chain is possible.
Maybe there is a more efficient way, but you could search the procedure code. It's not foolproof though in that it could get some false positives, but you shouldn't miss any. It doesn't ignore comments and variable names so it's quite possible to pick up some extra stuff.
SELECT name, xtype, colid, text
into #CodeBlocks
FROM dbo.sysobjects left join .dbo.syscomments
ON dbo.sysobjects.id = .dbo.syscomments.id
where xtype = 'P'
order by 1
SELECT name,
(SELECT convert(varchar(max),text)
FROM #CodeBlocks t2
WHERE t1.name = t2.name
ORDER BY t2.colid
FOR XML PATH('')
) text
into #AllCode
FROM #CodeBlocks t1
GROUP BY name
select #AllCode.name,
case when InterProc.name is not null then
'Possible Inter-Proc Nesting'
when #AllCode.text like '%CURSOR%FOR%CURSOR%FOR%DEALLOCATE%DEALLOCATE%' then
'Possible Nested Cursor'
when #AllCode.text like '%CURSOR%FOR%CURSOR%FOR%' then
'Possible Multiple Cursor Used'
ELSE
'Possible Cursor Used'
end
from #AllCode
left join #AllCode InterProc
on InterProc.text like '%CURSOR%FOR%'
and #AllCode.text like '%CURSOR%FOR%' + InterProc.name + '%DEALLOCATE%'
where #AllCode.text like '%CURSOR%FOR%'
I found a few nested cursors on our server I didn't know about. Interesting. :)
Related
I am investigating a problem with the execution speed of an inline table function in SQL Server. Or that's where I thought the problem lay. I came across
T-SQL code is extremely slow when saved as an Inline Table-valued Function
which looked promising, since it described what I was seeing, but I seemed to have the opposite problem - when I passed variables to my function, it took 17 seconds, but when I ran the code of my function in a query window, using DECLARE statements for the variables (which I thought effectively made them literals), it ran in milliseconds. Same code, same parameters - just wrapping them up in an inline table function seemed to drag it way down.
I tried to reduce my query to the minimum possible code that still exhibited the behaviour. I am using numerous existing inline table functions (all of which have worked fine for years), and managed to strip my code down to needing just a call of one existing inline table function to be able to highlight the speed difference. But in doing so I noticed something very odd
SELECT strStudentNumber
FROM dbo.udfNominalSnapshot('2019', 'REG')
takes 17 seconds whereas
DECLARE #strAcademicSessionStart varchar(4) = '2019'
DECLARE #strProgressCode varchar(12)= 'REG'
SELECT strStudentNumber
FROM dbo.udfNominalSnapshot(#strAcademicSessionStart, #strProgressCode)
takes milliseconds! So nothing to do with wrapping the code in an inline table function, but everything to do with how the parameters are passed to a nested function within it. Based on the cited article I'm guessing there are two different execution plans in play, but I have no idea why/how, and more importantly, what I can do to persuade SQL Server to use the efficient one?
P.S. here is the code of the inner UDF call in response to a comment request
ALTER FUNCTION [dbo].[udfNominalSnapshot]
(
#strAcademicSessionStart varchar(4)='%',
#strProgressCode varchar(10)='%'
)
RETURNS TABLE
AS
RETURN
(
SELECT TOP 100 PERCENT S.strStudentNumber, S.strSurname, S.strForenames, S.strTitle, S.strPreviousSurname, S.dtmDoB, S.strGender, S.strMaritalStatus,
S.strResidencyCode, S.strNationalityCode, S.strHESAnumber, S.strSLCnumber, S.strPreviousSchoolName, S.strPreviousSchoolCode,
S.strPreviousSchoolType,
COLLEGE_EMAIL.strEmailAddress AS strEmailAlias,
PERSONAL_EMAIL.strEmailAddress AS strPersonalEmail,
P.[str(Sub)Plan], P.intYearOfCourse, P.strProgressCode,
P.strAcademicSessionStart, strC2Knumber AS C2K_ID, AcadPlan, strC2KmailAlias
,ISNULL([strC2KmailAlias], [strC2Knumber]) + '#c2kni.net' AS strC2KmailAddress
FROM dbo.tblStudents AS S
LEFT JOIN
dbo.udfMostRecentEmail('COLLEGE') AS COLLEGE_EMAIL ON S.strStudentNumber = COLLEGE_EMAIL.strStudentNumber
LEFT JOIN
dbo.udfMostRecentEmail('PERSONAL') AS PERSONAL_EMAIL ON S.strStudentNumber = PERSONAL_EMAIL.strStudentNumber
INNER JOIN
dbo.udfProgressHistory(#strAcademicSessionStart) AS P ON S.strStudentNumber = P.strStudentNumber
WHERE (P.strProgressCode LIKE #strProgressCode OR (SUBSTRING(#strProgressCode, 1, 1) = '^' AND P.strProgressCode NOT LIKE SUBSTRING(#strProgressCode, 2, LEN(#strProgressCode)))) AND
(P.strStudentNumber NOT IN
(SELECT strStudentNumber
FROM dbo.tblPilgrims
WHERE (strAcademicSessionStart = #strAcademicSessionStart) AND (strScheme = 'BEI')))
ORDER BY P.[str(Sub)Plan], P.intYearOfCourse, S.strSurname
)
Expanding on #Ross Pressers comment, this might not really be an answer, but demonstrates what is happening (a bit), with my understanding (which could be wrong!) of what is happening...
Run the setup code at the end and then....
Execute the following with query plan on (Ctrl-M)... (note: depending on the random number generator you may or may not get any results, that does not affect the plan)
declare #one varchar(100) = '379', #two varchar(200) = '726'
select * from wibble(#one, #two) -- 1
select * from wibble('379', '726') -- 2
select * from wibble(#one, #two) OPTION (RECOMPILE) -- 3
select * from wibble(#one, #two) -- 4
Caveat. The following is what happens on MY system, your mileage may vary...
-- 1 (and -- 4) are the most expensive.
SQL Server creates a generic plan as it does not know what the parameters are (yes they are defined, but the plan is for wibble(#one, #two) where, at that point, the parameter values are "unknown")
https://www.brentozar.com/pastetheplan/?id=rJtIRwx_r
-- 2 has a different plan
Here, sql server knows what the parameters are, so can create a specific plan, which is quite different to --1
https://www.brentozar.com/pastetheplan/?id=rJa9APldS
-- 3 has the same plan as --2
Testing this further, adding OPTION (RECOMPILE) gets SQL Server to create a specific plan for the specific execution of wibble(#one, #two) so we get the same plan as --2
--4 is there for completeness to show that after all that mucking about the generic plan is still in place
So, in this simple example we have a parameterised TVF being called with identical values, that are passed either as parameters or inline, producing different execution plans and different execution times as per the OP
Set up
use tempdb
GO
drop table if EXISTS Orders
GO
create table Orders (
OrderID int primary key,
UserName varchar(50),
PhoneNumber1 varchar(50),
)
-- generate 300000 with randon "phone" numbers
;WITH TallyTable AS (
SELECT TOP 300000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS [N]
FROM dbo.syscolumns tb1,dbo.syscolumns tb2
)
insert into Orders
select n, 'user' + cast(n as varchar(10)), cast(CRYPT_GEN_RANDOM(3) as int)
FROM TallyTable;
GO
drop function if exists wibble
GO
create or alter function wibble (
#one varchar(4) = '%'
, #two varchar(4) = '%'
)
returns table
as
return select * from Orders
where PhoneNumber1 like '%' + #one + '%'
and PhoneNumber1 like '%' + #two + '%'
or (SUBSTRING(#one, 1, 1) = '^' AND PhoneNumber1 NOT LIKE SUBSTRING(#two, 2, LEN(#two)))
and (select 1) = 1
GO
Problem was overcome (I wouldn't say "fixed") by following up on Ross Presser's observation about the complexity of udfProgressHistory. This sucked data from a table tblProgressHistory which was joined to itself. The table is added to annually. I think this year's additional 2K records must have caused the sudden cost-hike when using a particular execution plan. I deleted >2K redundant records and we're back to sub-second execution.
I need to store the text of all of the stored procedures in a database into an XML data type. When I use, FOR XML PATH, the text within in the stored procedure contains serialized data characters like
and
for CRLF and ", etc. I need the text to stored in the xml structure without these characters because the text will need to be used to recreate the stored procedure.
This is the query that I use for FOR XML PATH:
SELECT
[View].name AS "#VName", [Module].definition AS "#VDefinition"
FROM
sys.views AS [View]
INNER JOIN
sys.sql_modules AS [Module] ON [Module].object_id = [View].object_id
FOR XML PATH ('View'), TYPE
I read that I should use CDATA for the text using FOR XML EXPLICIT. However, the output of the when I run the following query and view the XML data, it contains those characters also. I need the text to be in plain text without these characters.
This is my query:
SELECT
1 AS Tag,
0 AS Parent,
NULL AS [Database1!1],
NULL AS [StoredProcedure!2!VName],
NULL AS [StoredProcedure!2!cdata]
UNION ALL
SELECT
2 AS Tag,
1 AS Parent,
NULL,
[StoredProcedure].name as [StoredProcedure!2!!CDATA],
[Module].definition as [StoredProcedure!2!!CDATA]
FROM
sys.procedures AS [StoredProcedure]
INNER JOIN
sys.sql_modules [Module] ON [StoredProcedure].object_id = [Module].object_id
WHERE
[StoredProcedure].name NOT LIKE '%diagram%'
FOR XML EXPLICIT
How can I store the text of a the stored procedures that is in plain text? Or when I parse the xml data type to recreate the stored procedure can I deserialize it so that it does not have those characters?
Ideally, I would like to use FOR XML PATH but if that is not possible I will use FOR XML EXPLICIT.
If you want to store data with special characters within XML, there are two options (plus a joke option)
escaping
CDATA
just to mention: Convert everything to base64 or similar would work too :-)
The point is: You do not need this!
The only reason for CDATA (at least for me) is manually created content (copy'n'paste or typing). Whenever you build your XML automatically, you should rely on the implicitly applied escaping.
Why does it bother you, how the data is looking within the XML?
If you read this properly (not with SUBSTRING or other string based methods), you will get it back in the original look.
Try this:
DECLARE #TextWithSpecialCharacters NVARCHAR(100)=N'€ This is' + CHAR(13) + 'strange <ups, angular brackets! > And Ampersand &&&';
SELECT #TextWithSpecialCharacters FOR XML PATH('test');
returns
€ This is
strange <ups, angular brackets! > And Ampersand &&&
But this...
SELECT (SELECT #TextWithSpecialCharacters FOR XML PATH('test'),TYPE).value('/test[1]','nvarchar(100)');
...returns
€ This is
strange <ups, angular brackets! > And Ampersand &&&
Microsoft decided not even to support this with FOR XML (except EXPLICIT, which is a pain in the neck...)
Read two related answers (by me :-) about CDATA)
https://stackoverflow.com/a/38547537/5089204
https://stackoverflow.com/a/39034049/5089204 (with further links...)
When I use, FOR XML PATH, the text within in the stored procedure contains serialized data characters like
and
for CRLF and ", etc.
Yes, because that's how XML works. To take a clearer example, suppose your sproc contained this text:
IF #someString = '<' THEN
then to store it in XML, there must be some kind of encoding applied, since you can't have a bare < in the middle of your XML (I hope you can see why).
The real question is then not 'how do I stop my text being encoded when I store it as XML', but rather (as you guess might be the case):
Or when I parse the xml data type to recreate the stored procedure can I deserialize it so that it does not have those characters?
Yes, this is the approach you should be looking at.
You don't how us how you're getting your text out of the XML at the moment. The key thing to remember is that you can't (or rather shouldn't) treat XML as 'text with extra bits' - you should use methods that understand XML.
If you're extracting the text in T-SQL itself, use the various XQuery options. If in C#, use any of the various XML libraries. Just don't do a substring operation and expect that to work...
An example, if you are extracting in T-SQL:
DECLARE #someRandomText nvarchar(max) = 'I am some arbitrary text, eg a sproc definition.
I contain newlines
And arbitrary characters such as < > &
The end.';
-- Pack into XML
DECLARE #asXml xml = ( SELECT #someRandomText FOR XML PATH ('Example'), TYPE );
SELECT #asXml;
-- Extract
DECLARE #textOut nvarchar(max) = ( SELECT #asXml.value('.', 'nvarchar(max)') ) ;
SELECT #textOut;
But you can find many many tutorials on how to get values out of xml-typed data; this is just an example.
SELECT
1 as Tag,
0 as Parent,
[View].name AS 'StoredProcedure!1!Name',
[Module].definition AS 'StoredProcedure!1!Definition!cdata'
FROM sys.views AS [View]
INNER JOIN sys.sql_modules AS [Module] ON [Module].object_id = [View].object_id
FOR XML EXPLICIT
Sample of the output from Adventureworks2012:
<StoredProcedure Name="vStoreWithContacts">
<Definition><![CDATA[
CREATE VIEW [Sales].[vStoreWithContacts] AS
SELECT
s.[BusinessEntityID]
,s.[Name]
,ct.[Name] AS [ContactType]
,p.[Title]
,p.[FirstName]
,p.[MiddleName]
,p.[LastName]
,p.[Suffix]
,pp.[PhoneNumber]
,pnt.[Name] AS [PhoneNumberType]
,ea.[EmailAddress]
,p.[EmailPromotion]
FROM [Sales].[Store] s
INNER JOIN [Person].[BusinessEntityContact] bec
ON bec.[BusinessEntityID] = s.[BusinessEntityID]
INNER JOIN [Person].[ContactType] ct
ON ct.[ContactTypeID] = bec.[ContactTypeID]
INNER JOIN [Person].[Person] p
ON p.[BusinessEntityID] = bec.[PersonID]
LEFT OUTER JOIN [Person].[EmailAddress] ea
ON ea.[BusinessEntityID] = p.[BusinessEntityID]
LEFT OUTER JOIN [Person].[PersonPhone] pp
ON pp.[BusinessEntityID] = p.[BusinessEntityID]
LEFT OUTER JOIN [Person].[PhoneNumberType] pnt
ON pnt.[PhoneNumberTypeID] = pp.[PhoneNumberTypeID];
]]></Definition>
</StoredProcedure>
<StoredProcedure Name="vStoreWithAddresses">
<Definition><![CDATA[
CREATE VIEW [Sales].[vStoreWithAddresses] AS
SELECT
s.[BusinessEntityID]
,s.[Name]
,at.[Name] AS [AddressType]
,a.[AddressLine1]
,a.[AddressLine2]
,a.[City]
,sp.[Name] AS [StateProvinceName]
,a.[PostalCode]
,cr.[Name] AS [CountryRegionName]
FROM [Sales].[Store] s
INNER JOIN [Person].[BusinessEntityAddress] bea
ON bea.[BusinessEntityID] = s.[BusinessEntityID]
INNER JOIN [Person].[Address] a
ON a.[AddressID] = bea.[AddressID]
INNER JOIN [Person].[StateProvince] sp
ON sp.[StateProvinceID] = a.[StateProvinceID]
INNER JOIN [Person].[CountryRegion] cr
ON cr.[CountryRegionCode] = sp.[CountryRegionCode]
INNER JOIN [Person].[AddressType] at
ON at.[AddressTypeID] = bea.[AddressTypeID];
]]></Definition>
As you note there are no
/
/ "/ etc and NewLine characters is represented as new line
I have a fairly large amount of sql server objects that I would like to batch deploy. Each object (table, view, procedure, etc) is in it's own file.
The problem is, many of the objects have interdependencies, so the order of creation is important, otherwise errors will occur.
Currently, I am deploying using a dos batch file that calls a controlling script into which I have manually specified the order of script execution, like so:
BATCH FILE:
SQLCMD -S %SERVER_NAME% -d %DATABASE_NAME% -i "DeployProcedures.sql"
SQL Script (DeployProcedures.sql):
:r view1.sql
:r view2.sql
:r view3.sql
etc
:r proc1.sql
:r proc2.sql
:r proc1.sql
etc
This works, but it is cumbersome to have to constantly keep this up to date.
Is there any other way of doing this? I think I would even be happy with running the deploy 4 times, with suppressed or "do not fail" on errors for the first 3 iterations, and then only enable terminate on errors for the final iteration.
I would rather something self-authored rather than a commercial product like: http://solutioncenter.apexsql.com/re-order-script-to-avoid-dependency-based-errors/
EDIT: downvotes on a question regarding a problem for which someone actually bothered to go through the trouble to write a commercial application - sigh.
sys.sql_expression_dependencies is your friend.
An example:
-- Show all the things that all objects depend on
SELECT o.name AS [Object], o.[type] AS [ObjectType], t.name AS [DependsOn], t.type AS [DependentObjectType]
FROM sys.objects o
INNER JOIN sys.sql_expression_dependencies d ON o.object_id = d.referencing_id
INNER JOIN sys.objects t ON d.referenced_id = t.object_id
ORDER BY o.name, t.name
Basically, you can generate your file that runs the script in order based on the dependencies you find from this query. It'll take some massaging for your application, but this should be a good start. You can always add a filter to limit things to specific types, of course. You can pretty much create all the tables in any order you want. Views, Functions, and Stored Procedures (in that order) are a bit trickier... views especially can have recursive dependencies that can be difficult to deal with, but it's certainly possible (recursive CTEs can auto-generate order here as well).
This looks like a "crude" but extremely straightforward way to handle it:
http://inedo.com/blog/using-source-control-to-manage-stored-procedures
Key excerpt:
The rules of database objects in source control are simple: one file
per object (named after the object) that will DROP and then CREATE the
object. You can use a tool like Scriptio to extract existing objects
into files. Throw in a little file organization, and you're ready for
check-in:
1.FUNCTIONS\
1.FormatOrderNumber.sql
2.VIEWS\
1.OrdersWithTotals.sql
1.SalesReport.sql
2.OrdersPastDue.sql
3.PROCS\
1.AddItemToOrder.sql
1.ValidateOrder.sql
2.ProcessOrder.sql
Note that the numeric prefixes ensure proper execution order.
Procedures can use Views, which in turn can use functions... but
usually not the other way around. And since a handful database objects
depend on other objects, those can prefixed with “2.” instead of “1.”.
Execution of these scripts is just as easy, and can be accomplished
with a simple batch file:
FOR /R %%f IN (*.sql) DO (
ECHO Running %%f
OSQL -E -i "%%f" -n -b -d myDatabase -S myServer
)
With scripts and a batch script set-up in source control, building
your database code is as easy as grabbing from source control and
clicking “execute scripts”.
Another possible approach is outlined here:
https://www.simple-talk.com/sql/t-sql-programming/dependencies-and-references-in-sql-server/
(I'll paste in one script but there are others in that article worth checking out.)
Here is a routine that shows you the soft dependency order of the objects in your database, and lists the external dependencies of any objects. (note that a lot of entities in a database aren’t classed as objects. )
IF object_id (N'DependencyOrder') IS NOT NULL
DROP FUNCTION dbo.DependencyOrder
GO
CREATE FUNCTION dbo.DependencyOrder()
/*
summary: >
This table-valued function is designed to give you the order in which
database objects should be created in order for a build to succeed
without errors. It uses the sys.sql_expression_dependencies table
for the information on this.
it actually only gives the level 1,,n so within the level the order
is irrelevant so could, i suppose be done in parallel!
It works by putting in successive passes, on each pass adding in objects
who, if they refer to objects, only refer to those already in the table
or whose parent object is already in the table. It goes on until no more
objects can be added or it has run out of breath. If it does more than
ten iterations it gives up because there must be a circular reference
(I think that's impossible)
Revisions:
- Author: Phil Factor
Version: 1.0
Modification: First cut
date: 3rd Sept 2015
example:
- code: Select * from dbo.DependencyOrder() order by theorder desc
returns: >
a table, giving the order in which database objects must be built
*/
RETURNS #DependencyOrder TABLE
(TheSchema VARCHAR(120) null, TheName VARCHAR(120) NOT null, Object_id INT PRIMARY KEY, TheOrder INT NOT null,iterations INT null,ExternalDependency VARCHAR(2000) null )
AS
-- body of the function
BEGIN
DECLARE #ii INT,#EndlessLoop INT,#Rowcount INT
SELECT #ii=1, #EndlessLoop=10, #RowCount=1
WHILE #rowcount>0 AND #endlessLoop>0
BEGIN
;WITH candidates (object_ID, Parent_object_id) AS
(SELECT sys.objects.Object_ID,sys.objects.parent_Object_id
FROM sys.objects
LEFT OUTER JOIN #DependencyOrder Dep --not in the dependency table already
ON dep.Object_id = objects.object_id
WHERE dep.Object_id IS NULL
AND type NOT IN ('s', 'sq','it')
)
INSERT INTO #DependencyOrder (TheSchema,TheName,Object_id,TheOrder)
SELECT object_schema_name(c.Object_ID),object_name(c.Object_id),c.object_id, #ii
FROM candidates c INNER JOIN #DependencyOrder parent
ON c.Parent_object_ID= parent.Object_ID
UNION
SELECT object_schema_name(Object_ID),object_name(Object_id),object_id, #ii
FROM candidates c
WHERE Parent_object_id=0
AND object_id NOT IN (
SELECT c.object_id
FROM candidates c
INNER JOIN sys.sql_expression_dependencies
ON object_ID=referencing_ID
LEFT OUTER JOIN #DependencyOrder ReferedTo
ON ReferedTo.object_id =referenced_ID
WHERE ReferedTo.object_id IS NULL
AND referenced_id IS NOT NULL--not a cross-database dependency
)
SET #rowcount=##rowcount
SELECT #ii=#ii+1,#EndlessLoop=#EndlessLoop-1
END
UPDATE #dependencyOrder SET iterations= #ii-1
UPDATE #dependencyOrder SET ExternalDependency= ListOfDependencies
FROM
(SELECT Object_ID, STUFF(
(SELECT ', '+coalesce(referenced_server_name+'.','')
+coalesce(referenced_database_name+'.','')
+coalesce(referenced_schema_name+'.','')
+referenced_entity_name
FROM sys.sql_expression_dependencies sed
WHERE sed.referencing_ID=externalRefs.object_ID
and referenced_database_name IS NOT NULL AND is_ambiguous=0
FOR Xml PATH (''), ROOT('i'), TYPE).value('/i[1]', 'varchar(max)')
,1,2,'') ListOfDependencies
FROM #dependencyOrder externalRefs
)f
INNER JOIN #dependencyOrder d ON f.object_id=d.Object_ID
RETURN
END
GO
I have a table valued function declared so that I can return several values in one go. The values are calculated using DECLARE and Maths and Date functions.
The function is structured such that it only takes a 'logged date', and a priority for issues in a support system. I honestly thought that I'd be able to select as follows:
SELECT SupportCall.*, dbo.GetSLAStatus(SupportCall.createDate, SupportCall.priority).* FROM SupportCall
I've actually ended up with:
SELECT SupportCall.*,
SLADays = (select SLADays from dbo.GetSLAStatus(SupportCall.createDate, SupportCall.priority)),
SLARecieved = (select SLAReceived from dbo.GetSLAStatus(SupportCall.createDate, SupportCall.priority)),
SLATarget = (select SLATarget from dbo.GetSLAStatus(SupportCall.createDate, SupportCall.priority)),
SLAHoursRemaining = (select SLAHoursRemaining from dbo.GetSLAStatus(SupportCall.createDate, SupportCall.priority))
From SupportCall
I can't see a possible join for an Apply (which I don't fully understand anyway).
Does anybody know whether the function calls with the same parameters will be executed once? If I'm not going to end up with lots of subqueries and function calls when the query runs, then I don't care, the code is actually quite tidy if not concise.
If there is a massive overhead, does anybody know how to select all columns from a table function of this kind (i.e. no keys, just several calculations on the same input data).
Thanks.
Mark
Don't do that! Inline queries are NEVER faster than JOINS or APPLY. Rewrite your query and check the IO. You can rewrite it something like:
SELECT SupportCall.*,
SLADays = gs.SLADays,
SLAReceived = gs.SLAReceived,
...
From SupportCall sc
CROSS APPLY dbo.GetSLAStatus(SupportCall.createDate, SupportCall.priority) gs
Can't you just do this:
SELECT C.*,
F.SLADays,
F.SLAReceived,
F.SLATarget,
F.SLAHoursRemaining
From
SupportCall C
cross apply dbo.GetSLAStatus(C.createDate, C.priority) F
I hope you're function is an inline function (e.g has a single statement that starts returns table return (...) and does not have a defined result table)
In MS SQL Server. Please don't tell me to ctrl-f. I don't have access to the entire query, but I'm composing the columns that depend on whether certain variable is declared.
Thanks.
Edit:
I'm working with some weird query engine. I need to write the select columns part and the engine will take care of the rest (hopefully). But in some cases this engine will declare variables (thankfully I will know the variable names), and in other cases it doesn't. I need to compose my columns to take these variables when they are declared, and give default values when these variables are not declared.
Given the limitations of my understanding of what you're running (I'm deciphering the word problem to mean that your "query engine" is actually a "query generation engine" so, something like an ORM) you could observe what's occurring on the server in this scenario with the following query:
select
sql_handle,
st.text
from sys.dm_exec_requests r
cross apply sys.dm_exec_sql_text(r.sql_handle) st
where session_id <> ##SPID
and st.text like '%#<<parameter_name>>%';
The statement needs to have begun execution to be able to catch it. Depending on a multitude of situations, you may be able to pull it from query stats, too:
This will also get you the query plan (if it has one), but note that it will also pull stats for itself as well as the query above so you'll need to be discerning when you look at the outer and statement text values:
select
text,
SUBSTRING(
st.text,
(qs.statement_start_offset / 2) + 1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(st.TEXT)
ELSE qs.statement_end_offset
END -
qs.statement_start_offset) / 2) + 1)
AS statement_text,
plan_generation_num, creation_time, last_execution_time, execution_count
,total_worker_time, last_worker_time, min_worker_time, max_worker_time,
total_physical_reads, min_physical_reads, max_physical_reads, last_physical_reads,
total_logical_writes, min_logical_writes, max_logical_writes, last_logical_writes,
total_logical_reads, min_logical_reads, max_logical_reads, last_logical_reads,
total_elapsed_time, last_elapsed_time, min_elapsed_time, max_elapsed_time,
total_rows,last_rows,min_rows,max_rows
,qp.*
from sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) st
outer apply sys.dm_exec_query_plan(qs.plan_handle) qp
where st.text like '%#<<parameter_name>>%';