How to assign multiple values in CASE statement? - sql

I need to assign two values to my select based on a CASE statement. In pseudo:
select
userid
, case
when name in ('A', 'B') then 'Apple'
when name in ('C', 'D') then 'Pear'
end as snack
from
table
;
I am assigning a value for snack. But lets say I also want to assign a value for another variable, drink based on the same conditions. One way would be to repeat the above:
select
userid
, case
when name in ('A', 'B') then 'Apple'
when name in ('C', 'D') then 'Pear'
end as snack
, case
when name in ('A', 'B') then 'Milk'
when name in ('C', 'D') then 'Cola'
end as drink
from
table
;
However, if I have to assign more values based on the same conditions, say food, drink, room, etc. this code becomes hard to maintain.
Is there a better way of doing this? Can I put this in a SQL function, like you would normally do in another (scripting) language and if so, could you please explain how?

When doing things like this I tend to use a join with a table valued constructor:
SELECT t.UserID,
s.Snack,
s.Drink
FROM Table AS T
LEFT JOIN
(VALUES
(1, 'Apple', 'Milk'),
(2, 'Pear', 'Cola')
) AS s (Condition, Snack, Drink)
ON s.Condition = CASE
WHEN t.name IN ('A', 'B') THEN 1
WHEN t.name IN ('C', 'D') THEN 2
END;
I find this to be the most flexible if I need to add further conditions, or columns.
Or more verbose, but also more flexible:
SELECT t.UserID,
s.Snack,
s.Drink
FROM Table AS T
LEFT JOIN
(VALUES
('A', 'Apple', 'Milk'),
('B', 'Apple', 'Milk'),
('C', 'Pear', 'Cola'),
('D', 'Pear', 'Cola')
) AS s (Name, Snack, Drink)
ON s.Name= t.name;

Functions destroy performance. But you could use a common-table-expression(cte):
with cte as
(
Select IsNameInList1 = case when name in ('A', 'B')
then 1 else 0 end,
IsNameInList2 = case when name in ('C', 'D')
then 1 else 0 end,
t.*
from table
)
select
userid
, case when IsNameInList1=1 then 'Apple'
when IsNameInList2=1 then 'Pear'
end as snack
, case when IsNameInList1=1 then 'Milk'
when IsNameInList2=1 then 'Cola'
end as drink
from
cte
;
On this way you have only one place to maintain.
If query performance doesn't matter and you want to use a scalar valued function like this:
CREATE FUNCTION [dbo].[IsNameInList1]
(
#name varchar(100)
)
RETURNS bit
AS
BEGIN
DECLARE #isNameInList bit
BEGIN
SET #isNameInList =
CASE WHEN #name in ('A', 'B')
THEN 1
ELSE 0
END
END
RETURN #isNameInList
END
Then you can use it in your query in this way:
select
userid
, case when dbo.IsNameInList1(name) = 1 then 'Apple'
when dbo.IsNameInList2(name) = 1 then 'Pear'
end as snack
from
table
;
But a more efficient approach would be to use a real table to store them.

Hope this will help you
SELECT userid
,(CASE flag WHEN 1 THEN 'Apple' WHEN 2 THEN 'Pear' WHEN 3 THEN '..etc' END ) as snack
,(CASE flag WHEN 1 THEN 'Milk' WHEN 2 THEN 'Cola' WHEN 3 THEN '..etc' END ) as drink
FROM (
SELECT userid
,( CASE WHEN name IN ('A', 'B') THEN 1
WHEN name IN ('C', 'D') THEN 2
WHEN name IN ('X', 'Y') THEN 3
ELSE 0 END ) AS flag
FROM table ) t

As Ganesh suggested in a comment, I recommend creating a mapping table for this and just do lookups. Much easier to interpet, maintain, and scale - all with better performance.

Related

Combining several queries into 1 new query

I have a table that consists of all of our agency records. I have several queries set up that count something specific about these records and each query groups them all by date. What I am trying to figure out is how I can combine these queries into one new query. Right now, I run each one, put them into Excel and then do a vlookup and combine them into one. Here are just two of my queries.
Query #1:
select
LocationStateAbr,
count(LocationStateAbr) as "Total Agencies"
from
[PROD_Agency].[dbo].[AgAgency]
where
StatusId = ' '
and BusinessId in ('b', 'C')
and TypeId in ('A', 'C', 'F', 'I', 'X')
group by
LocationStateAbr
order by
LocationStateAbr ASC
Query #2:
select
LocationStateAbr,
count(LocationStateAbr) as "New Agencies"
from
[PROD_Agency].[dbo].[AgAgency]
where
year(AppointedDt) = 2018
and StatusId = ' '
and BusinessId in ('b', 'C')
and TypeId in ('A', 'C', 'F', 'I', 'X')
group by
LocationStateAbr
order by
LocationStateAbr ASC
Any suggestions? Thank you!
You can combine the two queries into one using CASE. In your case it would be something like this:
select
LocationStateAbr,
count(case when StatusId = ' '
and BusinessId in ('b', 'C')
and TypeId in ('A', 'C', 'F', 'I', 'X') then 1 else null end) as "Total Agencies",
count(case when year(AppointedDt) = 2018
and StatusId = ' '
and BusinessId in ('b', 'C')
and TypeId in ('A', 'C', 'F', 'I', 'X') the 1 else null end) as "New Agencies"
FROM
[PROD_Agency].[dbo].[AgAgency]
group by
LocationStateAbr
order by
LocationStateAbr ASC

Self join SQL query to return parents that have at least two same children

I have this table setup.
create table holdMyBeer
(
Id int,
Name varchar(20)
)
insert into holdMyBeer
values (1, 'park'), (1, 'washington'), (1, 'virginia'),
(2, 'harbor'), (2, 'premier'), (2, 'park'),
(3, 'park'), (3, 'washington'), (3, 'virginia'), (3, 'Ball');
I am looking for the id's (parents) that at least have park, washington and virginia as name(child).
I have the answer on Fiddle. http://sqlfiddle.com/#!6/e7346/1 but there must be a better way to do this.
This concept is called as Conditional Aggregation. I am grouping on Id and then checking whether there is atleast one entry for park,washington,virginia by using having clause and . This should answer your question.
SELECT Id
FROM holdMyBeer
GROUP BY Id
HAVING SUM( CASE WHEN Name = 'park' THEN 1 ELSE 0 END ) >= 1 AND
SUM( CASE WHEN Name = 'washington' THEN 1 ELSE 0 END ) >= 1 AND
SUM( CASE WHEN Name = 'virginia' THEN 1 ELSE 0 END ) >= 1;

Oracle - How to sum related items?

I have a set of data I am wanting to report on (in Oracle 12c). I am wanting to show the sum of certain groupings, based on the value of the 'ITEM' column. If ITEM = ('A', 'B' or 'C'), then they get "grouped" together and the values for columns Cost1 and Cost2 are summed up. This is called "Group1". Group2 contains ITEMSs ('D, E, F'). There are only 6 groupings, made up of around 13 static/fixed items. I know exactly what the items are in each grouping.
Here is a set of sample data, with borders showing what should be grouped together. The 2nd listing shows what the output should look like. I think i am wanting to sum within a case statement, but I just can't seem to wrap my head around it...
The CASE statement is good for this type of one-off value transformation:
SELECT
Year,
CASE
WHEN Item IN ('A', 'B', 'C') THEN 'Group1'
WHEN Item IN ('D', 'E', 'F') THEN 'Group2'
ELSE 'Others' END AS Item,
SUM(Cost1),
SUM(Cost2)
FROM myTable
GROUP BY
Year,
CASE
WHEN Item IN ('A', 'B', 'C') THEN 'Group1'
WHEN Item IN ('D', 'E', 'F') THEN 'Group2'
ELSE 'Others' END;
Note that the GROUP BY expressions must be stated exactly as they are in the column selection list, except you have to drop the alias (AS Item).
Look like you want Group By query:
select Year,
case
when item = 'A' or item = 'B' or item = 'C' then
'Group1'
else
'Group2'
end as Item
Sum(nvl(Cost1, 0)) as Cost1, -- note NVL to prevent nulls from summing
Sum(nvl(Cost2, 0) as Cost2
from MyTable
group by Year,
Item
If the values for groups are not stored then the simplest apporach is to use a case statement and an aggregate function. You could create a cte with the values for easier maintenance; or possibly a pipelined tablefunction
Select year, case when item in ('A','B','C') then 'Group1'
when item in ('D','E','F' then 'GROUP2' else
'Unhandled' end as Item, Sum(Cost1) as cost1, Sum(Cost2) as cost2
From TableName
Group by year, case when item in ('A','B','C') then 'Group1'
when item in ('D','E','F' then 'GROUP2' else
'Unhandled' end
Additional to the mapping with CASE you may extract the mapping logic in the dedicated table myItem
CREATE TABLE myItem
("ITEM" VARCHAR2(1),
"GROUP_ID" VARCHAR2(6),
PRIMARY KEY ("ITEM")
);
and join to this table to get the group before GROUPing
with add_group as (
select a.*, b.group_id from myTable a
left outer join myItem b
on a.item = b.item
)
select .... your GROUP BY query here
This will keep the query more stable even if new items or groups emerges.

SQL query to get count based on filtered status

I have a table which has two columns, CustomerId & Status (A, B, C).
A customer can have multiple status in different rows.
I need to get the count of different status based on following rules:
If the status of a customer is A & B, he should be counted in Status A.
If status is both B & C, it should be counted in Status B.
If status is all three, it will fall in status A.
What I need is a table with status and count.
Could please someone help?
I know that someone would ask me to write my query first, but i couldn't understand how to implement this logic in query.
You could play with different variations of this:
select customerId,
case when HasA+HasB+HasC = 3 then 'A'
when HasA+HasB = 2 then 'A'
when HasB+HasC = 2 then 'B'
when HasA+HasC = 2 then 'A'
when HasA is null and HasB is null and HasC is not null then 'C'
when HasB is null and HasC is null and HasA is not null then 'A'
when HasC is null and HasA is null and HasB is not null then 'B'
end as overallStatus
from
(
select customerId,
max(case when Status = 'A' then 1 end) HasA,
max(case when Status = 'B' then 1 end) HasB,
max(case when Status = 'C' then 1 end) HasC
from tableName
group by customerId
) as t;
I like to use Cross Apply for this type of query as it allows for use of the calculated status in the Group By clause.
Here's my solution with some sample data.
Declare #Table Table (Customerid int, Stat varchar(1))
INSERT INTO #Table (Customerid, Stat )
VALUES
(1, 'a'),
(1 , 'b'),
(2, 'b'),
(2 , 'c'),
(3, 'a'),
(3 , 'b'),
(3, 'c')
SELECT
ca.StatusGroup
, COUNT(DISTINCT Customerid) as Total
FROM
#Table t
CROSS APPLY
(VALUES
(
CASE WHEN
EXISTS
(SELECT 1 FROM #Table x where x.Customerid = t.CustomerID and x.Stat = 'a')
AND EXISTS
(SELECT 1 FROM #Table x where x.Customerid = t.CustomerID and x.Stat = 'b')
THEN 'A'
WHEN
EXISTS
(SELECT 1 FROM #Table x where x.Customerid = t.CustomerID and x.Stat = 'b')
AND EXISTS
(SELECT 1 FROM #Table x where x.Customerid = t.CustomerID and x.Stat = 'c')
THEN 'B'
ELSE t.stat
END
)
) ca (StatusGroup)
GROUP BY ca.StatusGroup
I edited this to deal with Customers who only have one status... in which case it will return A, B or C dependant on the customers status

SQL return multiple values from CASE statement

How do I rewrite this SQL statement to do what I need?
SELECT * FROM TABLE
WHERE COLUMN_NAME_1 IN (CASE
WHEN COLUMN_NAME_2 = 'X' THEN 'A'
WHEN COLUMN_NAME_2 = 'Y' THEN 'B', 'C' END)
Obviously I can't return multiple values from a CASE clause... so how else could I write this? I am pretty sure I am slow today because this seems so easy ....
SELECT * FROM Table WHERE
(COLUMN_NAME_2 = 'X' AND COLUMN_NAME_1 = 'A') OR
(COLUMN_NAME_2 = 'Y' AND COLUMN_NAME_1 IN ('B', 'C'))
or
SELECT * FROM Table WHERE COLUMN_NAME_2 = 'X' AND COLUMN_NAME_1 = 'A'
UNION ALL
SELECT * FROM Table WHERE COLUMN_NAME_2 = 'Y' AND COLUMN_NAME_1 IN ('B', 'C')
This presumes that you only want results with X or Y in COLUMN_NAME_2. If you want other rows it's not possible to tell which ones from your original SQL.