I am a neophyte to creating stored procedures and functions and I just can't figure out why one of these versions runs so much faster than the other. This is a function that just returns a string with a description when called. The original function relies on supplying about 10 variables (Version running in about 4 seconds). I wanted to cut that down to a single variable (version running long).
The code below the declaration of the variables is identical, the only difference is that I'm attempting to pull the variables from the appropriate within the function itself rather than having to supply them on the query side.
i.e. dbo.cf_NoRateReason(V1) as ReasonCode
rather than
dbo.cf_NoRateReason(V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12)
I apologize up front if I am not supplying enough information, as I said, new to functions/stored procedures.
This version runs in about 2.5 minutes to run
declare #Agencyid int
declare #ServiceCode varchar(10)
declare #Mod1 varchar(2)=null
declare #Mod2 varchar(2)=null
declare #Mod3 varchar(2)=null
declare #Mod4 varchar(2)=null
declare #POS int
declare #ServiceDate datetime
declare #ProvType varchar(1)
declare #PayerID int
declare #BirthDate datetime
declare #RenderingStaffID int
declare #SupervisingStaffID int
Select #Agencyid=s.agencyid, #ServiceCode = ServiceCode,
#Mod1 = ModifierCodeId, #Mod2 = ModifierCodeId2,
#Mod3 = ModifierCodeId3, #Mod4 = ModifierCodeId4,
#POS=PlaceOfServiceId, #ServiceDate = ServiceDate,
#RenderingStaffId=isnull(dbo.GetProviderStaffId('S',s.ServiceTransactionId,'82'),0),
#SupervisingStaffId=isnull(dbo.GetProviderStaffId('C',ClaimId,'DQ'),0),
#ProvType=s.servicetype, #Payerid=pmt.payerid,
#BirthDate=i.birthdate
From ServiceTransaction s
join individual i on s.servicetransactionid = i.individualid
join pmtadjdetail pmt on s.servicetransactionid = pmt.servicetransactionid
declare #Result Varchar(100) = ''
declare #Age int = dbo.getageatservicedate(#birthdate, #ServiceDate)
declare #ModString varchar(8) = dbo.sortmodifiers(#Mod1, #Mod2, #Mod3, #Mod4)
declare #DirectSupervision int = (iif(#Mod1 in ('U1','U6','U7','U9','UA')
or #Mod2 in ('U1','U6','U7','U9','UA')
or #Mod3 in ('U1','U6','U7','U9','UA')
or #Mod4 in ('U1','U6','U7','U9','UA'),1,0))
'************************************************************************************'
'This version takes about 4 seconds to run'
'************************************************************************************'
begin
declare #Result Varchar(100) = ''
declare #Age int = dbo.getageatservicedate(#birthdate, #ServiceDate)
declare #RenderingStaffID int = dbo.getstaffid(#STID,'DQ')
declare #SupervisingStaffID int = dbo.getstaffid(#STID,'82')
declare #ModString varchar(8) = dbo.sortmodifiers(#Mod1, #Mod2, #Mod3, #Mod4)
declare #DirectSupervision int = (iif(#Mod1 in ('U1','U6','U7','U9','UA')
or #Mod2 in ('U1','U6','U7','U9','UA')
or #Mod3 in ('U1','U6','U7','U9','UA')
or #Mod4 in ('U1','U6','U7','U9','UA'),1,0))
This kind of falls under "typo" or simple oversight, but....
When you see that big of a performance difference, for no discernible reason (those functions were used in the original version as well), that is usually when you need to start look for these kinds of mistakes: typos, missing conditions, incorrect conditions from leaning too hard on intellisense/code-completion, etc...
When replacing multiple parameters with one that can used to retrieve the others automatically, always make sure to actually use that parameter.
The version you listed first has no filter (no WHERE clause) on the SELECT it uses to get the "parameter" values it is normally passed. You're effectively getting the entire join resultset, with the cost of the function calls for every result row, and only taking last result's values.
You are correct - the only difference is using the function. Please see similar questions where this has been addressed.
In short, functions are going to be performed on a row-by-row basis whereas code on the query side is going to have other options with no overhead calls to the function.
You may be able to use a scalar function with schema binding and nulls return nulls for better performance.
Additional consideration for the schema plan would be valuable. There are also joins and other embedded logics here that aren't clear without sample data.
Related
CREATE OR ALTER FUNCTION sso.FINDSEQVALUE
(#sequence_text text)
RETURNS int
AS
BEGIN
DECLARE #value int;
DECLARE #sequence_value nvarchar(150);
SELECT #sequence_value = CAST(#sequence_text AS nvarchar(150));
SELECT #value = NEXT VALUE FOR #sequence_value;
RETURN #value;
END;
I have a problem. I have created a function on SQL Server and I defined the parameter as you can see. But I cannot add the this command #sequence_value after NEXT VALUE FOR command and I am getting an error.
Incorrect syntax near '#sequence_value'
Somebody can say that "You can use (SELECT NEXT VALUE FOR [SEQUENCE])". But I need this function because of there are two different database on my project. I need same function for databases. In addition function parameter need to be text.
What should I do?
I have the following SQL XML in several rows of a table (table is tbldatafeed column in configuration_xml). All of the UserName="" and Password="" is different each time for each row and does not repeat so I can not find/replace off of that. I am trying to write a query that finds all of those and replaces them with Username/Passwords I choose.
<DataFeed xmlns="http://www.tech.com/datafeed/dfx/2010/04" xmlns:plugin="pluginExtensions" Type="TODO" Guid="TODO" UserAccount="DF_LEAN_PopulateCommentsSubForm" Locale="en-US" DateFormat="" ThousandSeparator="" NegativeSymbol="" DecimalSymbol="" SendingNotifications="false" SendJobStatusNotifications="false" RecipientUserIds="" RecipientGroupIds="" RecipientEmailAddresses="" Name="CI_C11.01_Lean-Lean_Reject Comments_A2A" >
<Transporter>
<transporters:ArcherWebServiceTransportActivity xmlns:transporters="clr-namespace:ArcherTech.DataFeed.Activities.Transporters;assembly=ArcherTech.DataFeed" xmlns:out="clr-namespace:ArcherTech.DataFeed;assembly=ArcherTech.DataFeed" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:compModel="clr-namespace:ArcherTech.DataFeed.ComponentModel;assembly=ArcherTech.DataFeed" xmlns:channel="clr-namespace:ArcherTech.DataFeed.Engine.Channel;assembly=ArcherTech.DataFeed" xmlns:engine="clr-namespace:ArcherTech.DataFeed.Engine;assembly=ArcherTech.DataFeed" xmlns:kernel="clr-namespace:ArcherTech.Kernel.Channel;assembly=ArcherTech.Kernel" xmlns="clr-namespace:ArcherTech.DataFeed;assembly=ArcherTech.DataFeed" xmlns:schema="clr-namespace:System.Xml.Schema;assembly=System.Xml" xmlns:xmlLinq="clr-namespace:System.Xml.Linq;assembly=System.Xml" xmlns:domain="clr-namespace:ArcherTech.Common.Domain;assembly=ArcherTech.Common" xmlns:s="clr-namespace:System;assembly=mscorlib" x:Key="transportActivity" SearchType="ReportId" Uri="https://arcs-d" RecordsPerFile="100" ReportID="EC514865-88D5-49CE-A200-7769EC1C2A88" UseWindowsAuth="false" IsWindowsAuthSpecific="false" WindowsAuthUserName="i9XzCczAQ7J2rHwkg6wG9QF8+O9NCYJZP6y5Kzw4be0+cdvUaGu/9+rHuLstU736pnQrRcwmnSIhd6oPKIvnLA==" WindowsAuthPassword="+y0tCAKysxEMSGv1unpHxfg6WjH5XWylgP45P5MLRdQ6+zAdOLSVy7s3KJa3+9j2i83qn8I8K7+1+QBlCJT1E7sLQHWRFOCEdJgXaIr1gWfUEO+7kjuJnZcIEKZJa2wHyqc2Z08J2SKfdCLh7HoLtg==" WindowsAuthDomain="" ProxyName="" ProxyPort="8080" ProxyUsername="" ProxyPassword="" ProxyDomain="" IsProxyActive="False" ProxyOption="None" InstanceName="ARCS-D" TempFileOnSuccessAction="DoNothing" TempFileOnSuccessRenameString="" TempFileOnErrorAction="DoNothing" TempFileOnErrorRenameString="" Transform="{engine:DataFeedBinding Path=Transform}" SessionContext="{engine:DataFeedBinding Path=Session}">
<transporters:ArcherWebServiceTransportActivity.Credentials>
<NetworkCredentialWrapper UserName="TeSZmI1SqO0eJ0G2nDVU+glFg/9eZfeMppYQnPfbeg8=" Password="Slt4VHqjkYscWyCwZK40QJ7KOQroG9OTKr+RGt9bQjE=" />
</transporters:ArcherWebServiceTransportActivity.Credentials>
</transporters:ArcherWebServiceTransportActivity>
</Transporter>
</DataFeed>
I need to be able to set a value and replace it with a query
I have written the following
select #config_xml=configuration_xml from bldatafeed where datafeed_name = 'REMOVED'
update tbldatafeed set configuration_xml.modify(//*:NetworkCredentialWrapper/#UserName)[1] with "abc" ')
where datafeed_name = 'REMOVED'
This does the trick but it only works if I set the "abc" password each time in each area and in some cases I am running this against 50+ rows.
I also tried:
Declare #server nvarchar(max) = 'abc'
Declare #config_xml xml
select #config_xml=configuration_xml from bldatafeed where datafeed_name = 'REMOVED'
update tbldatafeed set configuration_xml.modify(//*:NetworkCredentialWrapper/#UserName)[1] with #server ')
where datafeed_name = 'REMOVED'
The error from this is that: XQuery [tbldatafeed.configuration_xml.modify()]: Top-level attribute nodes are not supported
What I would like to be able to do is set my variable and utilize that as I will be setting this up for multiple rows and unfortunately this error is making this a very difficult problem to solve.
Thanks for any help, this has kept me confused for a bit.
Use the function sql:variable() to use a variable in the XQuery expression.
declare #T table(X xml);
insert into #T values('<X UserName=""/>');
declare #UserName nvarchar(max) = 'abc'
update #T set
X.modify('replace value of (/X/#UserName)[1]
with sql:variable("#UserName")');
Unfortunately, I have two tables to compare float datatypes between. I've read up on trying casts, converts, using a small difference and tried them all.
The strange part is, this only fails when I'm executing a stored procedure. If I cut-and-paste the body of the stored procedure into a SSMS window, it works just great.
Sample SQL:
set #newEnvRiskLevel = -1
select
#newEnvRiskLevel = rl.RiskLevelId
from
LookupTypes lt
inner join
RiskLevels rl on lt.LookupTypeId = rl.RiskLevelTypeFk
where
lt.Code = 'RISK_LEVEL_ENVIRONMENTAL'
and convert(numeric(1, 0), rl.RiskFactor) = #newEnvScore
set #errorCode = ##ERROR
if (#newEnvRiskLevel = -1 or #errorCode != 0)
begin
print 'newEnvScore = ' + cast(#newEnvScore as varchar) + ' and risk level = ' + cast(isnull(#newEnvRiskLevel, -1) as varchar)
print 'ERROR finding environmental risk level for code ' + #itemCode + ', skipping record'
set #recordsErrored = #recordsErrored + 1
goto NEXTREC
end
My #newEnvScore variable is also a float converted to numeric(1, 0). I've verified that there are only 0, 1, 2, and 3 for values in the RiskFactor column, and (via debug) that #newEnvScore has a value of 2. I've also verified that my query has a row with code = 'RISK_LEVEL_ENVIRONMENTAL' and RiskFactor = 2.
I've verified via debug that failure is due to #newEnvRiskLevel staying at -1 and that #errorCode is 0.
I've also tried cast to both decimal and int, convert to int, and "rl.RiskFactor - #newEnvScore < 1" in my where clause, none of which set newEnvRiskLevel.
As I say, it's only when running this as a stored procedure that failure happens, which is the part I really don't understand. I'd expect SQL Server to be deterministic, whether the SQL is running the body of a stored procedure, or running the exact same SQL in a SSMS tab.
It is unfortunate that you do post neither your stored procedure nor a complete script. It is difficult to diagnose a problem without a useful demonstration. But I see the use of "goto" which is concerning in many ways. I also see the use of a select statement to assign a local variable - which is often a problem because the developer might be assuming an assignment always occurs. To demonstrate - with a bonus at the end
set nocount on;
declare #risk smallint;
declare #risklevels table (risklevel float primary key, code varchar(10));
insert #risklevels(risklevel, code) values (1, 'test'), (2, 'test'), (-5, 'test');
-- here is your assignment logic. Notice that #risk is
-- never changed because there are no matching rows.
set #risk = 0;
select #risk = risklevel from #risklevels where code = 'zork';
select #risk;
-- here is a better IMO way to make the assignment. Note that
-- #risk is set to NULL when there are no matching rows.
set #risk = -1;
set #risk = (select risklevel from #risklevels where code = 'zork');
select #risk;
-- and a last misconception. What value is #risk set to? and why?
set #risk = -1;
select #risk = risklevel from #risklevels where code = 'test';
select #risk;
Whether this is the source of your problem (or contributes to it) I can't say. But it is a possibility. And storing integers in a floating point datatype is just a problem generally. Even if you cannot change your table, you can change your local variables and force the use of a more appropriate datatype. So perhaps that is another change you should consider.
In TSQL, I need to do a cross-reference from our value for a given field, to the specified client's value. We have many clients and each client's encoding of values is different. The db is a CRM Dynamics 2011 db.
I set up a scalar-valued function like this:
[dbo].[fn_GetXRef]
(#Guid uniqueidentifier, #LookupType nvarchar(20),
#OurValue nvarchar(20), #Parm4 nvarchar(20) = null,
#Parm5 uniqueidentifier = null, #Parm6 nvarchar(1) = null,
#Parm7 nvarchar(1) = null)
Parms 4, 5, 6 and 7 may be null; they are used for some cross-references but not others.
If I run execute the logic outside the function, it works. When I execute the function it returns NULL.
For example:
Select dbo.fn_getXRef('22BF20B1-55F1-E211-BF73-00155D062F00',
'Lookup Type 1', 'Our value', null, null, null, '3')
It returns null but pulling the logic out of the function and running it as a separate query, and using the same input parameter values, returns the correct client-value.
What am I not seeing?
update: 12/11/13
Thanks all for trying to help. While researching I found some nifty code that looked more efficient than my own so I re-wrote the function using that technique and now it works. It uses OPTION (RECOMPILE):
SELECT #TheirValue = X.carriervalue
FROM dbo.Filteredcrossreference X
WHERE
X.carrier = #CarrierId
and X.lookuptype = #LookupType
and X.ourvalue = #OurValue
and (#Parm4 IS NULL OR (X.parm4 = #Parm4))
and (#Parm5 IS NULL OR (X.parm5 = #Parm5))
and (#Parm6 IS NULL OR (X.parm6 = #Parm6))
OPTION (RECOMPILE)
Hard to tell, without seeing the body of your function. But a common place to look will be at what parms are actually passed. Chances are, you may think you're passing null, when actually you're passing an empty string, or a default value, or something along those lines.
select * from FOO.MBR_DETAILS where BAR= 'BAZ' and MBR_No = '123'
execution time = 0.25 seconds
CREATE PROCEDURE My.MEMBER_SEARCH
(
i_BAR varchar(3),
i_member_surname varchar(50),
i_member_code varchar(10),
i_member_given_name varchar(50)
)
RESULT SETS 1
LANGUAGE SQL
BEGIN
DECLARE c1 cursor with return for
select *
FROM FOO.MBR_DETAILS m
WHERE
BAR= i_BAR
and (i_member_code = '' or m.MBR_No = i_member_code)
and (i_member_surname = '' or m.surname = i_member_surname)
and (i_member_given_name = '' or m.given_names LIKE '%'||i_member_given_name||'%');
OPEN c1;
END
call My.MEMBER_SEARCH('BAZ','','123','')
execution time = 1.9 seconds
I thought both queries should have a similar time as i_member_surname and i_member_given_name are both empty they would not be evaulated.
The solution is to enable REOPT ALWAYS for any stored procedure that runs a flexible, parameter-driven search.
The REOPT ALWAYS option will force the optimizer to analyze the input parameter values and come up with a new access plan every time the procedure is executed, instead of just once when the procedure is compiled. Although REOPT ALWAYS adds a few extra milliseconds of optimizer overhead for each and every execution of the stored procedure, that is most likely faster than continually reusing the one-size-fits-all access plan that the optimizer guessed at while initially compiling the stored procedure.