Using CASE Statement inside IN Clause - sql

Is is possible to use a CASE statement inside an IN clause?
This is a simplified version of what I have been trying to get to compile correctly:
SELECT * FROM MyTable
WHERE StatusID IN (
CASE WHEN #StatusID = 99 THEN (5, 11, 13)
ELSE (#StatusID) END )
Thanks!

CASE returns a scalar value only. You can do this instead. (I am assuming, as per your example, that when #StatusID = 99, a StatusID value of 99 is not a match.)
select *
from MyTable
where (#StatusID = 99 and StatusID in (5, 11, 13))
or (#StatusID <> 99 and StatusID = #StatusID)

No. Instead, you can put it outside
SELECT *
FROM MyTable
WHERE 1 = (CASE WHEN #StatusID = 99 and StatusId in (5, 11, 13) then 1
WHEN coalesce(#StatusId, 0) <> 99 and StatusId in (#StatusID) then 1
ELSE 0
END)
You can also write this without the case statement.
Another option is dynamic SQL, where you actually create a string with the SQL statement and then execute it. However, dynamic SQL seems like overkill in this case.

I thought I'd attempt this differently using a Table Valuue Constructor - are TVCs not allowed in the following context?
SELECT *
FROM MyTable
WHERE StatusID IN
(
SELECT
CASE
WHEN #StatusID = 99 THEN (values(5),(11),(13)) t(StatusID )
ELSE #StatusID
END
)

You can do this using TVCs, but the approach is a little different. It doesn't use case, but it will scale more nicely where there are a number of possible options to choose from:
SELECT *
FROM MyTable
join (values
(99,5),(99,11),(99,13),
(#StatusID , #StatusID)
) t(k,v) on t.k= #StatusID and t.v = StatusID)
or if you need everything in the where clause then:
SELECT *
FROM MyTable
WHERE exists (
select 1
from (values
(99,5),(99,11),(99,13),
(#StatusID , #StatusID)
) t(k,v)
where t.k= #StatusID and t.v = StatusID)

Related

Conditionally modify query based on parameter

I have this query (something like a case statement which I can use and fix it)
select *
from mytable
where 1=1
and (isNull(ID, 0) = 0 OR UtilityID IN (9,40))
I also want to add another statement
select *
from mytable
where 1=1
and UtilityID NOT IN (9,40)
Everything is happening in a procedure, so want to use a variable like declare #something so if that is passed as 1, use the first statement and the if 0 is passed, use the latter one.
While I appreciate the genius in Dale's answer I find this more readable:
IF #something = 0
BEGIN
select *
from mytable
where ID IS NULL OR ID = 0 OR UtilityID IN (9,40);
END
IF #something = 1
BEGIN
select *
from mytable
where UtilityID NOT IN (9,40);
END
It's procedure code, so use IF to direct the control flow. Also expanded and simplified your where clauses
I think I understand your logic, ignoring the 1=1 (which does nothing) you want to only allow id = 0 when #something = 1. This should do it:
declare #something bit = 0;
declare #mytable table (ID int, UtilityID int);
insert into #mytable (ID, UtilityID)
select 0, 1 union all
select 1, 2 union all
select 2, 9 union all
select 3, 40;
select *
from #mytable
where (
(#something = 1 and (isnull(ID, 0) = 0 or UtilityID in (9,40)))
or (#something = 0 and (UtilityID not in (9,40)))
);
A more performant approach for a larger query could be:
select *
from #mytable
where (#something = 1 and (isnull(ID, 0) = 0 or UtilityID in (9,40)))
union all
select *
from #mytable
where (#something = 0 and (UtilityID not in (9,40)));
PS: Hopefully your ID cannot ever by null - it should have a constraint on it.

When #parm =-1 return with IN, else return #parm value

I would like to filter my query but do know the syntax:
Here is my incorrect syntax:
WHERE
(#StatusId = - 1 then Status.StatusId IN (1, 2, 3, 4))
else
Status.StatusId = #StatusId)
WHERE
(#StatusId = -1 AND Status.StatusID IN (1,2,3,4))
OR
(#StatusId <> -1 AND Status.StatusID = #StatusId)
Note that #StatusId <> -1 returns false if #StatusId is null. I'm assuming that the equality check should only happen if #StatusId does not equal -1. If that's not the case, then you can remove that part from the second clause.
You need to use Boolean logic. Try this:
WHERE
((#StatusId = - 1 AND Status.StatusId IN (1, 2, 3, 4)) OR Status.StatusId = #StatusId)
Note that the whole statement is in parenthesis.
You could do it like this, but it can cause query performance problems doing it this way (search for parameter sniffing). Better would be to generate the correct SQL dynamically depending on the value of #StatusID and only run the bit you need.
Where (
#StatusID = -1 And
Status.StatusID In (1, 2, 3, 4)
) Or (
#StatusID != -1 And
Status.StatusID = #StatusID
)

Can a UDF return a list that can be used with an "IN" keyword?

With this SQL snippet from my SPROC:
-- Having the PanelPayout role gives you more transaction types to look for, hence the CASE statement.
CASE WHEN EXISTS (SELECT RoleID FROM aspnet_UsersInRoles ur WHERE ur.RoleID = 'DEDCD456-A25A-43C5-8125-A1D1223B19EC' AND ur.UserID = s.aspnet_UserID) THEN
(SELECT ISNULL(SUM(Amount), 0) FROM TransactionsLog payouts WHERE
payouts.StaffID = s.ID
AND payouts.TransactionType IN (48, 49, 3, 16, 292, 293)
AND (payouts.OrderDate >= dbo.Date(#date) AND payouts.OrderDate <= dbo.EndOfDay(#date))
AND #shift = CASE #shift WHEN 0 THEN 0 ELSE dbo.GetShiftByDate(payouts.OrderDate) END
)
ELSE
(SELECT ISNULL(SUM(Amount), 0) FROM TransactionsLog payouts WHERE
payouts.StaffID = s.ID
AND payouts.TransactionType IN (48, 49, 3)
AND (payouts.OrderDate >= dbo.Date(#date) AND payouts.OrderDate <= dbo.EndOfDay(#date))
AND #shift = CASE #shift WHEN 0 THEN 0 ELSE dbo.GetShiftByDate(payouts.OrderDate) END
)
END
And this works fine. However, overall, the query is not returning what I want due to some issues in how the database was originally structured 7 years ago. So, I'm going to use C# to do what I need to do. My question is can a UDF return a list of ints that could be used with IN, so I would only need to write
(SELECT ISNULL(SUM(Amount), 0) FROM TransactionsLog payouts WHERE
payouts.StaffID = s.ID
AND payouts.TransactionType IN dbo.PayoutTransactionsValidForStaff(s.ID)
AND (payouts.OrderDate >= dbo.Date(#date) AND payouts.OrderDate <= dbo.EndOfDay(#date))
AND #shift = CASE #shift WHEN 0 THEN 0 ELSE dbo.GetShiftByDate(payouts.OrderDate) END
)
where PayoutTransactionsValidForStaff(s.ID) would return the list of ints needed to check? I would imagine it could be a table-valued function, but I don't have time to test because I gotta get this update shipped. Just wondering if anyone knew off the top of their head. Thanks!
Yes, you can create a table valued function in SQL which returns a table instead of a scalar result. You can use the result of this in a Sql in statement
http://msdn.microsoft.com/en-us/library/ms191165(v=sql.105).aspx
CREATE FUNCTION dbo.Test()
RETURNS #funcTable TABLE
(
-- Columns returned by the function
ID int PRIMARY KEY NOT NULL
)
AS
-- Return some arbitrary ints
BEGIN
INSERT #funcTable VALUES (1)
INSERT #funcTable VALUES (2)
RETURN
END
GO
-- Test it - insert 1,2,3 into a table var
DECLARE #SomeTable table (ID int)
INSERT INTO #SomeTable VALUES (1)
INSERT INTO #SomeTable VALUES (2)
INSERT INTO #SomeTable VALUES (3)
-- Select from #SomeTable where the ID is in the result of the UDF
select * from #SomeTable WHERE ID IN (SELECT ID FROM dbo.Test())

How to know if all the cells have the same value in some column

How to know if all the cells have the same value in some column (title changed)
I want to have a bit scalar value that tells me if all the values in a column equal something:
DECLARE #bit bit
SELECT #bit = TRUEFORALL(Name IS NOT NULL) FROM Contact
UPDATE
I now realized that I actually don't need the TrueForAll, what I do need is to make sure, that all values in a column are equal, for example, I want to know whether all Group.Items have the same price.
Why not?
select count( distinct price) from table
If returns 1, all values are the same... Add
where price is not null
if need be
For your updated requirement something like this would appear to do what you want:
DECLARE #IsSameGroup bit
SELECT #IsSameGroup = CASE WHEN COUNT(*) > 1 THEN 0 ELSE 1 END
FROM (SELECT Name FROM Contact GROUP BY Name) groups
When the count is greater the 1 you have two different names (or prices depending on what you group on)
Not very good for NULLs, but 2008 can do:
SELECT 1 WHERE 'Blue' = ALL ( SELECT Color FROM dbo.Hat )
OR
DECLARE #bit bit
SET #bit =
CASE ( SELECT 1 WHERE 'Blue' = ALL ( SELECT Color FROM dbo.Hat ))
WHEN 1 THEN 1 ELSE 0 END
UPDATE
All same color
SET #bit =
CASE(
SELECT 1 WHERE
(SELECT TOP(1) Color FROM dbo.Hat) = ALL ( SELECT Color FROM dbo.Hat )
)
WHEN 1 THEN 1 ELSE 0 END
Maybe this?
DECLARE #bit bit
if exists(SELECT Name FROM Contact WHERE Name IS NULL)
SET #bit = 0
ELSE
SET #bit = 1
This solves your first question:
SELECT
CASE
WHEN EXISTS(
SELECT 1
FROM Contact
WHERE Name IS NULL
) THEN 0
ELSE 1
END
ADDED:
This will solve your second:
SELECT
CASE
WHEN EXISTS(
SELECT TOP 1 1 FROM (
SELECT
ItemGroupName,
COUNT(Price) AS CNT
FROM ItemGroup
GROUP BY ItemGroupName
HAVING COUNT(Price) > 1
) t
) THEN 0
ELSE 1
END
By the way, when you use the exists function, its better to SELECT 1 (a constant) so less data gets returned

SQL Switch/Case in 'where' clause

I tried searching around, but I couldn't find anything that would help me out.
I'm trying to do this in SQL:
declare #locationType varchar(50);
declare #locationID int;
SELECT column1, column2
FROM viewWhatever
WHERE
CASE #locationType
WHEN 'location' THEN account_location = #locationID
WHEN 'area' THEN xxx_location_area = #locationID
WHEN 'division' THEN xxx_location_division = #locationID
I know that I shouldn't have to put '= #locationID' at the end of each one, but I can't get the syntax even close to being correct. SQL keeps complaining about my '=' on the first WHEN line...
How can I do this?
declare #locationType varchar(50);
declare #locationID int;
SELECT column1, column2
FROM viewWhatever
WHERE
#locationID =
CASE #locationType
WHEN 'location' THEN account_location
WHEN 'area' THEN xxx_location_area
WHEN 'division' THEN xxx_location_division
END
without a case statement...
SELECT column1, column2
FROM viewWhatever
WHERE
(#locationType = 'location' AND account_location = #locationID)
OR
(#locationType = 'area' AND xxx_location_area = #locationID)
OR
(#locationType = 'division' AND xxx_location_division = #locationID)
Here you go.
SELECT
column1,
column2
FROM
viewWhatever
WHERE
CASE
WHEN #locationType = 'location' AND account_location = #locationID THEN 1
WHEN #locationType = 'area' AND xxx_location_area = #locationID THEN 1
WHEN #locationType = 'division' AND xxx_location_division = #locationID THEN 1
ELSE 0
END = 1
I'd say this is an indicator of a flawed table structure. Perhaps the different location types should be separated in different tables, enabling you to do much richer querying and also avoid having superfluous columns around.
If you're unable to change the structure, something like the below might work:
SELECT
*
FROM
Test
WHERE
Account_Location = (
CASE LocationType
WHEN 'location' THEN #locationID
ELSE Account_Location
END
)
AND
Account_Location_Area = (
CASE LocationType
WHEN 'area' THEN #locationID
ELSE Account_Location_Area
END
)
And so forth... We can't change the structure of the query on the fly, but we can override it by making the predicates equal themselves out.
EDIT: The above suggestions are of course much better, just ignore mine.
The problem with this is that when the SQL engine goes to evaluate the expression, it checks the FROM portion to pull the proper tables, and then the WHERE portion to provide some base criteria, so it cannot properly evaluate a dynamic condition on which column to check against.
You can use a WHERE clause when you're checking the WHERE criteria in the predicate, such as
WHERE account_location = CASE #locationType
WHEN 'business' THEN 45
WHEN 'area' THEN 52
END
so in your particular case, you're going to need put the query into a stored procedure or create three separate queries.
OR operator can be alternative of case when in where condition
ALTER PROCEDURE [dbo].[RPT_340bClinicDrugInventorySummary]
-- Add the parameters for the stored procedure here
#ClinicId BIGINT = 0,
#selecttype int,
#selectedValue varchar (50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT
drugstock_drugname.n_cur_bal,drugname.cdrugname,clinic.cclinicname
FROM drugstock_drugname
INNER JOIN drugname ON drugstock_drugname.drugnameid_FK = drugname.drugnameid_PK
INNER JOIN drugstock_drugndc ON drugname.drugnameid_PK = drugstock_drugndc.drugnameid_FK
INNER JOIN drugndc ON drugstock_drugndc.drugndcid_FK = drugndc.drugid_PK
LEFT JOIN clinic ON drugstock_drugname.clinicid_FK = clinic.clinicid_PK
WHERE (#ClinicId = 0 AND 1 = 1)
OR (#ClinicId != 0 AND drugstock_drugname.clinicid_FK = #ClinicId)
-- Alternative Case When You can use OR
AND ((#selecttype = 1 AND 1 = 1)
OR (#selecttype = 2 AND drugname.drugnameid_PK = #selectedValue)
OR (#selecttype = 3 AND drugndc.drugid_PK = #selectedValue)
OR (#selecttype = 4 AND drugname.cdrugclass = 'C2')
OR (#selecttype = 5 AND LEFT(drugname.cdrugclass, 1) = 'C'))
ORDER BY clinic.cclinicname, drugname.cdrugname
END
Please try this query.
Answer To above post:
select #msgID, account_id
from viewMailAccountsHeirachy
where
CASE #smartLocationType
WHEN 'store' THEN account_location
WHEN 'area' THEN xxx_location_area
WHEN 'division' THEN xxx_location_division
WHEN 'company' THEN xxx_location_company
END = #smartLocation
Try this:
WHERE (
#smartLocationType IS NULL
OR account_location = (
CASE
WHEN #smartLocationType IS NOT NULL
THEN #smartLocationType
ELSE account_location
END
)
)
CREATE PROCEDURE [dbo].[Temp_Proc_Select_City]
#StateId INT
AS
BEGIN
SELECT * FROM tbl_City
WHERE
#StateID = CASE WHEN ISNULL(#StateId,0) = 0 THEN 0 ELSE StateId END ORDER BY CityName
END
Try this query, it's very easy and useful: Its ready to execute!
USE tempdb
GO
IF NOT OBJECT_ID('Tempdb..Contacts') IS NULL
DROP TABLE Contacts
CREATE TABLE Contacts(ID INT, FirstName VARCHAR(100), LastName VARCHAR(100))
INSERT INTO Contacts (ID, FirstName, LastName)
SELECT 1, 'Omid', 'Karami'
UNION ALL
SELECT 2, 'Alen', 'Fars'
UNION ALL
SELECT 3, 'Sharon', 'b'
UNION ALL
SELECT 4, 'Poja', 'Kar'
UNION ALL
SELECT 5, 'Ryan', 'Lasr'
GO
DECLARE #FirstName VARCHAR(100)
SET #FirstName = 'Omid'
DECLARE #LastName VARCHAR(100)
SET #LastName = ''
SELECT FirstName, LastName
FROM Contacts
WHERE
FirstName = CASE
WHEN LEN(#FirstName) > 0 THEN #FirstName
ELSE FirstName
END
AND
LastName = CASE
WHEN LEN(#LastName) > 0 THEN #LastName
ELSE LastName
END
GO
In general you can manage case of different where conditions in this way
SELECT *
FROM viewWhatever
WHERE 1=(CASE <case column or variable>
WHEN '<value1>' THEN IIF(<where condition 1>,1,0)
WHEN '<value2>' THEN IIF(<where condition 2>,1,0)
ELSE IIF(<else condition>,1,0)
END)
Case Statement in SQL Server Example
Syntax
CASE [ expression ]
WHEN condition_1 THEN result_1
WHEN condition_2 THEN result_2
...
WHEN condition_n THEN result_n
ELSE result
END
Example
SELECT contact_id,
CASE website_id
WHEN 1 THEN 'TechOnTheNet.com'
WHEN 2 THEN 'CheckYourMath.com'
ELSE 'BigActivities.com'
END
FROM contacts;
OR
SELECT contact_id,
CASE
WHEN website_id = 1 THEN 'TechOnTheNet.com'
WHEN website_id = 2 THEN 'CheckYourMath.com'
ELSE 'BigActivities.com'
END
FROM contacts;
This worked for me.
CREATE TABLE PER_CAL ( CAL_YEAR INT, CAL_PER INT )
INSERT INTO PER_CAL( CAL_YEAR, CAL_PER ) VALUES ( 20,1 ), ( 20,2 ), ( 20,3 ), ( 20,4 ), ( 20,5 ), ( 20,6 ), ( 20,7 ), ( 20,8 ), ( 20,9 ), ( 20,10 ), ( 20,11 ), ( 20,12 ),
( 99,1 ), ( 99,2 ), ( 99,3 ), ( 99,4 ), ( 99,5 ), ( 99,6 ), ( 99,7 ), ( 99,8 ), ( 99,9 ), ( 99,10 ), ( 99,11 ), ( 99,12 )
The 4 digit century is determined by the rule, if the year is 50 or more, the century is 1900, otherwise 2000.
Given two 6 digit periods that mark the start and end period, like a quarter, return the rows that fall in that range.
-- 1st quarter of 2020
SELECT * FROM PER_CAL WHERE (( CASE WHEN CAL_YEAR > 50 THEN 1900 ELSE 2000 END + CAL_YEAR ) * 100 + CAL_PER ) BETWEEN 202001 AND 202003
-- 4th quarter of 1999
SELECT * FROM PER_CAL WHERE (( CASE WHEN CAL_YEAR > 50 THEN 1900 ELSE 2000 END + CAL_YEAR ) * 100 + CAL_PER ) BETWEEN 199910 AND 199912
Try this query. Its very easy to understand:
CREATE TABLE PersonsDetail(FirstName nvarchar(20), LastName nvarchar(20), GenderID int);
GO
INSERT INTO PersonsDetail VALUES(N'Gourav', N'Bhatia', 2),
(N'Ramesh', N'Kumar', 1),
(N'Ram', N'Lal', 2),
(N'Sunil', N'Kumar', 3),
(N'Sunny', N'Sehgal', 1),
(N'Malkeet', N'Shaoul', 3),
(N'Jassy', N'Sohal', 2);
GO
SELECT FirstName, LastName, Gender =
CASE GenderID
WHEN 1 THEN 'Male'
WHEN 2 THEN 'Female'
ELSE 'Unknown'
END
FROM PersonsDetail