I have a variable called _gender thats going to be either 0, 1 or 2
0 male
1 female
2 everyone
I want to achieve the case where _gender is everyone, then I want the gender to be 0 or 1
But if it was 0 or 1, then I should match with users that are only as the variable
SELECT * FROM TABLE
WHERE COUNTRY = 1
AND Gender = --"IF _gender == 2, then I need to get gender either = 0 or 1, else I get the value of the variable (to match 0 or 1)"
You have to Denote Variable starting with #.
Lets consider variable #gender
If you want when #gender is everyone, then the gender to be 0 or 1.
select * from TABLE where Country=1
and Gender = (case when #gender=2 then 0 else #gender end)
If you want when #gender is everyone, then the all type of gender from your table.
select * from TABLE where Country=1
and Gender = (case when #gender=2 then Gender else #gender end)
For reference click below link
Code Reference Link Click Here
I think you are looking for something like:
where country = 1 and
(gender = #gender or #gender = 2)
Often null is used for the value to mean everyone. In that case, you need to phrase it as:
where country = 1 and
(gender = #gender or #gender is null)
Related
Suppose I have a data like this
first_name last_name city
John Bon Jovi null
John Lennon null
John Deer null
And I want to create aggregating query which will return json which looks like this
{ "first_name": "John", "city": null }
Essentially, the query should check if there's only one distinct value within each column and if it is, put this value to json. All non-null columns are relatively easy to get with a query like this:
select
case when count(distinct first_name) = 1 then max(first_name) end as first_name,
case when count(distinct last_name) = 1 then max(last_name) end as last_name,
case when count(distinct city) = 1 then max(city) end as city
from ...
for json path, without_array_wrapper
or
select
case when max(first_name) = min(first_name) then max(first_name) end as first_name,
case when max(last_name) = min(last_name) then max(last_name) end as last_name,
case when max(city) = min(city) then max(city) end as city
from ...
for json path, without_array_wrapper
The result of the queries above is json like this {"first_name":"John"}. But then there are problems with nulls. Problem (1) - queries above do not take nulls into account, so if I have data like this
first_name last_name city
----------------------------------
John Lennon null
John Lennon null
John null null
Then last name is also included in the resulting json
{ "first_name": "John", "last_name": "Lennon" }
Ok, that's understandable (cause ...Null value is eliminated by an aggregate...) and I can solve it with a query like this:
select
case when count(distinct first_name) = 1 and count(first_name) = count(*) then max(first_name) end as first_name,
case when count(distinct last_name) = 1 and count(last_name) = count(*) then max(last_name) end as last_name,
case when count(distinct city) = 1 and count(city) = count(*) then max(city) end as city
from ...
for json path, without_array_wrapper
But there are other problems with nulls I can't really solve neatly for now. Problem (2) - I want to have also "city":null in my json. Of course I can do something like this
...
case when count(city) = 0 then 'null' end as city
...
and then replace string null with real nulls, but it's not very neat. Another annoying thing is (3) - I'd really like to get rid of warnings
Warning: Null value is eliminated by an aggregate or other SET operation.
without turning ANSI_WARNINGS off. For now I can only think about using some placeholders with isnull which doesn't look like a clean solution
...
case when count(distinct isnull(city, 'null')) = 1 then max(city) end as city
...
So, any ideas on how to elegantly solve problems (2) and (3)? see examples in db<>fiddle.
Ok, so nobody posted any answers so far, I have thought of one way doing it. It's not perfect, but it seems to work.
So the idea is to use #var = #var + 1 trick inside of select. But it should be a bit more complicated:
declare
#first_name varchar(4), #first_name_state tinyint = 0,
#last_name varchar(4), #last_name_state tinyint = 0,
#city varchar(4), #city_state tinyint = 0,
#country varchar(10), #country_state tinyint = 0,
#result nvarchar(max) = '{}';
select
#first_name_state =
case
when #first_name_state = 0 then 1
when #first_name_state = 1 and #first_name = t.first_name then 1
when #first_name_state = 1 and #first_name is null and t.first_name is null then 1
else 2
end,
#first_name = t.first_name,
#last_name_state =
case
when #last_name_state = 0 then 1
when #last_name_state = 1 and #last_name = t.last_name then 1
when #last_name_state = 1 and #last_name is null and t.last_name is null then 1
else 2
end,
#last_name = t.last_name,
#city_state =
case
when #city_state = 0 then 1
when #city_state = 1 and #city = t.city then 1
when #city_state = 1 and #city is null and t.city is null then 1
else 2
end,
#city = t.city,
#country_state =
case
when #country_state = 0 then 1
when #country_state = 1 and #country = t.country then 1
when #country_state = 1 and #country is null and t.country is null then 1
else 2
end,
#country = t.country
from Table1 as t;
if #first_name_state = 1
set #result = json_modify(json_modify(#result,'$.first_name','null'),'strict $.first_name',#first_name);
if #last_name_state = 1
set #result = json_modify(json_modify(#result,'$.last_name','null'),'strict $.last_name',#last_name);
if #city_state = 1
set #result = json_modify(json_modify(#result,'$.city','null'),'strict $.city',#city);
if #country_state = 1
set #result = json_modify(json_modify(#result,'$.country','null'),'strict $.country',#country);
select #result;
----------------------------------
{"first_name":"John","city":null}
see db<>fiddle with examples.
Please note that, according to Microsoft docs you shouldn't use this variable aggregation assignment trick cause some of the statements can be called more than once.
Don't use a variable in a SELECT statement to concatenate values (that
is, to compute aggregate values). Unexpected query results may occur.
Because, all expressions in the SELECT list (including assignments)
aren't necessarily run exactly once for each output row.
I hope in this case it should work fine cause it's not exactly an aggregation, and it's ok if these statements will be called more than once per row.
Still, you can find some useful links in this answer.
I recently started developing/improving the way my search algorithm works. There is few different queries in the system that will use this approach. I'm new in stored procedures world and from what I researched they should improve security, performance and save some code redundancy. Here is example of what I have done in one of my stored procedures for Account search:
ALTER PROCEDURE [dbo].[SearchAccounts]
#Status INT = NULL,
#Type INT = NULL,
#FilterBy INT = NULL,
#Username VARCHAR(50) = NULL,
#Email VARCHAR(80) = NULL,
#LastName VARCHAR(50) = NULL,
#FirstName VARCHAR(50) = NULL,
#FullName VARCHAR(100) = NULL
WITH RECOMPILE
AS
DECLARE #AccountStatus INT = #Status;
DECLARE #AccountType INT = #Type;
DECLARE #AccountFilter INT = #FilterBy;
DECLARE #AccountUsername VARCHAR(50) = #Username;
DECLARE #AccountEmail VARCHAR(80) = #Email;
DECLARE #AccountLast VARCHAR(50) = #LastName;
DECLARE #AccountFirst VARCHAR(50) = #FirstName;
DECLARE #AccountFull VARCHAR(10) = #FullName;
SELECT
A.AccountID, A.FirstName, A.LastName, A.Middle, A.Email,
A.IsUser, A.ActiveUser, A.SystemAdmin, A.AccessType,
A.AccessLevel, A.UserName, A.IsStaff, A.ActiveStaff,
A.Position AS PositionCode, M.Name AS Position
FROM
Accounts AS A
LEFT OUTER JOIN
Master AS M ON M.Tblid = 'STAFF_POS'
AND M.Code = A.Position
WHERE
( -- If Account Type is 1 (User)
(#AccountType = 1 AND A.IsUser = 1)
OR -- If Account Type is 2 (Staff)
(#AccountType = 2 AND A.IsStaff = 1)
OR -- Or if Account type is 0 (All accounts)
(#AccountType = 0 AND 1 = 1)
)
AND
(
(-- If account type is user and Status is 1 (Active) or 0 (Inactive) or 2(pull active and inactive)
#AccountType = 1
AND
(#AccountStatus = 0 OR #AccountStatus = 1) AND ActiveUser = #AccountStatus)
OR
(#AccountType = 1 AND #AccountStatus = 2 AND 1 = 1
)
OR
(-- If account type is staff and Status is 1 (Active) or 0 (Inactive) or 2 (pull active and inactive)
#AccountType = 2
AND
(#AccountStatus = 0 OR #AccountStatus = 1) AND ActiveStaff = #AccountStatus)
OR
(#AccountType = 2 AND #AccountStatus = 2 AND 1 = 1
)
OR
(-- If account type is all pull all accounts active and inactive
(#AccountType != 1 AND #AccountType != 2 AND 1 = 1)
)
)
AND
( -- Filter is 1 then check user name.
(#AccountFilter = 1 AND A.UserName LIKE '%'+#AccountUsername+'%')
OR -- Filter is 2 then check email.
(#AccountFilter = 2 AND A.Email = #AccountEmail)
-- Here if filter is 3 then I should check First or Last or Full Name.
-- Still not sure what is the best approach to filter on the name fields.
)
ORDER BY
A.LastName, A.FirstName
As you can see query above has few different filters. First user can choose if they want to search Users, Staff or pull All account types. Then to choose if they want to pull Active, Inactive or all records. Last thing is to pick the filter. I give them an option to search Username, Email or Name. Each Account has First, Last name. What would be a good option to search for those names? Check just one full name or I have to check first and last separately? First and Last name allow this set of characters in the Accounts form : A-Z, space, dash, apostrophe, period, comma - no other special characters
There are probably dozens of ways to accomplish such a goal. But one way I would prefer (if I was using a query like the one presented) would be like this:
AND A.LNAME LIKE
CASE WHEN Len(#AccountLast) > 0 THEN
'%' + #AccountLast + '%'
ELSE '%'
END
AND A.FNAME LIKE
CASE WHEN Len(#AccountFirst) > 0 THEN
'%' + #AccountFirst + '%'
ELSE '%'
END
I have a SQL query that returns all employees. It gives me an error because it expects after the WITH( ), it expects a SELECT statement.
But if I would like to filter the result a second time, how do I work around using IF-ELSE IF?
Note: My SQL might sound a little confusing (i.e.: if #Gender = Male, look for all single employees) because those are placeholders I put in place of my real SQL. I apologize for not being too creative in naming.
DECLARE #Gender VARCHAR(10)
SET #Gender = 'Female'
/* Store all employees aged 50+ in a variable called queryResult */
WITH queryResult AS
(
SELECT * FROM Employees WHERE Age >= 50
)
/* If Gender = Male, I'm looking for all employees that are single */
IF #Gender = 'Male'
BEGIN
SELECT * From queryResult WHERE Status = 'Single'
END
/* If Gender = Female, I'm looking for all employees that are married */
ELSE IF #Gender = 'Female'
BEGIN
SELECT * From queryResult WHERE Status = 'Married'
END
SOLVED: In case you wanted to see my real query, here's my working query based on Rahul's answer.
WITH queryResult AS
(
SELECT TOP 1 t.userName, t.timeStamp
FROM [Tracking].[dbo].[Tracking] t join (SELECT URL, PrimaryOwner FROM Governance WHERE Content = #Content) g
ON t.referringURL like g.URL
WHERE
timeStamp >=
CASE WHEN #Frequency = 'Daily' THEN CAST(GETDATE() As Date)
WHEN #Frequency = 'Monthly' THEN DATEADD(month, -1, CAST(GETDATE() As Date))
WHEN #Frequency = 'Annually' THEN DATEADD(year, -1, CAST(GETDATE() As Date))
END
and PrimaryOwner = t.userName
ORDER BY timestamp desc
)
SELECT * from queryResult
Why not just:
DECLARE #Gender VARCHAR(10)
SET #Gender = 'Female';
WITH queryResult AS
(
SELECT * FROM Employees WHERE Age >= 50
)
SELECT * From queryResult WHERE Gender = #Gender
EDIT:
If your WHERE clauses are very different you are likely better off putting the conditional on the outside. Putting the same cte or subquery in multiples places sort of offends the eye of a developer, but its could be the right answer here.
So to show that with your oversimplified example:
DECLARE #Gender VARCHAR(10)
SET #Gender = 'Female'
IF #Gender = 'Male'
BEGIN
;WITH queryResult AS
(
SELECT * FROM Employees WHERE Age >= 50
)
SELECT * From queryResult WHERE Gender = 'Male'
END
ELSE IF #Gender = 'Female'
BEGIN
;WITH queryResult AS
(
SELECT * FROM Employees WHERE Age >= 50
)
SELECT * From queryResult WHERE Gender = 'Female'
END
Why not do the filtering in CTE itself like
DECLARE #Gender VARCHAR(10)
SET #Gender = 'Female'
WITH queryResult AS
(
SELECT * FROM Employees WHERE Age >= 50 AND Gender = #Gender
)
select * from queryResult;
EDIT: In that case change your CTE with a CASE expression like below
DECLARE #Gender VARCHAR(10)
SET #Gender = 'Female'
WITH queryResult AS
(
SELECT * FROM Employees WHERE Age >= 50
AND Status = CASE WHEN #Gender = 'Male' THEN 'Single' ELSE 'Married' END
)
SELECT * FROM queryResult;
You can do it like this:
DECLARE #Gender VARCHAR(10)
SET #Gender = 'Female'
WITH queryResult AS
(
SELECT * FROM Employees WHERE Age >= 50
)
select *
from queryResult
where
(#Gender = 'Male' and Status = 'Single')
or (#Gender = 'Female' and Status = 'Married')
This question already has answers here:
Stored Procedure with optional "WHERE" parameters
(6 answers)
Closed 4 years ago.
Lets suppose there is a stored procedure that has 3 params. Out of all the possibilities, I'm looking to achieve this with a single WHERE clause without getting out of control with using () AND () OR () too much...
Example:
//Params
#CITY VARCHAR(100) = NULL,
#GENDER VARCHAR(100) = NULL,
#AGE VARCHAR(100) = NULL
I suppose you can do it using IF BEGIN ... END for each Variable if Exists, but that makes the code alot longer than desired..
This method below won't work because its way too long (there are about 10 different fields like this, but the example is only 3.) and i'm not sure if it even directly pulls up distinctive values...
SELECT NAME FROM TABLE
WHERE (
(CITY=#CITY AND GENDER=#GENDER AND AGE=#AGE)
OR (CITY=#CITY AND GENDER=#GENDER)
OR (GENDER=#GENDER AND AGE=#AGE)
OR (CITY=#CITY AND AGE=#AGE)
OR (CITY=#CITY)
OR (GENDER=#GENDER)
OR (AGE=#AGE)
)
Is there an even shorter more efficient way to do this?
If yes, it is preferable for the method to be compatible with JOIN's also.
Alternatively to the ISNULL / COALESCE options, you can test the parameters for being null:
SELECT NAME
FROM TABLE
WHERE
(#City IS NULL OR City = #City)
AND
(#Gender IS NULL OR Gender = #Gender)
AND
(#Age IS NULL OR Age = #Age)
what about this?
SELECT
NAME
FROM TABLE
WHERE CITY = COALESCE(#CITY, CITY)
AND GENDER = COALESCE(#GENDER, GENDER)
AND AGE = COALESCE(#AGE, AGE)
Try something like this:
SELECT NAME
FROM TABLE
WHERE
City = IsNull(#City, City) AND
Gender = IsNull(#Gender, Gender) AND
Age = IsNull(#Age, Age)
OR:
SELECT NAME
FROM TABLE
WHERE
(City = #City OR #City IS NULL) AND
(Gender = #Gender OR #Gender IS NULL) AND
(Age = #Age OR #Age IS NULL)
SELECT NAME
FROM TABLE
WHERE
City = case when isnull(#City ,'') = '' then City
else #City end
AND
Gender = case when isnull(#Gender ,'') = '' then Gender
else #Gender end
AND
Age = case when isnull(#Age ,0) = 0 then Age
else #Age end
Possibly this:
create procedure myProc
--Params
#CITY VARCHAR(100) = NULL,
#GENDER VARCHAR(100) = NULL,
#AGE VARCHAR(100) = NULL
as
SELECT NAME FROM [TABLE]
WHERE ISNULL(CITY,'')=ISNULL(#CITY,ISNULL(CITY,''))
AND ISNULL(GENDER,'')=ISNULL(#GENDER,ISNULL(GENDER,''))
AND ISNULL(AGE,'')=ISNULL(#AGE,ISNULL(AGE,''))
go
Assuming the columns in the WHERE clause are nullable, using ISNULL to avoid null comparison.
Looking for an elegant way to workaround this...
DECLARE #ZIP INT
SET #ZIP = 55555
IF #ZIP = ALL(SELECT ZIP FROM PEOPLE WHERE PERSONTYPE = 1)
PRINT 'All people of type 1 have the same zip!'
ELSE
PRINT 'Not All people of type 1 have the same zip!'
The issue is that, if (SELECT ZIP FROM PEOPLE WHERE PERSONTYPE = 1) returns no records, then the above IF evaluates to true. I'm looking for a way to make this evaluate to false when there are no records returned by the ALL's subquery.
My current solution:
DECLARE #ZIP INT
SET #ZIP = 55555
DECLARE #ALLZIPS TABLE (INT ZIP)
INSERT INTO #ALLZIPS
SELECT ZIP FROM PEOPLE WHERE PERSONTYPE = 1
IF EXISTS(SELECT TOP 1 * FROM #ALLZIPS) AND (#ZIP = ALL (SELECT ZIP FROM #ALLZIPS))
PRINT 'All people of type 1 have the same zip!'
ELSE
PRINT 'Not All people of type 1 have the same zip!'
Use:
IF EXISTS(SELECT NULL
FROM PEOPLE p
WHERE p.persontype = 1
HAVING MIN(p.zip) = #Zip
AND MAX(p.zip) = #Zip)
PRINT 'All people of type 1 have the same zip!'
ELSE
PRINT 'Not All people of type 1 have the same zip!'
Jumping in:
IF (SELECT SUM(CASE WHEN ZIP = #ZIP THEN 0 ELSE 1 END)
FROM PEOPLE WHERE PERSONTYPE = 1) = 0
PRINT 'All people of type 1 have the same zip!'
ELSE
PRINT 'Not All people of type 1 have the same zip!'
Consider using EXISTS as well.
IF #ZIP = ALL(SELECT ZIP FROM PEOPLE WHERE PERSONTYPE = 1)
AND EXISTS(SELECT 1 FROM PEOPLE WHERE PERSONTYPE = 1)