optional parameter checking in where clauses - sql

I have a bunch of report parameters and as a result my criteria checking first checks if parameter value is null and if not compares it with a column value .
(#dateStart IS NULL OR #dateStart <= BELGE.AccDate)
AND (#dateEnd IS NULL OR #dateEnd >= BELGE.AccDate)
AND (#CompanyId IS NULL OR #CompanyId = hrktlr.CompanyId)
AND ((#onKayitlarDahil = 1 and hrktlr.StatusCode in ('M', 'O'))
OR (#onKayitlarDahil = 0 AND hrktlr.StatusCode = 'M'))
AND (#BizPartnerId IS NULL or CK.BizPartnerId = #BizPartnerId)
AND (#BizPartnerKodStart is null or #BizPartnerKodStart = '' or #BizPartnerKodStart <= CK.BizPartnerKod)
AND (#BizPartnerKodEnd is null or #BizPartnerKodEnd = '' or #BizPartnerKodEnd >= CK.BizPartnerKod)
AND (#BizPartnerType is null or #BizPartnerType=CK.BizPartnerType)
this is great for a maintainable sql query, but the problem is that Sql Query Optimizer prepares itself for the worst case I guess, and index usage is bad. For example when I pass in BizPartnerId and thus avoid BizPartnerId is null check, query runs a 100 times faster.
So if I keep going with this approach are there any pointers that you can recommend for Query Planner to help increase query performance.
Any viable alternatives to optional parameter checking?

To stop sql server form saving a sub optimal query plan you can use the option WITH RECOMPILE. The query plan will be recalculated each time you run the query.

Related

Is there any option to replace NVL in where clause for parameter

I have been using NVL in my WHERE clause and it worked well till now.
But in such case where the column has NULL value and parameter was also NULL, it didnt return any query.
select * from Table
where
f_date BETWEEN NVL(:F_DATE_FROM,F_DATE) AND NVL(:F_DATE_TO,F_DATE)
AND op_code = NVL(:CODE, OP_CODE)
AND T_CBC = NVL(:TO_CBC,T_CBC)
order by fiscal_date desc
I updated the query as below, and it returns me all the records as expected. However it takes way too long to execute the query. The original query took 1.5min and the new query takes 7min. Is there any way to fine tune the below query please?
select * from Table
where
f_date BETWEEN NVL(:F_DATE_FROM,F_DATE) AND NVL(:F_DATE_TO,F_DATE)
AND (OP_CODE = :CODE or :CODE is null)
AND (T_CBC = :TO_CBC or :TO_CBC is null)
order by fiscal_date desc
Sure:
WHERE
(f_date >= :F_DATE_FROM OR :F_DATE_FROM IS NULL) AND
(f_date <= :F_DATE_TO OR :F_DATE_TO IS NULL) AND
...
though I'm not sure how much of a performance improvement it'll realize. If your query is about performance specifically, ask a question that includes a query plan

Where statement in my query is supposed to return only today's and yesterday's dates, but is still returning earlier dates

I am looking to only retrieve data from the past 24 hours. The WHERE statement I am using, in theory, should retrieve only from those productiondates. However, I am still having week-old productiondates returned. Any thoughts on how to improve this, or am I doing it wrong? I am using periscope.
select example1,
example2,
example3,
productiondate,
example4,
example5
from final
where exampleX = exampleY or exampleX is null
and productiondate > DATEADD(day,-1, GETDATE())
and example1 <> 'XXX'
and example2 <> 'YYY'
and example2 <> 'ZZZ'
order by 2
Logical operator precedence in SQL can be surprising. You need parentheses around the OR.
where (exampleX = exampleY or exampleX is null)
Alternatively, you could do this:
where coalesce(exampleX, exampleY) = exampleY

Query optimization beyond indexes

I wrote this query that 'cubes' some data writing partial totals:
select upper(coalesce(left(k.SubStabilimento,12),'ALL')) as Stabilimento,
sum(k.PotenzialmenteInappropriato) as Numeratore,
count(k.ProgrSdo)-sum(k.PotenzialmenteInappropriato) as Denominatore,
case when (count(k.ProgrSdo)-sum(k.PotenzialmenteInappropriato)) > 0 then 1.0*sum(k.PotenzialmenteInappropriato) / (count(k.ProgrSdo)-sum(k.PotenzialmenteInappropriato)) else 0 end as Rapporto,
upper(coalesce(DescrDisciplina,'ALL')) AS Disciplina,
case when K.TipologiaDRG = 'C' then 'CHIR.'
when K.TipologiaDRG = 'M' then 'MED.'
when K.TipologiaDRG is null then 'ALL'
when K.TipologiaDRG = '' then 'SENZA TIPO'
end as TipoDRG,
case when [Anno]=#anno then 'ATTUALE'
when [Anno]=#anno-1 then 'PRECEDENTE'
else cast([Anno] as varchar(4))
end as Periodo,
upper(coalesce(left(k.mese,2), 'ALL')) as Mese,
upper(coalesce(NomeMese,'ALL')) as MeseDescr
from
tabella k
where k.Mese <= #mese
and k.anno between #anno-1 and #anno
and k.RegimeRicovero = 1
and codicepresidio=080808
and TipologiaFlusso like 'Pro%'
group by SubStabilimento, DescrDisciplina, TipologiaDRG, anno, mese,nomemese with cube
having grouping(anno) = 0
AND GROUPING(nomeMese) = GROUPING(mese)
this groovy code is added runtime according to parameters value that have to be passed to the query:
if ( parameters.get('par_stabilimenti').toUpperCase() != "'TUTTO'" )
{ query = query + "and upper(coalesce(left(k.SubStabilimento,12),'AUSL_TOTALE')) in ("+ parameters.get('par_stabilimenti').toUpperCase() +" )";}
if ( parameters.get('par_discipline').toUpperCase() != "'TUTTO'" )
{ query = query + "and upper(coalesce(k.DescrDisciplina,'TOT. STABILIMENTO')) in ("+ parameters.get('par_discipline').toUpperCase() +" )";}
SQL parameters are passed by the application runtime
I did (manually) all indexing on single columns and on table primary key, I also added indexes suggested by sql server query tuner.
Now it still takes too long to execute (about 4"), now I need to have it running 8 time faster.
Is there some optimization I can do on the query? (parameters are passed by the application)
Is there a way I can precalculate execution plan,so sql server don't have to re-do it all the times I launch the query?
I really don't have an idea how to improve performances beyond whayt I already did.
I'm on SQL Server 2018 pro (so no columnstore indexes)
Here you can find the execution plan.

Include NULL values in a designated field in my where clause on SSRS

I am learning SQL so be gentle. If I have designated a specific role in my where clause it is only pulling those cases where that role is populated. How can I also include the NULL values or those roles that are blank?
Here is the where clause now:
WHERE (dbo.vcases.lawtype = 'My Cases') AND
(dbo.vcase_parties_people.role_sk = 4001) AND
**(v1.role_sk = 3940) AND
(v1.report_ind = 'Y') AND
(v2.role_sk = 3939) AND
(v2.report_ind = 'Y')** AND
(dbo.vcases.case_type NOT IN ('Case type 1', 'Case type 2'))
The COALESCE() expression in SQL is useful for substituting a default value when NULL is encountered for a given column or expression. When the query optimizer encounters a COALESCE() call, it will internally rewrite that expression to an equivalent CASE...WHEN expression. In your sample query, WHERE (COALESCE(v1.role_sk, 3940) = 3940) would operate (and optimize) the same as WHERE (CASE WHEN v1.role_sk IS NOT NULL THEN v1.role_sk ELSE 3940 END = 3940).
Since your example specifically involves a condition in the WHERE clause, you may want to use an OR operation, which could optimize better than a COALESCE() expression: WHERE (v1.role_sk = 3940 OR v1.role_sk IS NULL).
This is also assuming that any joins in your query aren't filtering out rows whose role_sk column is NULL.
You might edit your code as follows:
WHERE (dbo.vcases.lawtype = 'My Cases') AND
(dbo.vcase_parties_people.role_sk = 4001) AND
(v1.role_sk = 3940 OR v1.role_sk IS NULL) AND
(v1.report_ind = 'Y') AND
(v2.role_sk = 3939) AND
(v2.report_ind = 'Y') AND
(dbo.vcases.case_type NOT IN ('Case type 1', 'Case type 2'))
The use of the Coalesce function has been suggested but a good rule of thumb in SQL is to avoid the use of functions in the WHERE clause because it reduces the efficiency of the table's indexes. Functions in WHERE clause often cause Index-Scans instead of the more efficient Index-Seeks.

How to create dynamic WHERE select without COALESCE

I have found out that my SQL 2008 R2 database is really struggling with COALESCE function if used within search.
CODE:
where
i.id_categ = COALESCE(#id_categ, i.id_categ )
and i.id_brand = COALESCE(#id_brand , i.id_brand )
and i.id_model = COALESCE(#id_model , i.id_model )
and i.id_type = COALESCE(#id_karoseria, i.id_type )
and i.id_fuel = COALESCE(#id_palivo, i.id_fuel )
and (i.year between #year_from and #year_to)
and (i.price between #price_from and #price_to)
DYNAMIC variables:
ALTER PROCEDURE [dbo].[spInzeratSelect]
#id_categ int = null,
#id_brand int = null,
#id_model int = null,
#id_fuel int = null,
#id_type int = null,
Search should work with or without these variables.
Benchmark:
with COALESCE = Total Execution Time: 3582
without COALESCE conditions = Total Execution Time: 13
You get the difference ...
Is there a nice solution how to ignore COALESCE and create dynamic SQL select with different approch ?
Thanks.
tIn your very specific case you should replace all your COALESCE search parameters with the following pattern:
AND ( (#id_brand IS NULL) OR (i.id_brand = #id_brand) )
etc
The parameter is evaluated as a literal before execution, so doing it this way makes the condition sargable.
This is functionally equivalent to your query except you're first checking against the literal value, which can be optimized away if it is, in fact, null.
EDIT: Apparently this is the approach recommended in #Joe Stefanelli's link as well. I originally poached it from Erland Sommerskog.
EDIT2: And I always forget to mention OPTION (RECOMPILE) too.