Is there a faster way to run an SQL Where Case - sql

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.

Related

How to get rid of Cursor and use UPDATE with SELECT

I believe that the cursor used in this code is the reason for some major performance issues, however I am new to TSQL.
Following script runs on SQL SERVER 2008. I am trying to redo it so I use JOIN statements instead, however I have not been able to do so successfully.
DECLARE AIRAMSDET CURSOR FOR
SELECT BILL, RECIEPT, NAME
FROM Client_Table
WHERE IsProcessed = 1
AND TYPE IN ('Sub','First_Time','Old') AND LEN(BILL) > 1
OPEN AIRAMSDET
FETCH AIRAMSDET into #VARBILL, #VARRECIEPT, #VARNAME
WHILE ##Fetch_Status = 0
BEGIN
UPDATE archieve
SET entry = left(#VARBILL + '- '+ #VARNAME)
WHERE archiveID = #VARBILL
END
It should be something like following
UPDATE ARCHIEVE
SET ENTRY = CT.BILL + '-' + CT.NAME
FROM CLIENT_TABLE CT
WHERE
ARCHIEVE.ARCHIVEID = CT.BILL
AND CT.ISPROCESSED = 1
AND CT.TYPE IN ('Sub','First_Time','Old') AND LEN(BILL) > 1
I have not included LEFT() as its use in your query wasn't very clear. Left takes an integer_expression as its second parameter while you are passing ##VARNAME which most likely is a VARCHAR. Please add that as you deem fit.

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

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

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).

MSSQL 2008: Problems with the 'Case' - statement

I'm having some troubles finding a solution to my SQL-Problem. I've tried google but so far my search didn't give me any statisfactory results.
I have an SSRS report with two parameters:
#SupplierId NVARCHAR (May contain NULL)
#EmployeeId NVARCHAR (May contain NULL)
My original query retrieved all the employees who came in service during the last year:
SELECT Name, Surname from dbo.Employee Where Employee.DateInService > DATEADD(year,-1,GETDATE())
Right now i want to add those parameters to the query using the following logic.
Remark this is pseudo SQL:
SELECT
...
FROM ...
WHERE Employee.DateInService > DATEADD(year,-1,GETDATE()) AND
IF (LEN(RTRIM(#SupplierId)) = 0 Or #SupplierID IS NULL ) THEN
dbo.Employee.EmployeeId = #EmployeeId
Else
dbo.Employee.SupplierId = #SupplierId
My search sofar led me to the Case statement. I made a query which contains an syntax error (obviously). My base query:
SELECT
...
FROM ...
WHERE Employee.DateInService > DATEADD(year,-1,GETDATE()) AND
CASE WHEN (LEN(RTRIM(#SupplierId)) = 0) THEN
dbo.Employee.EmployeeId = #EmployeeId
Else
dbo.Employee.SupplierId = #SupplierId
Error: Syntax error near '='.
Question 1: Why does he give an error near the '='?
Question 2: How do i correctly implement the following:
CASE WHEN (LEN(RTRIM(#SupplierId)) = 0 "Or #SupplierId is null" ) THEN
Instead of
CASE WHEN (LEN(RTRIM(#SupplierId)) = 0) Then dbo.Employee.EmployeeId = #EmployeeId
WHEN (#SupplierId IS NULL) Then dbo.Employee.EmployeeId = #EmployeeId
ELSE dbo.Employee.EmployeeId = #EmployeeId END
Note: if i've missed a post during my google searches, please don't hesitate to point it out.
Thanks for your help
You can't change the actual query predicate like that with CASE - there are 2 distinct queries depending on the value of #SupplierId. You can conditionally apply the filter as follows (I've assumed the #SupplierId = null flow is the same as the whitespace branch:
SELECT
...
FROM ...
WHERE Employee.DateInService > DATEADD(year,-1,GETDATE())
AND
(
(dbo.Employee.EmployeeId = #EmployeeId
AND (LEN(RTRIM(#SupplierId)) = 0 OR #SupplierId IS NULL))
OR
(dbo.Employee.SupplierId = #SupplierId AND LEN(RTRIM(#SupplierId)) > 0)
)
Although this can be prone to query plan sniffing related performance issues, in which case you might need to consider an alternative approach, e.g. using parameterized dynamic sql to build up and execute the sql, as there are 2 distinct process flows through the query.
Edit
As per Ypercube's comment above, in order to provide the boolean result needed for the predicate, if you can find a hack workaround is to find a way to project a COMMON scalar from each of the CASE .. WHEN row and then do a comparison of the scalar. In the example below, projecting a yes / no flag.
SELECT * FROM dbo.Employee
WHERE
CASE
WHEN (LEN(RTRIM(#SupplierId)) = 0 OR #SupplierId IS NULL)
THEN CASE WHEN dbo.Employee.EmployeeId = #EmployeeId THEN 1 ELSE 0 END
ELSE CASE WHEN dbo.Employee.SupplierId = #SupplierId THEN 1 ELSE 0 END
END = 1;
But the big problem with this approach is performance - the above will require a full scan to determine the results.

Nhibernate Criteria Conditional Where

Im working on a NHibernate criteria wich i graduatly builds upp depending on input parameters.
I got some problem with the postal section of these paramters.
Since we got a 5 number digit zipcodes the input parameter is a int, but since we in database also accept foreign zipcodes the database saves it as string.
What im trying to replicate in NHibernate Criteria/Criterion is the following where clause.
WHERE
11182 <=
(case when this_.SendInformation = 0 AND dbo.IsInteger(this_.Zipcode) = 1 then
CAST(REPLACE(this_.Zipcode, ' ', '') AS int)
when this_.SendInformation = 1 AND dbo.IsInteger(this_.WorkZipcode) = 1 then
CAST(REPLACE(this_.WorkZipcode, ' ', '') AS int)
when this_.SendInformation = 2 AND dbo.IsInteger(this_.InvoiceZipcode) = 1 then
CAST(REPLACE(this_.InvoiceZipcode, ' ', '') AS int)
else
NULL
end)
What we do is to check where the member contact (this_) has preferenced to get information sent to, then we check the input zipcode as integer against three different columns depending on if the column is convertable to int (IsInteger(expr) function) if column is not convertable we mark the side as NULL
in this case we just check if the zipcode is >= input parameter (reversed in sql code since paramter is first), the goal is to do a between (2 clauses wrapped with 'AND' statement), >= or <=.
UPDATE
Got a hint of success.
Projections.SqlProjection("(CASE when SendInformation = 0 AND dbo.IsInteger(Zipcode) = 1 then CAST(REPLACE(Zipcode, ' ', '') AS int) when SendInformation = 1 AND dbo.IsInteger(WorkZipcode) = 1 then CAST(REPLACE(WorkZipcode, ' ', '') AS int) when SendInformation = 2 AND dbo.IsInteger(InvoiceZipcode) = 1 then CAST(REPLACE(InvoiceZipcode, ' ', '') AS int) else NULL END)"
, new[] { "SendInformation", "Zipcode", "WorkZipcode", "InvoiceZipcode" },
new[] { NHibernateUtil.Int32, NHibernateUtil.String, NHibernateUtil.String, NHibernateUtil.String });
Throw my whole clause in a Projections.SqlProjection, however when i run my code some of my projection is cut (" AS int) else NULL END)" is cut from the end) and makes the sql corrupt.
Is there some kind of limit on this ?
Got it working yesterday.
Projections.SqlProjection worked, however if you don't name the projection as a column it some how cuts some of the TSQL code.
(Case
when x = 1 then 'bla'
when x = 2 then 'bla_bla'
else NULL
END) as foo
when using the last part (as foo) and naming the entire case syntax it works and dont cut anything.
However i dont know why but i could not manage to use the aliases from the other part of the criteria.