Why does scalar-value function fail with null parameter? - sql-server

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.

Related

Function Efficiency

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.

Having an Issue with too many parameters being passed to stored procedures in ColdFusion 2016

I have several stored procedures in an application that were functioning perfectly (for years) until our recent upgrade from ColdFusion 2010 to ColdFusion 2016. Now, I am getting error messages of either too many parameters or a certain parameter is not a parameter is not contained in the procedure that is being called.
I have opted to upload some code so people can better understand what is actually happening. Still learning how to format code here so please forgive me if it is still lacking.
In both cases I have double checked the parameter lists in the stored procedure in the procedure calls and have found that they are all indeed correct. In fact, nothing has changed in this code for over 5 years. This behavior has only begun since the upgrade has taken place.
Below is the first example. I will list the procedure call (in cfscript)
first then the parameter list from the stored procedure and then the error message it produced:
public query function readStorage(numeric group1=0,numeric group2=0) {
local.group1Value = arguments.group1?arguments.group1:"";
local.group2Value = arguments.group2?arguments.group2:"";
spService = new storedproc();
spService.setDatasource(variables.dsn);
spService.setUsername(variables.userName);
spService.setPassword(variables.password);
spService.setProcedure("usp_readCompatibilityStorage");
spService.addParam(dbvarname="#group1Id",cfsqltype="cf_sql_integer"
, type="in",value=local.group1Value,null=!arguments.group1);
spService.addParam(dbvarname="#group2Id",cfsqltype="cf_sql_integer"
,type="in",value=local.group2Value,null=!arguments.group2);
spService.addProcResult(name="rs1",resultset=1);
local.result = spService.execute();
return local.result.getProcResultSets().rs1;
}
Below is the parameter list from the stored procedure:
#groupId1 int = NULL
,#groupId2 int = NULL
Below is the error message I get:
[Macromedia][SQLServer JDBC Driver][SQLServer]#group1Id is not a
parameter for procedure usp_readCompatibilityStorage.
Second Example:
public query function read(string cribIdList="",
numeric cribNumber=0,
string isAnnex="",
numeric siteId=0,
string parentCribIdList="",
numeric supervisorId=0,
numeric statusId=0,
string orderBy="cribNumber ASC") {
local.cribNumberValue = arguments.cribNumber?arguments.cribNumber:"";
local.siteIdValue = arguments.siteId?arguments.siteId:"";
local.superIdValue = arguments.supervisorId ? arguments.supervisorId:"";
local.statusIdValue = arguments.statusId ? arguments.statusId:"";
spService = new storedproc();
spService.setDatasource(variables.dsn);
spService.setUsername(variables.userName);
spService.setPassword(variables.password);
spService.setProcedure("usp_readCrib");
spService.addParam(dbvarname="#cribIdList",cfsqltype="cf_sql_varchar"
,type="in",value=arguments.cribIdList
,null=!len(arguments.cribIdList));
spService.addParam(dbvarname="#cribNumber",cfsqltype="cf_sql_integer"
,type="in",value=local.cribNumberValue
,null=!arguments.cribNumber);
spService.addParam(dbvarname="#isAnnex",cfsqltype="cf_sql_varchar"
,type="in",value=arguments.isAnnex,null=!len(arguments.isAnnex));
spService.addParam(dbvarname="#siteId",cfsqltype="cf_sql_integer"
,type="in",value=local.siteIdValue,null=!arguments.siteId);
spService.addParam(dbvarname="#parentCribIdList"
, cfsqltype="cf_sql_varchar", type="in"
, value=arguments.parentCribIdList
, null=!len(arguments.parentCribIdList));
spService.addParam(dbvarname="#supervisorId",
cfsqltype="cf_sql_integer", type="in",value=local.superIdValue
, null=!arguments.supervisorId);
spService.addParam(dbvarname="#statusId"
, cfsqltype="cf_sql_integer", type="in"
, value=local.statusIdValue, null=!arguments.statusId);
spService.addParam(dbvarname="#orderBy",cfsqltype="cf_sql_varchar"
, type="in",value=arguments.orderBy);
spService.addProcResult(name="rs1",resultset=1);
local.result = spService.execute();
return local.result.getProcResultSets().rs1;
}
Below is the parameter list from the stored procedure:
#cribIdList varchar(500) = NULL
,#cribNumber int = NULL
,#isAnnex varchar(3) = NULL
,#siteId int = NULL
,#parentCribIdList varchar(500) = NULL
,#supervisorId int = NULL
,#statusId int = NULL
,#orderBy varchar(50)
Below is the message returned from the server:
[Macromedia][SQLServer JDBC Driver][SQLServer]Procedure or function
usp_readCrib has too many arguments specified.
In the case of both errors, they seem to be occurring at the following path:
Error Details - struct
COLUMN 0
ID CFSTOREDPROC
LINE 489
RAW_TRACE at cfbase2ecfc235349229$funcINVOKETAG.runFunction(E:\ColdFusion2016\cfusion\CustomTags\com\adobe\coldfusion\base.cfc:489)
TEMPLATE E: \ColdFusion2016\cfusion\CustomTags\com\adobe\coldfusion\base.cfc
TYPE CFML````
ColdFusion 10 and greater limit the amount of parameters in a request to 100 by default. Fortunately this can be updated and changed to reflect the required amount of parameters you need for your stored procedures.

SQL: how to do an IF check for data range parameters

I am writing a stored procedure that takes in 4 parameters: confirmation_number, payment_amount, start_range, end_range.
The parameters are optional, so I am doing a check in this fashion for the confirmation_number, and the payment_amount parameters:
IF (#s_Confirmation_Number IS NOT NULL)
SET #SQL = #SQL + ' AND pd.TransactionNumber = #s_Confirmation_Number'
IF (#d_Payment_Amount IS NOT NULL)
SET #SQL = #SQL + ' AND pd.PaymentAmount = #d_Payment_Amount'
I would like to ask for help because I am not sure what is the best method to check for the date range parameters.
If someone could give me en example, or several on how this is best achieved it would be great.
Thank you in advance.
UPDATE - after receiving some great help -.
This is what I have so far, I am following scsimon recommendation, but I am not sure about the dates, I got the idea from another post I found and some playing around with it. Would you care looking at it and tell me what you all think?
Many thanks.
#s_Confirmation_Number NVARCHAR(50) = NULL
, #d_Payment_Amount DECIMAL(18, 2) = NULL
, #d_Start_Range DATE = NULL
, #d_End_Range DATE = NULL
...
....
WHERE
ph.SourceType = #s_Source_Type
AND ((pd.TransConfirmID = #s_Confirmation_Number) OR #s_Confirmation_Number IS NULL)
AND ((pd.PaymentAmount = #d_Payment_Amount) OR #d_Payment_Amount IS NULL)
AND (((NULLIF(#d_Start_Range, '') IS NULL) OR CAST(pd.CreatedDate AS DATE) >= #d_Start_Range)
AND ((NULLIF(#d_End_Range, '') IS NULL) OR CAST(pd.CreatedDate AS DATE) <= #d_End_Range))
(The parameter sourceType is a hard-coded value)
This is called a catch all or kitchen sink query. It is usually written as such:
create procedure myProc
(#Payment_Amount int = null
,#Confirmation_Number = null
,#start_range datetime
,#end_range datetime)
as
select ...
from ...
where
(pd.TransactionNumber = #Confirmation_Number or #Confirmation_Number is null)
and (pd.PaymentAmount = #Payment_Amount or #Payment_Amount is null)
The NULL on the two parameters gives them a default of NULL and makes them "optional". The WHERE clause evaluates this to only return rows where your user input matches the column value, or all rows when no user input was supplied (i.e. parameter IS NULL). You can use this with the date parameters as well. Just pay close attention to your parentheses. They matter a lot here because we are mixing and and or logic.
Aaron Bertrand has blogged extensively on this.
I do it like this
WHERE
COALESCE(#s_Confirmation_Number,pd.TransactionNumber) = pd.TransactionNumber AND
COALESCE(#d_Payment_Amount,pd.PaymentAmount) = pd.PaymentAmount
If we have a value for each of these parameters then it will check against the filter value otherwise it will always match the filter value if the parameter is null.
I've found that using COALESCE is faster and clearer than IF control statements or using OR in the WHERE clause.
There is another way.
But I tested and realized that a scsimon query is faster than mine.
AND (CASE
WHEN #Confirmation_Number is not null
THEN (CASE
WHEN pd.TransactionNumber = #Confirmation_Number
THEN 1
ELSE 0
END)
ELSE 1
END = 1)

Manipulating the list of data from stored procedure to be used in another stored procedure

Currently I'm writing a stored procedureusing T-SQL in SQL Server. My script contains code to run another stored procedure to get list of data from a table. I want to manipulate the data, using the list of data to modify them for another purpose (e.g. summing up a column and adding more lists of data) from the stored procedure. A way that I know is to create a temporary table. But after that, I'm not so sure. Please help. thanks.
This is my code:
ALTER PROCEDURE [dbo].[AJU_Rpt_ARAgingSp]
(#Slsman_Starting slsmantype = NULL,
#Slsman_Ending slsmantype = NULL,
#Custnum_Starting custnumtype = NULL,
#Custnum_Ending custnumtype = NULL,
#CustType endusertypetype = NULL,
#CutOff_Date datetype = NULL,
#SumToCorp ListYesNoType = NULL, -- >> 0 = individual, 1 = corp customer
#ShowActive ListYesNoType = NULL, -- >> 0 = all trx, 1 = active only
#TransDomCurr ListYesNoType = NULL, -- >> 0 = dont convert, 1 = convert to local currency
#AgingBasis ArAgeByType = NULL, -->> i = invoice date, d = due date
#LeftToRight ListYesNoType = NULL, -- >> 0 = right to left, 1 = left to right
#CurrSite NVARCHAR(8),
#ShowDetailInfo NVARCHAR(1) = NULL)
AS
BEGIN
SET NOCOUNT ON
IF ISNULL(#CurrSite ,'') = ''
SET #CurrSite = (SELECT TOP 1 site_ref FROM parms_mst)
DECLARE #v_StartDate DateType
SET #Slsman_Starting = ISNULL(#Slsman_Starting, dbo.LowCharacter())
SET #Slsman_Ending = ISNULL(#Slsman_Ending, dbo.HighCharacter())
SET #Custnum_Starting = ISNULL(#Custnum_Starting, dbo.LowCharacter())
SET #Custnum_Ending = ISNULL(#Custnum_Ending, dbo.HighCharacter())
SET #v_StartDate = dbo.LowDate()
SET #CutOff_Date = GETDATE()
EXEC dbo.ApplyDateOffsetSp #v_StartDate OUT, NULL, 0
EXEC AJU_Rpt_DebtorSp
#CustNumStart = #Custnum_Starting
,#CustNumEnd = #Custnum_Ending
,#DistDateStart = #v_StartDate
,#DistDateEnd = #CutOff_Date
,#CurrCodeStart = NULL
,#CurrCodeEnd = NULL
,#SlsmanStart = #Slsman_Starting
,#SlsmanEnd = #Slsman_Ending
,#TerritoryStart = NULL
,#TerritoryEnd = NULL
,#CustTypeStart = NULL
,#CustTypeEnd = NULL
,#SiteGroup = #CurrSite
,#ConsolidatePayment = NULL
,#DisplayResult = 1
END
Since the initial result table is temporarily needed, you could do as follows:
In the invoking procedure, create a table variable with identical structure as returned by the invoked procedure,
Write within the invoking procedure a statement like the following:
INSERT INTO #_Tempo_Table
EXEC Invoked_Procedure (<params>) ;
Within the invoked procedure issue a SELECT that will return the records set.
If, in the other hand, you need to pass the initial table to the invoked procedure:
Create a type with a structure identical to the table that needs to be shared,
Add to the invoked procedure an argument of the type you just created (must be declared as READONLY),
Once you have the table with data within the invoking procedure, make the call to the invoked procedure passing the table variable as argument.
This methods will render the best performance (here I'm assuming that you are not passing a table with millions of records; if you do, it will still be the fastest way though you might need a lot of memory).

DbString, IsFixedLength and IsAnsi for varchar

I am new to Dapper, want to know why below is suggested, when my code runs without it?
Ansi Strings and varchar
Dapper supports varchar params, if you are executing a where clause on
a varchar column using a param be sure to pass it in this way:
Query<Thing>("select * from Thing where Name = #Name", new {Name = new
DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi
= true });
On SQL Server it is crucial to use the unicode when querying unicode
and ansi when querying non unicode.
Below is my code, which runs against SQL server 2012 without using DbString etc.
create table Author (
Id int identity(1,1),
FirstName varchar(50),
LastName varchar(50)
);
go
insert into Author (FirstName, LastName) values ('Tom', 'John');
public Author FindByVarchar(string firstName)
{
using (IDbConnection db = DBHelper.NewSqlConnection())
{
return db.Query<Author>("Select * From Author WHERE FirstName = #firstName", new { firstName }).SingleOrDefault();
}
}
Questions:
1 Why is DbString type used in this case?
2 Why the length is set to 10 (e.g.Length = 10) when "abcde" is 5?
3 Do I still need to use DbString when my current code works?
4 Is it correct to set IsAnsi = false for unicode column?
5 For varchar column, is it correct to set IsFixedLength = false, and ignore setting Length?
The purpose of the example is that it is describing a scenario where the data type is char(10). If we just used "abcde", Dapper might think that nvarchar(5) was appropriate. This would be very inefficient in some cases - especially in a where clause, since the RDBMS can decide that it can't use the index, and instead needs to table scan doing a string conversion for every row in the table from char(10) to the nvarchar version. It is for this reason that DbString exists - to help you control exactly how Dapper configures the parameter for text data.
I think this answers your 1 and 2.
3: are you using ANSI (non-unicode text) or fixed-width text? Note that the ANSI default can also be set globally if you always avoid unicode
4: yes
5: yes
4+5 combined: if you're using nvarchar: just use string

Resources