SQL function not working properly, but does when not as a function - sql

I'm having another issue with a SQL Function that I had written. If I run it not in a function and use declared variables, then it works perfectly. But as soon as I put it into a function and run it, nothing appears, its empty.
I cannot put this into a stored procedure, it needs to be in a function.
the code is
select * from [MYTABLE]
where MajorGroupID = #MajorGroupID
and ((#Status = 0 and (
Inactive = 0)
))
or MajorGroupID = #MajorGroupID and (#Status = 1 and (Inactive = 0 or Inactive = 1))
I am not really familiar with functions, I can do basic things with functions but when it comes to adding logic to it. If I was allowed to use stored procedures then I wouldn't be having problems.
This is MSSQL and using SQL Server 2010.
EDIT, Added complete function
CREATE FUNCTION [dbo].[WT_FN_GET_MYTABLE_By_MajorGroupID_Inactive]
(
#MajorGroupID varchar,
#Status int
)
RETURNS TABLE
AS
RETURN
select * from [MYTABLE]
where MajorGroupID = #MajorGroupID
and ((#Status = 0 and (
Inactive = 0)
))
or MajorGroupID = #MajorGroupID and (#Status = 1 and (Inactive = 0 or Inactive = 1))

You don't provide a length for #MajorGroupID varchar, so it is going to be 1 by default, at which point it will not find anything in the table.
Provide a length, e.g. #MajorGroupID varchar(30).

Related

Is there a faster way to run an SQL Where Case

I have the following stored procedure (In MS SQL):
ALTER PROCEDURE [dbo].[proc_GetWorksWithEngineerVisits3]
#sTextSearch nvarchar(255) = NULL,
#bCompleteFlag bit = NULL,
#dExpectedStartDateTime datetime = NULL,
#dExpectedEndDateTime datetime = NULL,
#sResponsible_UserIDs nvarchar(255) = NULL,
#bEnableTextSearchFilter bit = false,
#bEnableCompleteFlagFilter bit = false,
#bEnableExpectedDateTimeRangeFilter bit = false,
#bEnableResponsible_UserIDFilter bit = false
AS
SELECT *
FROM dbo.vwWorksWithEngineerVisits
WHERE
--TextSearch Filter Start
(sCustomer LIKE CASE
WHEN #bEnableTextSearchFilter = 1
THEN '%' + #sTextSearch + '%'
ELSE sCustomer
END
OR
sSite LIKE CASE
WHEN #bEnableTextSearchFilter = 1
THEN '%' + #sTextSearch + '%'
ELSE sSite
END
OR
sCallID LIKE CASE
WHEN #bEnableTextSearchFilter = 1
THEN '%' + #sTextSearch + '%'
ELSE sCallID
END)
--TextSearch Filter End
AND
--Complete Filter Start
bIsComplete = CASE
WHEN #bEnableCompleteFlagFilter = 1
THEN #bCompleteFlag
ELSE bIsComplete
END
--Complete Filter End
AND
--Expected DateTime Range Filter Start
dExpectedStartDateTime >= CASE
WHEN #bEnableExpectedDateTimeRangeFilter = 1
THEN #dExpectedStartDateTime
ELSE dExpectedStartDateTime
END
AND
dExpectedEndDateTime <=
CASE
WHEN #bEnableExpectedDateTimeRangeFilter = 1
THEN #dExpectedEndDateTime
ELSE dExpectedEndDateTime
END
----Expected DateTime Range Filter End
AND
--Responsible_UserID Filter Start
lResponsible_UserID in (
CASE
WHEN #bEnableResponsible_UserIDFilter = 0
THEN lResponsible_UserID
ELSE (SELECT Value FROM dbo.CSVToList(#sResponsible_UserIDs) AS CSVToList_1)
END
)
--Responsible_UserID Filter End
ORDER BY dExpectedEndDateTime
The output is fine, but it is very slow (15 sec for only 5000 rows) Executing dbo.vwWorksWithEngineerVisits directly takes 1sec for the same number. When executing the SP, I am setting all enable flags = 0.
DECLARE #return_value int
EXEC #return_value = [dbo].[proc_GetWorksWithEngineerVisits3]
#sTextSearch = NULL,
#bCompleteFlag = False,
#dExpectedStartDateTime = N'01/01/1969',
#dExpectedEndDateTime = N'01/01/2021',
#sResponsible_UserIDs = NULL,
#bEnableTextSearchFilter = 0,
#bEnableCompleteFlagFilter = 0,
#bEnableExpectedDateTimeRangeFilter = 0,
#bEnableResponsible_UserIDFilter = 0
SELECT 'Return Value' = #return_value
I want to be able to only filter a column, if the corresponding flag is set. I probably could just check for NULL in the primary parameters and reduce the parameters, but I don't think it changes the problem I am having.
The first 4 Case filters are very basic, and when I comment the remaining last 3 out, the performance/result is instantaneous. As soon as I add one of last 3 back into the mix, things slow down as above. What makes these different is that they do ">=" or "in", rather than just an "=" or "like". The other thing that I noticed is that when I changed the following:
lResponsible_UserID in (
CASE
WHEN #bEnableResponsible_UserIDFilter = 0
THEN lResponsible_UserID
ELSE (SELECT Value FROM dbo.CSVToList(#sResponsible_UserIDs) AS CSVToList_1)
END
to
lResponsible_UserID in (
CASE
WHEN #bEnableResponsible_UserIDFilter = 0
THEN lResponsible_UserID
ELSE lResponsible_UserID
END
This also speed things up to 1 sec. How is this the case that changing the else part of the statement makes any difference whatsoever, when the flag is always 0, so should never run?
I need these filters, and I need them dynamic. There are a mix of operator types (including an IN that targets a function). Is there a way to refactor this stored procedure to have the same result (it does work), but in a much more optional way?
Apologies if I have missed something in my post, and I will edit if this pointed out.
Thanks
That's a big query!
SQL Server runs a compiler against the queries in your sp when you define it. Then it uses that compiled procedure, blithely ignoring any optimizations that might come from your specific parameter values. This page explains:
When SQL Server executes procedures, any parameter values that are used by the procedure when it compiles are included as part of generating the query plan. If these values represent the typical ones with which the procedure is subsequently called, then the procedure benefits from the query plan every time that it compiles and executes. If parameter values on the procedure are frequently atypical, forcing a recompile of the procedure and a new plan based on different parameter values can improve performance.
In your situation, your parameter settings dramatically simplify the search you want. But the compiled sp doesn't know that so it uses an excessively generalized search plan.
Try appending this to the query in your SP (after your ORDER BY clause) to force the generation of a new, hopefully more specific, execution plan.
OPTION (RECOMPILE)
Also, you can tidy up your filter clauses and make them a little less gnarly.
Try this for your text-search cases: Change
sCustomer LIKE CASE
WHEN #bEnableTextSearchFilter = 1
THEN '%' + #sTextSearch + '%'
ELSE sCustomer
END
to
(#bEnableTextSearchFilter <> 1 OR sCustomer LIKE '%' + #sTextSearch + '%')
This will refrain from saying column LIKE column when your filter is disabled, and may save some time.
You can apply the same principle to the rest of your CASE statements too.
Note: the filter pattern column LIKE '%value%' is inherently slow; it can't use an index range scan on column because the text-matching isn't anchored at the beginning of the pattern. Rather it must scan all the values.

DB2 SQL function returning multiple values when I am expecting only one

I am trying to get the location of the last time an item was moved via sql function with the code below. Pretty basic, I'm just trying to grab the max date and time. If I run the sql as a regular select and hard code an item number in ATPRIM I get only one location. But if I create this function and then try to run it and then pass the function an item number I get every occurrence in the history file instead of just the MAX which would be the most recent. Also I have tried a Select Distinct and that did not do anything for me.
ATOGST = Item Location
ATPRIM = Item
ATDATE = Date
ATTIME = Time
CREATE FUNCTION ERPLXU/F#QAT1(AATPRIM VARCHAR(10))
RETURNS CHAR(50)
LANGUAGE SQL
NOT DETERMINISTIC
BEGIN DECLARE F#QAT1 CHAR(50) ;
SET F#QAT1 = ' ' ;
SELECT ATOGST
INTO F#QAT1 FROM ERPLXF/QAT as t1
WHERE ATPRIM = AATPRIM
AND ATDATE = (SELECT MAX(ATDATE) FROM ERPLXF/QAT AS T2
WHERE T2.ATPRIM = AATPRIM)
AND ATTIME = (SELECT MAX(ATTIME) FROM ERPLXF/QAT AS T3
WHERE T3.ATPRIM = AATPRIM
AND T3.ATDATE = T1.ATDATE) ;
RETURN F#QAT1 ;
END
EDIT:
So what I am trying to do is get that location and I got it to work on my iSeries in strsql but the problem is we use a web application called Web Object Wizard (WoW) which lets us use sql to make reports that are more user friendly. Below is what I was trying to get to work but the subquery in the select does not work in WoW so that is where I was trying create a function which we know works in other applications.
SELECT distinct t1.atprim, atdesc, dbtabl, dbdtin, dblife, dblpdp,
dbcost, dbbas, dbresv, dbyrdp, dbcurr,
(select atogst
from erplxf.qat as t2
where t1.atprim = t2.atprim and atdate = (select max(atdate) from
erplxf.qat as t3 where t2.atprim = t3.atprim) and attime = (select
max(attime) from erplxf.qat as t4 where t1.atprim = t4.atprim and
t1.atdate = t4.atdate)
) as #113_ToLoc
FROM erplxf.qat as t1 join erplxf.qdb on atassn = dbassn
where dbrcid = 'DB'
and dbcurr != 0
So instead of that subquery at the end of the select it would just be
, erplxu.f#qat1(atprim) as #113_ToLoc
Try this:
CREATE FUNCTION ERPLXU/F#QAT1(AATPRIM VARCHAR(10))
RETURNS CHAR(50)
LANGUAGE SQL
RETURN
SELECT ATOGST
FROM ERPLXF/QAT
WHERE ATPRIM = AATPRIM
ORDER BY ATDATE DESC, ATTIME DESC
FETCH FIRST 1 ROW ONLY;

SQL - Why does the ordering matter for this select?

I ran the below code and got the following error;
Conversion failed when converting the varchar value '1.5' to data type int
BEGIN -- first update to check Interface held SMR and Interface held SMA
update interface set INTERR = 'U7'
from interface i
where
i.conttype = 'SMR'
and isnumeric(i.contrate)=1
and cast(i.contrate as decimal(12,2)) < 5
and caseno = #caseno
and exists (
select 1
from interface i2
where i2.caseno = #caseno
and i2.conttype = 'SMA'
and i2.intmembno = i.intmembno
and i2.effdte = i.effdte
and i2.contrate > cast(0 as decimal(12,2))
and isnumeric(i2.contrate)=1
)
In this example SMR = 5 and SMA = 1.5 and both values have been declared as Numerics. However by switching around the ordering of the clauses the error stops occuring and the stored procedure continues on as it should (see below)
BEGIN -- first update to check Interface held SMR and Interface held SMA
update interface set INTERR = 'U7'
from interface i
where
i.conttype = 'SMR'
and isnumeric(i.contrate)=1
and cast(i.contrate as decimal(12,2)) < 5
and caseno = #caseno
and exists (
select 1
from interface i2
where i2.caseno = #caseno
and isnumeric(i2.contrate)=1 -- This was moved up
and i2.conttype = 'SMA'
and i2.intmembno = i.intmembno
and i2.effdte = i.effdte
and i2.contrate > cast(0 as decimal(12,2))
)
Can you help me understand why the ordering matters? Normally it doesn't and shouldn't, as far as I know.
Thanks!
Paul
WHERE conditions are not executed in any particular order. This is even true when using subqueries and CTEs -- the optimizer rearranges processing and for good reason.
And, using implicit conversion is dangerous -- as you are finding. So, use explicit conversions and do:
where i.conttype = 'SMR' and
try_cast(i.contrate as decimal(12,2)) < 5 and
caseno = #caseno and
exists (select 1
from interface i2
where i2.caseno = #caseno and
i2.conttype = 'SMA' and
i2.intmembno = i.intmembno and
i2.effdte = i.effdte and
try_cast(i2.contrate as decimal(12,2)) > 0
)
Notes:
You do not have to check isnumeric().
There is no need to cast a constant such as 0 for the comparison.
You can do similar things with a case in pre-2012 versions of SQL Server.

Mathematical Function within Sql Case Statement

I am trying to come up with a sql statement which converts the odometer if stored in km to miles. If the odometer is stored in miles, it leaves as it is.
After the conversion, it then needs to check for Search paramters i.e Mileage.
The steps I have taken is using the Case Statement.
Here is my snippet of the select statement that I am using currently:
DECLARE
#Mileage NVARCHAR(75) = NULL,
#IsMiles BIT = 1,
#Converted NVARCHAR(75) = NULL
SELECT [Id],Odometer,IsMiles,
CASE IsMiles when 0 THEN OdometerValue * 0.62137
else Odometer end
FROM [dbo].[Vehicle]
where IsMiles = 0
Is there anyway to pass the Result of the case statement to ConvertedOdometer. I want to use that value to evaluate against the search Mileage parameters.
Something like this with this condition:
(ConvertedOdometer >=0 AND ConvertedOdometer <= #Mileage)
I am new to Case statement so have used these guides:
StackOverflow
Sql School
Some Blog
Perhaps something like this ...
DECLARE
#Mileage NVARCHAR(75) = NULL,
#IsMiles BIT = 1,
#Converted NVARCHAR(75) = NULL
select a.* from
(SELECT [Id],Odometer,IsMiles,
CASE when IsMiles=0 THEN OdometerValue * 0.62137 else Odometer end as ConvertedOdometer
FROM [dbo].[Vehicle]
where IsMiles = 0)a
where a.ConvertedOdometer >=0 AND
a.ConvertedOdometer <= #Mileage

How to use case when in SQL Server 2008

I want to use case after where clause in SQL Server 2008; how can I use it like
select *
from MPR
where
case when #min >= 0
then MainComponent_Id = #mainc and SubComponent_Id = #subcomp
else MainComponent_Id = #mainc and SubComponent_Id = #subcomp and MinorComponent_Id = #minorc
end
end
I am using a stored procedure, and in my stored procedure if minorcomponent_id value is 0 then it will add 1 one else 2 one will work.
I had tried hard but its giving error in near case.
How to resolve it?
Thanks
You don't need to use a CASE statement. You can simplify logic as follows:
select
*
from
MPR
where
MainComponent_Id = #mainc AND SubComponent_Id = #subcomp
AND (#min >= 0 OR MinorComponent_Id = #minorc)