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

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)

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!!

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?

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.

Query with integers not working

I've been searching here on stackoverflow and other sources but not found a solution to this
The query below works as expected expect for when either custinfo.cust_cntct_id or custinfo.cust_corrcntct_id = '' (blank not NULL) then I get no results. Both are integer fields and if both have an integer value then I get results. I still want a value returned for either cntct_email or corrcntct_email even if custinfo.cust_cntct_id or custinfo.cust_corrcntct_id = blank
Can someone help me out in making this work? The database is PostgreSQL.
SELECT
cntct.cntct_email AS cntct_email,
corrcntct.cntct_email AS corrcntct_email
FROM
public.custinfo,
public.invchead,
public.cntct,
public.cntct corrcntct
WHERE
invchead.invchead_cust_id = custinfo.cust_id AND
cntct.cntct_id = custinfo.cust_cntct_id AND
corrcntct.cntct_id = custinfo.cust_corrcntct_id;
PostgreSQL won't actually let you test an integer field for a blank value (unless you're using a truly ancient version - 8.2 or older), so you must be using a query generator that's "helpfully" transforming '' to NULL or a tool that's ignoring errors.
Observe this, on Pg 9.2:
regress=> CREATE TABLE test ( a integer );
CREATE TABLE
regress=> insert into test (a) values (1),(2),(3);
INSERT 0 3
regress=> SELECT a FROM test WHERE a = '';
ERROR: invalid input syntax for integer: ""
LINE 1: SELECT a FROM test WHERE a = '';
If you are attempting to test for = NULL, this is not correct. You must use IS NOT NULL or IS DISTINCT FROM NULL instead. Testing for = NULL always results in NULL, which is treated as false in a WHERE clause.
Example:
regress=> insert into test (a) values (null);
INSERT 0 1
regress=> SELECT a FROM test WHERE a = NULL;
a
---
(0 rows)
regress=> SELECT a FROM test WHERE a IS NULL;
a
---
(1 row)
regress=> SELECT NULL = NULL as wrong, NULL IS NULL AS right;
wrong | right
-------+-------
| t
(1 row)
By the way, you should really be using ANSI JOIN syntax. It's more readable and it's much easier to forget to put a condition in and get a cartesian product by accident. I'd rewrite your query for identical functionality and performance but better readability as:
SELECT
cntct.cntct_email AS cntct_email,
corrcntct.cntct_email AS corrcntct_email
FROM
public.custinfo ci
INNER JOIN public.invchead
ON (invchead.invchead_cust_id = ci.cust_id)
INNER JOIN public.cntct
ON (cntct.cntct_id = ci.cust_cntct_id)
INNER JOIN public.cntct corrcntct
ON (corrcntct.cntct_id = ci.cust_corrcntct_id);
Use of table aliases usually keeps it cleaner; here I've aliased the longer name custinfo to ci for brevity.

How do I handle Error in sql queries?

For sql queries like..
select Quantity_Books/datepart(hour,Rent_Hour) from Rent_Book where (some conditions..)
They will return error when datepart(hour,Rent_Hour) is 0.
If sth like that Happens, I would like to show 0.
I know I should use case when But I am not really sure how..
Or any other better method?
You'd simply test the value first
select
CASE
WHEN datepart(hour,Rent_Hour) = 0 THEN 0
ELSE Quantity_Books/datepart(hour,Rent_Hour)
END
from
Rent_Book where (some conditions..)
Alternatively, use NULL rules
ISNULL((Quantity_Books / (NULLIF(datepart(hour,Rent_Hour), 0))), 0)
select case when datepart(hour,Rent_Hour)<>0 then Quantity_Books/datepart(hour,Rent_Hour) else 0 end as col ...

Resources