How can I write a better stored procedure to return all records with a flag/bit field = 1 in which I pass the flag-field's name into the sproc? - sql-server

I'm dealing with a substantially more complicated schema (of course), but I think i'm including just enough in this question to show what I need and no more. (I'll update as needed based on comments.) Let's say I have this table:
create table peeps (
id int primary key identity(1,1),
name varchar(50),
eligible bit,
lefty bit,
contractor bit
)
And I want to write a sproc that's going to return the names of all my peeps if they are eligible or lefties or contractors and I want to have a single procedure to handle all those cases.
My current approach to this (which seems to totally work) looks like this:
CREATE PROCEDURE getFlagMatchingPeeps #flagName varchar(30)
select name
from peeps
where
(eligible = 1 or eligible = case when #flagName = 'eligible' then 1 else 0 end) and
(lefty = 1 or lefty = case when #flagName = 'lefty' then 1 else 0 end) and
(contractor = 1 or contractor = case when #flagName = 'contractor' then 1 else 0 end)
But that feels like an ugly solution (e.g. testing whether a value is 1 twice feels wasteful). So I'm here looking for more advanced SQL people to help me craft something better.
I guess first, is there anything wrong with my solution? If not, this can be quick. But if there is:
What is it?
What's the right approach?
How should I have identified the problem?
How should I be thinking differently in order to solve this?

If you just wanted to get rid of the extra comparison (for simplicity, this assumes that the bit columns are non-nullable contrary to the defined schema)
SELECT
[name]
FROM
[dbo].[peeps]
WHERE
([eligible] = CASE WHEN #flagName = 'eligible' THEN 1 ELSE [eligible] END) AND
([lefty] = CASE WHEN #flagName = 'lefty' THEN 1 ELSE [lefty] END) AND
([contractor] = CASE WHEN #flagName = 'contractor' THEN 1 ELSE [contractor] END);
Ignoring the fact that you are filtering on bit columns, in general there will be performance issues with queries using dynamic search parameters without the use of OPTION(RECOMPILE) or dynamic sql due to parameter sniffing. See Erland Sommarskog's article on this topic.

Related

Is there any way to optimize this query?

I need to optimize the following query:
IF object_id('tempdb..#TAB001') IS NOT NULL
DROP TABLE #TAB001;
select *
into #TAB001
from dbo.uvw_TAB001
where 1 = 1
and isnull(COD_CUSTOMER,'') = isnull(#cod_customer,isnull(COD_CUSTOMER,''))
and isnull(TAXCODE,'') = isnull(#taxcode, isnull(TAXCODE,''))
and isnull(SURNAME,'') = isnull(#surname,isnull(SURNAME,''))
and isnull(VATCODE,'') = isnull(#vatCode,isnull(VATCODE,''))
The goal is to improve the performance of this query.
It is currently quite fast but I would like to speed it up even more.
This query has the optional parameters for which it is necessary to make a query that regardless of whether all or 1 parameter is set, returns results in the shortest possible time.
What you have here is known as a "catch-all" or "kitchen sink" query, which need a little helping hand sometimes.
Firstly, you need to get rid of those ISNULLs; they are making the query non-SARGable. Also, I would suggest getting rid of the SELECT * and limiting the query to the columns you need.
Then, finally, we can add OPTION (RECOMPILE) to the query; why is discussed in the articles I linked above. This gives you the following:
SELECT * --Replace with Column Names
INTO #TAB001 --Do you actually need to do this?
FROM dbo.uvw_TAB001
--Removed WHERE 1 = 1 as it's always true, thus pointless
WHERE (COD_CUSTOMER = #cod_customer OR #cod_customer IS NULL)
AND (TAXCODE = #taxcode OR #taxcode IS NULL)
AND (SURNAME = #surname OR #surname IS NULL)
AND (VATCODE = #vatCode OR #vatCode IS NULL)
OPTION (RECOMPILE);
Note I am assuming that when a variable (for example #cod_customer) has the value NULL you mean that the variable should be "ignored" and not matched against NULL.
If you actually want {Column} = #{Variable} including NULL then use SQL with the format below instead:
({Column} = #{Variable} OR ({Column} IS NULL AND #{Variable} IS NULL))

Filter SQL datatable according to different parameters, without a WHERE clause

I'm building an application that needs to allow the user to filter a data table according to different filters. So, the user will have three different filter posibilites but he might use only one, or two or the three of them at the same tame.
So, let's say I have the following columns on the table:
ID (int) PK
Sede (int)
Programa (int)
Estado (int)
All of those columns will store numbers, integers. The "ID" column is the primary key, "Sede" stores 1 or 2, "Programa" is any number between 1 and 15, and "Estado" will store numbers between 1 and 13.
The user may filter the data stored in the table using any of those filters (Sede, Programa or Estado). But the might, as well, use two filters, or the three of them at the same time.
The idea is that this application works like the data filters on Excel. I created a simulated table on excel to show what I want to achieve:
This first image shows the whole table, without applying any filter.
Here, the user selected a filter for "Sede" and "Programa" but leaved the "Estado" filter empty. So the query returns the values that are equal to the filter, but leaves the "Estado" filter open, and brings all the records, filering only by "Sede" (1) and "Programa" (6).
In this image, the user only selected the "Estado" filter (5), so it brings all the records that match this criteria, it doesn't matter if "Sede" or "Programa" are empty.
If I use a SELECT clasuse with a WHERE on it, it will work, but only if the three filters have a value:
DECLARE #sede int
DECLARE #programa int
DECLARE #estado int
SET #sede = '1'
SET #programa = '5'
SET #estado = '12'
SELECT * FROM [dbo].[Inscripciones]
WHERE
([dbo].[Inscripciones].[Sede] = #sede)
AND
([dbo].[Inscripciones].[Programa] = #programa)
AND
([dbo].[Inscripciones].[Estado] = #estado)
I also tryed changing the "AND" for a "OR", but I can't get the desired result.
Any help will be highly appreciated!! Thanks!
common problem: try using coalesce on the variable and for the 2nd value use the field name you're comparing to. Be careful though; Ensure it's NULL and not empty string being passed!
What this does is take the first non-null value of the variable passed in or the value you're comparing to.. Thus if the value passed in is null the comparison will always return true.
WHERE
[dbo].[Inscripciones].[Sede] = coalesce(#sede, [dbo].[Inscripciones].[Sede])
AND
[dbo].[Inscripciones].[Programa] = coalesce(#programa, [dbo].[Inscripciones].[Programa])
AND
[dbo].[Inscripciones].[Estado] = coalesce(#estado, [dbo].[Inscripciones].[Estado])
If sede is null and programa and estado are populated the compare would look like...
?=? (or 1=1)
?=programa variable passed in
?=Estado variable passed in
Boa Sorte!
Thank you all for your anwers. After reading the article posted in the comments by #SeanLange I was finally able to achieve what was needed. Using a CASE clause in the WHERE statement solves the deal. Here's the code:
SELECT
*
FROM [dbo].[Inscripciones]
WHERE
([dbo].[Inscripciones].[Sede] = (CASE WHEN #sede = '' THEN [dbo].[Inscripciones].[Sede] ELSE #sede END))
AND
([dbo].[Inscripciones].[Programa] = (CASE WHEN #programa = '' THEN [dbo].[Inscripciones].[Programa] ELSE #programa END))
AND
([dbo].[Inscripciones].[Estado] = (CASE WHEN #estado = '' THEN [dbo].[Inscripciones].[Estado] ELSE #estado END))
AND
([dbo].[Inscripciones].[TipoIngreso] = (CASE WHEN #tipoingreso = '' THEN [dbo].[Inscripciones].[TipoIngreso] ELSE #tipoingreso END))
Thanks again!!

Need to use information from one qry to dictate an action on another

Ok so this is going to sound a little complicated. I want to somehow put some kind of function that will divide a table value by two when its Policy number matches up with a policy number in another table.
Here is the query where I want that functions
SELECT
qryReinsuranceDPA1_izzy.POLICY_NO,
qryReinsuranceDPA1_izzy.PHASE_CODE,
qryReinsuranceDPA1_izzy.SUB_PHASE_CODE,
qryReinsuranceDPA1_izzy.SchedNP,
qryReinsuranceDPA1_izzy.ProdType,
Sum(qryReinsuranceDPA1_izzy.SumOfAMOUNT_INFORCE) AS SumOfSumOfAMOUNT_INFORCE,
Sum(qryReinsuranceDPA1_izzy.SumOfPUA_FACE) AS SumOfSumOfPUA_FACE,
Sum(qryReinsuranceDPA1_izzy.SumOfOYT_FACE) AS SumOfSumOfOYT_FACE, TotalDPA = sum(case when qryReinsuranceDPA1_izzy.[SumOfNetDefExtraAdj] Is Null then qryReinsuranceDPA1_izzy.[SumOfNetDefPremiumAdj] else qryReinsuranceDPA1_izzy.[SumOfNetDefPremiumAdj] +qryReinsuranceDPA1_izzy.[SumOfNetDefExtraAdj] end),
qryReinsuranceDPA1_izzy.SumOfGROSS_ANNLZD_PREM,
qryReinsuranceDPA1_izzy.SumOfStatNetPremium,
qryReinsuranceDPA1_izzy.[SumOfStatNetPremium]/qryReinsuranceDPA1_izzy.[SumOfGROSS_ANNLZD_PREM] AS NetToGrossRatio,
qryReinsuranceDPA1_izzy.NetvsGrossInd,
DPA_NetPrem = case when qryReinsuranceDPA1_izzy.[NetvsGrossInd]='Net' then sum(qryReinsuranceDPA1_izzy.[SumOfStatNetPremium]) else sum([qryReinsuranceDPA1_izzy].[SumOfGROSS_ANNLZD_PREM]) end,
qryPolicyListforNYDefPRemAsset_Re.ReinType AS ReinType,
Sum(qryPolicyListforNYDefPRemAsset_Re.ReinsAmount) AS SumOfReinsAmount,
qryPolicyListforNYDefPRemAsset_Re.[ReinsAmount]/(qryReinsuranceDPA1_izzy.[SumOfAMOUNT_INFORCE]+qryReinsuranceDPA1_izzy.[SumOfOYT_FACE]+qryReinsuranceDPA1_izzy.[SumOfPUA_FACE]) AS [Reins%],
qryPolicyListforNYDefPRemAsset_Re.ReinsStatRsv AS ReinsStatRsv,
Sum(qryPolicyListforNYDefPRemAsset_Re.SumOfGROSS_ANNLZD_PREM) AS ReinsPrem, qryReinsuranceDPA1_izzy.PAID_TO_DATE,
qryReinsuranceDPA1_izzy.VAL_DATE,
qryReinsuranceDPA1_izzy.ISSUE_DATE
FROM qryPolicyListforNYDefPRemAsset_Re RIGHT JOIN
qryReinsuranceDPA1_izzy
ON
qryReinsuranceDPA1_izzy.POLICY_NO = qryPolicyListforNYDefPRemAsset_Re.POLICY_NO
AND qryReinsuranceDPA1_izzy.PHASE_CODE = qryPolicyListforNYDefPRemAsset_Re.PHASE_CODE AND
qryReinsuranceDPA1_izzy.SUB_PHASE_CODE= qryPolicyListforNYDefPRemAsset_Re.SUB_PHASE_CODE
GROUP BY
qryReinsuranceDPA1_izzy.POLICY_NO,
qryReinsuranceDPA1_izzy.PHASE_CODE,
qryReinsuranceDPA1_izzy.SUB_PHASE_CODE,
qryReinsuranceDPA1_izzy.SchedNP,
qryReinsuranceDPA1_izzy.ProdType,
qryReinsuranceDPA1_izzy.SumOfGROSS_ANNLZD_PREM,
qryReinsuranceDPA1_izzy.SumOfStatNetPremium,
qryReinsuranceDPA1_izzy.[SumOfStatNetPremium]/qryReinsuranceDPA1_izzy.[SumOfGROSS_ANNLZD_PREM],
qryReinsuranceDPA1_izzy.NetvsGrossInd,
qryPolicyListforNYDefPRemAsset_Re.ReinType,
qryPolicyListforNYDefPRemAsset_Re.[ReinsAmount]/(qryReinsuranceDPA1_izzy.[SumOfAMOUNT_INFORCE]+qryReinsuranceDPA1_izzy.[SumOfOYT_FACE]+qryReinsuranceDPA1_izzy.[SumOfPUA_FACE]),
qryPolicyListforNYDefPRemAsset_Re.ReinsStatRsv,
qryReinsuranceDPA1_izzy.PAID_TO_DATE,
qryReinsuranceDPA1_izzy.VAL_DATE,
qryReinsuranceDPA1_izzy.ISSUE_DATE
GO
and this is the qry that contains the policy numbers that I want divided by 2 in the above qry.
SELECT
qryReinsuranceDPA1.POLICY_NO,
qryReinsuranceDPA1.PHASE_CODE,
qryReinsuranceDPA1.SUB_PHASE_CODE,
qryReinsuranceDPA1.ProdType,
TotalDPA = Sum(case when [SumOfNetDefExtraAdj] Is Null then [SumOfNetDefPremiumAdj] else [SumOfNetDefPremiumAdj] + SumOfNetDefExtraAdj end),
Sum(1) AS Expr1
FROM qryPolicyListforNYDefPRemAsset_Re RIGHT JOIN qryReinsuranceDPA1
ON
qryReinsuranceDPA1.POLICY_NO = qryPolicyListforNYDefPRemAsset_Re.POLICY_NO AND
qryReinsuranceDPA1.PHASE_CODE= qryPolicyListforNYDefPRemAsset_Re.PHASE_CODE AND
qryReinsuranceDPA1.SUB_PHASE_CODE = qryPolicyListforNYDefPRemAsset_Re.SUB_PHASE_CODE
GROUP BY qryReinsuranceDPA1.POLICY_NO,
qryReinsuranceDPA1.PHASE_CODE,
qryReinsuranceDPA1.SUB_PHASE_CODE,
qryReinsuranceDPA1.ProdType
HAVING (((Sum(1))<>1))
GO
quick example. Say that the Policy number 064543200 is located in the results of that second qry. I then want the number located in first qry in Total DPA assosciated with that Policy number to be divided by 2.
If this is still confusing please let me know and I will try to explain better. Both are views.

Using a bit input in stored procedure to determine how to filter results in the where clause

I'm beating my head against the wall here... can't figure out a way to pull this off.
Here's my setup:
My table has a column for the date something was completed. If it was never completed, the field is null. Simple enough.
On the front end, I have a checkbox that defaults to "Only show incomplete entries". When only pulling incomplete entries, it's easy.
SELECT
*
FROM Sometable
WHERE Completed_Date IS NULL
But offering the checkbox option complicates things a great deal. My checkbox inputs a bit value: 1=only show incomplete, 0=show all.
The problem is, I can't use a CASE statement within the where clause, because an actual value uses "=" to compare, and checking null uses "IS". For example:
SELECT
*
FROM Sometable
WHERE Completed_Date IS <---- invalid syntax
CASE WHEN
...
END
SELECT
*
FROM Sometable
WHERE Completed_Date =
CASE WHEN #OnlyIncomplete = 1 THEN
NULL <----- this translates to "WHERE Completed_Date = NULL", which won't work.. I have to use "IS NULL"
...
END
Any idea how to accomplish this seemly easy task? I'm stumped... thanks.
...
WHERE #OnlyIncomplete = 0
OR (#OnlyIncomplete = 1 AND Completed_Date IS NULL)
Hmmm... I think what you want is this:
SELECT
*
FROM Sometable
WHERE Completed_Date IS NULL OR (#OnlyIncomplete = 0)
So that'll show Date=NULL plus, if OnlyIncomplete=0, Date != Null. Yeah, I think that's it.
If you still want to use a CASE function (although it may be overkill in this case) :
SELECT
*
FROM Sometable
WHERE 1 =
(CASE WHEN #OnlyIncomplete = 0 THEN 1
WHEN #OnlyIncomplete = 1 AND Completed_Date IS NULL THEN 1
END)

SQL Server 2008 Stored Proc Performance where Column = NULL

When I execute a certain stored procedure (which selects from a non-indexed view) with a non-null parameter, it's lightning fast at about 10ms. When I execute it with a NULL parameter (resulting in a FKColumn = NULL query) it's much slower at about 1200ms.
I've executed it with the actual execution plan and it appears the most costly portion of the query is a clustered index scan with the predicate IS NULL on the fk column in question - 59%! The index covering this column is (AFAIK) good.
So what can I do to improve the performance here? Change the fk column to NOT NULL and fill the nulls with a default value?
SELECT top 20 dbo.vwStreamItems.ItemId
,dbo.vwStreamItems.ItemType
,dbo.vwStreamItems.AuthorId
,dbo.vwStreamItems.AuthorPreviewImageURL
,dbo.vwStreamItems.AuthorThumbImageURL
,dbo.vwStreamItems.AuthorName
,dbo.vwStreamItems.AuthorLocation
,dbo.vwStreamItems.ItemText
,dbo.vwStreamItems.ItemLat
,dbo.vwStreamItems.ItemLng
,dbo.vwStreamItems.CommentCount
,dbo.vwStreamItems.PhotoCount
,dbo.vwStreamItems.VideoCount
,dbo.vwStreamItems.CreateDate
,dbo.vwStreamItems.Language
,dbo.vwStreamItems.ProfileIsFriendsOnly
,dbo.vwStreamItems.IsActive
,dbo.vwStreamItems.LocationIsFriendsOnly
,dbo.vwStreamItems.IsFriendsOnly
,dbo.vwStreamItems.IsDeleted
,dbo.vwStreamItems.StreamId
,dbo.vwStreamItems.StreamName
,dbo.vwStreamItems.StreamOwnerId
,dbo.vwStreamItems.StreamIsDeleted
,dbo.vwStreamItems.RecipientId
,dbo.vwStreamItems.RecipientName
,dbo.vwStreamItems.StreamIsPrivate
,dbo.GetUserIsFriend(#RequestingUserId, vwStreamItems.AuthorId) as IsFriend
,dbo.GetObjectIsBookmarked(#RequestingUserId, vwStreamItems.ItemId) as IsBookmarked
from dbo.vwStreamItems WITH (NOLOCK)
where 1 = 1
and vwStreamItems.IsActive = 1
and vwStreamItems.IsDeleted = 0
and vwStreamItems.StreamIsDeleted = 0
and (
StreamId is NULL
or
ItemType = 'Stream'
)
order by CreateDate desc
When it's not null, do you have
and vwStreamItems.StreamIsDeleted = 0
and (
StreamId = 'xxx'
or
ItemType = 'Stream'
)
or
and vwStreamItems.StreamIsDeleted = 0
and (
StreamId = 'xxx'
)
You have an OR clause there which is most likely the problem, not the IS NULL as such.
The plans will show why: the OR forces a SCAN but it's manageable with StreamId = 'xxx'. When you use IS NULL, you lose selectivity.
I'd suggest changing your index make StreamId the right-most column.
However, a view is simply a macro that expands so the underlying query on the base tables could be complex and not easy to optimise...
The biggest performance gain would be for you to try to loose GetUserIsFriend and GetObjectIsBookmarked functions and use JOIN to make the same functionality. Using functions or stored procedures inside a query is basically the same as using FOR loop - the items are called 1 by 1 to determine the value of a function. If you'd use joining tables instead, all of the items values would be determined together as a group in 1 pass.

Resources