How do I select records into a single row? - sql-server

I've tried writing my sql query to select multiple records on to one row but it isn't working the way I expected it to
Currently my table looks something like this
person id
fruit
1
apple
1
orange
1
banana
2
apple
2
orange
3
apple
I've tried using CASE and GROUP BY but it just gave extra records and didn't display the way I wanted it to and is displaying like this
SELECT DISTINCT
F.MEMBER
,F.GIVEN_NAMES
,F.SURNAME
--VALUES NEEDED
,CASE WHEN F.VALUE_NEEDED = 'Postal Address' THEN 'Yes' ELSE '' END POSTAL_ADDRESS
,CASE WHEN F.VALUE_NEEDED = 'Birthday' THEN 'Yes' ELSE '' END BIRTHDAY
,CASE WHEN F.VALUE_NEEDED = 'Email Address' THEN 'Yes' ELSE '' END EMAIL_ADDRESS
,CASE WHEN F.VALUE_NEEDED = 'First Name' THEN 'Yes' ELSE '' END FIRST_NAME
,CASE WHEN F.VALUE_NEEDED = 'Surname' THEN 'Yes' ELSE '' END SURNAME
,CASE WHEN F.VALUE_NEEDED = 'Title and Gender' THEN 'Yes' ELSE '' END 'TITLE|GENDER'
,CASE WHEN F.VALUE_NEEDED = 'Mobile' THEN 'Yes' ELSE '' END MOBILE
,CASE WHEN F.VALUE_NEEDED = 'Beneficiary' THEN 'Yes' ELSE '' END BENEFICIARY
FROM #FINAL F
GROUP BY F.MEMBER,F.GIVEN_NAMES
,F.SURNAME,VALUE_NEEDED
ORDER BY F.MEMBER
person id
apple
orange
banana
1
yes
1
yes
1
yes
How do I write the query so it looks more like this?
person id
apple
orange
banana
1
yes
yes
yes
2
yes
yes
3
yes

You are almost there, just needed to add the max and group by to aggregate it . This used to be a typical interview question back then.
Some thing like this if I understood correctly
with t as
(
select 1 as person_id, 'apple' fruit
union
select 1 ,'orange'
union
select 1 ,'banana'
union
select 2 ,'apple'
union
select 2 ,'orange'
union
select 3 ,'apple'
)
, b as
(
select
person_id,
case when fruit= 'apple' then 'yes' else null end 'apple',
case when fruit= 'orange' then 'yes' else null end 'orange',
case when fruit= 'banana' then 'yes' else null end 'banana'
from t
)
select
person_id,
max(apple) apple,
max(orange) orange,
max(banana) banana
from b
group by 1;
person_id
apple
orange
banana
1
yes
yes
yes
2
yes
yes
NULL
3
yes
NULL
NULL

You have tagged the tool that you are using (SQL Server Management Studio) which can be used with different DBMS. As Microsoft's SQL Server is the most typical used in this tool, I assume your are using that.
First let's look at your table. It seems a bit weird. It seems to be a kind of key-value table (aka EAV). Each row tells us for an attribute whether it is needed for a person. Now how to identify a person in the table? Is the column member a unique person ID? Probably not, because then, what would be given_names and surname be for in that table, that can change with every entry. Why would the same person with the ID 1234 be called John Smith when the value_needed is 'Birthday', but be called 'Anne Miller' when value_needed is 'Mobile'? That wouldn't make much sense. So maybe member is just a flag, whether a person is a member or not, and a person is uniquely identified by their given_names and surname. But then again, why would the same person John Smith be a member when value_needed is 'Birthday', but not a member when value_needed is 'Mobile'? So something is amiss here. It seems your table is not normalized. Better have one person table and one table for the attributes.
That being said, GROUP BY ___ means "I want one result row per ___". You group by the person and their value_needed. But you don't want one result row per person and value_needed. You want one resut row per person. Hence, group by person.
Then you SELECT DISTINCT .... This means you want to remove duplicate rows. But look at the rows you are selecting. There are no duplicates. If you use GROUP BY, you can be 99.99% sure you don't need DISTINCT. (There do exist rare situations where you voluntarily group by columns, don't select all of them and then apply DISTINCT, but these are so rare that you probably won't ever use them at all.)
Now to the task: You want to get from rows to columns. This is called pivot and can be achieved with the PIVOT keyword, but it is more common to use conditional aggregation. "Conditional aggregation" means that you aggregate your data (per person) and then apply a condition. In standard SQL:
SELECT MIN('YES') FILTER (WHERE f.value_needed = 'Postal Address')
You can use MIN or MAX here, and it is only required for syntax reasons (the FILTER clause must refer to some aggregation function.
In SQL Server there is no FILTER clause, so you use a CASE expression instead:
SELECT MIN(CASE WHEN f.value_needed = 'Postal Address' THEN 'YES' END)
If you want the empty string '' instead of NULL, apply COALESCE:
SELECT COALESCE(MIN(CASE WHEN f.value_needed = 'Postal Address' THEN 'YES' END), '')
Columns aliases containing special characters like | require quoting. But not single quotes, as these denote string literals. In standard SQL use double quotes, in SQL Server use brackets. But better, just avoid them alltogether, by avoiding special characters in names.
The complete query:
SELECT
person_id,
MIN(CASE WHEN value_needed = 'Postal Address' THEN 'yes' end) AS postal_address,
MIN(CASE WHEN value_needed = 'Birthday' THEN 'Yes' end) AS birthday,
MIN(CASE WHEN value_needed = 'Email' THEN 'Yes' END) AS email_address,
MIN(CASE WHEN value_needed = 'First Name' THEN 'Yes' END) AS first_name,
MIN(CASE WHEN value_needed = 'Surname' THEN 'Yes' END) AS surname,
MIN(CASE WHEN value_needed = 'Title and Gender' THEN 'Yes' END) AS title_gender,
MIN(CASE WHEN value_needed = 'Mobile' THEN 'Yes' END) AS mobile,
MIN(CASE WHEN value_needed = 'Beneficiary' THEN 'Yes' END) AS beneficiary
FROM #FINAL
GROUP BY person_id
ORDER BY person_id;

Related

SQL CASE WHEN - NULL values returning unwanted rows

I have two case statements running in the same select statement, but here is a simplified example:
SELECT t.Person,
CASE WHEN t.Order LIKE 'Test Order 1 CHRG' THEN (SUBSTRING(t.Order PATINDEX('%[0-9]%',ord.Name),1)) END AS 'Order1'
CASE WHEN t.Order LIKE 'Test Order 2 CHRG' THEN (SUBSTRING(t.Order PATINDEX('%[0-9]%',ord.Name),1)) END AS 'Order2'
The results that I am getting are:
Name Order1 Order2
======================================
Person A 4 NULL
Person A NULL 3
Person B 2 NULL
Person B NULL 3
Person C 1 NULL
Person C NULL 5
Is there a way to ignore NULL value that is being produced by the CASE statements and have the query return only one row for each t.Name? Like this:
Name Order1 Order2
======================================
Person A 4 3
Person B 2 3
Person C 1 5
Thanks in advance!
(Now that I'm not on a Teams call) You're effectively doing a pivot here, which means you need to add some aggregation to eliminate the NULL values so that both values are on the same row. Filling in a few gaps, I suspect you therefore need:
SELECT t.Person,
MAX(CASE WHEN t.Order = 'Test Order 1 CHRG' THEN (SUBSTRING(t.Order PATINDEX('%[0-9]%',ord.Name),1)) END) AS Order1 --Don't use single quotes for aliases
MAX(CASE WHEN t.Order = 'Test Order 2 CHRG' THEN (SUBSTRING(t.Order PATINDEX('%[0-9]%',ord.Name),1)) END) AS Order2 --You didn't need LIKE either, as there was no pattern
FROM dbo.Table1 t
JOIN table2 ord ON t.id = ord.tid
GROUP BY t.Person;
Using Searched Case Statement is the best fit to exclude NULL marker from the result set of the Case Statement.
Below is the base T-SQL syntax for it;
--Searched CASE expression:
CASE
WHEN Boolean_expression THEN result_expression [ ...n ]
[ ELSE else_result_expression ]
END
Detailed information

A way to search SQL to only pull rows of data that ONLY HAS null value

This is my query:
SELECT patientname, patientID,
(CASE WHEN patientID is not null THEN 'yes'
WHEN CpatientID is null then 'No'
END) as submittedintohospital
FROM dbo].[database](nolock)
and patientname = "John Smith"
this is data result:
PatientName PatientID
John Smith 12345488999 NULL
John Smith 12880889976 NULL
As you can see, a patient have 2 rows of result, and I want to set a condition where only ALL the rows
's patientID is null, then it will go into case for yes.
Because currently query I wrote it will show patient data with both null and non-null value:
For example:
John Wick 1895
John Wick NULL
I want to eliminate that and only pull patient name where all the rows must have null patientID, not just one row.
I want to set a condition where only ALL the rows 's patientID is null, then it will go into case for yes.
You can use window functions:
select patientname, patientid,
case when max(patientid) over(partition by patientname) is null
then 'yes'
else 'no'
end as submittedintohospital
from [dbo].[database]
If you want to actually filter the results:
select patientname, patientid
from (
select d.*,
max(patientid) over(partition by patientname) max_patientid
from [dbo].[database] d
) d
where max_patientid is null
not exists might also come handy for filtering:
select d.*
from [dbo].[database] d
where not exists (
select 1
from [dbo].[database] d1
where d1.patientname = d.patientname and d1.patientid is not null
)

Find gender of employees from their SSN

I should find gender of employees from their (SSN).and theory is , if the last number of SSN is even (0,2,4,6,8) gender is woman or/else if the last number of SSN is odd (1,3,5,7,9) gender is man.
SSN of employees look like this in my DataBase ,for ex : 1111112020 or 22222231 - and column name is XX and datatype is nvarchar(30).
The sql i wrote look like this to find out gender of employees and when i executed query i get NULL. Can someone please point me in the right direction. Thanks.
DECLARE #Mand char(5) = '1,3,5,9,7'
DECLARE #Woman char(5) = '0,2,4,6,8'
DECLARE #Gender char (1)
SELECT (
CASE
WHEN right(rtrim(SSN),1) = #Mand THEN 'MAN'
WHEN right(rtrim(SSN),1) = #Woman THEN 'Woman'
ELSE NULL
END) as gender
FROM U
WHERE I = XXX
You cannot use strings and in that way. Just use the explicit list in the query:
SELECT (CASE WHEN right(rtrim(SSN), 1) IN ('1', '3', '5', '7', '9') THEN 'MALE'
WHEN right(rtrim(SSN), 1) IN ('2', '4', '6', '8', '0') THEN 'FEMALE'
END) as gender
FROM dbo.Users
WHERE CustomerId = 214;
Alternatively, you could use LIKE:
SELECT (CASE WHEN rtrim(SSN) LIKE '%[13579]' THEN 'MALE'
WHEN rtrim(SSN) LIKE '%[24680]' THEN 'FEMALE'
END) as gender
FROM dbo.Users
WHERE CustomerId = 214;
I should note that I am not aware that gender is assigned this way for US social security numbers.
Assuming that your SSNs are guaranteed valid (i.e. only contain digits), then the expression RIGHT(SSN,1)%2 (or RIGHT(RTRIM(SSN),1)%2 if there's extra space at the end) will return 0 for females and 1 for males. There's no need for complex string searching when things have been set up to make maths easy.
You can then optionally put that in a CASE if you want to, e.g.
CASE RIGHT(SSN,1)%2
WHEN 0 THEN 'Female'
ELSE 'Male'
END
DECLARE #ie nvarchar(30)
SET #ie = '1111112020'
SELECT (
CASE
WHEN right(rtrim(#ie),1) IN ('1','3','5','9','7') THEN 'MAN'
WHEN right(rtrim(#ie),1) IN ('0','2','4','6','8') THEN 'Woman'
END ) as gender

Order by more than clause in a case statement

I'm working a query that will retrieve various personal information, including first and last names. I want to change the order that the data is returned by based on a passed parameter, called #p_Code. When #p_Code is 4, I want to order by first name, then last name. If it's not 4, then I want to order by last name, then first name.
I'm working with MS Sql.
Here's the query as it stands:
Select Last,
First,
Phone,
Email
From Master.dbo.Cust
Order by
case #p_Code
when '4' then
([First], [Last])
else
([Last], [First])
end
This should do it:
DECLARE #p_Code VARCHAR(10)
SET #p_Code = '4'
Select [Last],
[First],
Phone,
Email
From Master.dbo.Cust
Order by
case WHEN #p_Code = '4' THEN (RANK() OVER (ORDER BY [First], [Last]))
ELSE (RANK() OVER (ORDER BY [Last],[First] )) END
I don't have a test environment handy, but how about this:
Select Last,
First,
Phone,
Email,
(case #p_Code when '4' then [first] else [last] end) as key1,
(case #p_Code when '4' then [last] else [first] end) as key2
From Master.dbo.Cust
Order by key1, key2

Return multiple fields using case in select statement of sql server

I have a table tb3 wherein id,name,sal are to be displayed using a SELECT statement and city,descrip fields need to be displayed in the same SELECT statement only when the flag is 'Y'. How do I do this using CASE statements?
id name sal city descrip flag
7 john 80000.00 Canada prog y
6 jone 90000.00 NY test y
3 san 70000.00 NY lead y
2 sam 70000.00 Cali sub n
1 sally 60000.00 Canada archi n
4 carl 70000.00 SA plain n
I need to do something like this.. I know it's wrong , but for a sample please have a look..
declare #test varchar(1)
select #test=flag from tb3
select id,name,case #test
when 'Y' then select city,descrip from tb3
when 'n' then 'inactive'
end as status from tb3
A result set in SQL has a fixed set of columns - you can't vary how many columns there are on a row-by-row basis. If you're wanting something that is either the columns city and descrip or the word inactive, then you'll have to join those two columns together into a single value:
select id,name,
CASE WHEN flag='Y' then city + ',' + descrip ELSE 'inactive' END as status
from tb3
Or, you can keep them as two columns, and set them as NULL when not appropriate:
select id,name,
CASE WHEN flag='Y' then city END as city,
CASE WHEN flag='Y' then descrip END as descrip
from tb3
You can directly use the name of the flag column as following
Updated:
select id,name ,case flag
when 'Y' then (city +' ' +descrip )
when 'N' then 'inactive'
end as status
from tb3
How can you use #test, because it is varchar(1) variable, it will hold either 'Y' or 'N' which will return only one type of the result. if the last row flag value is 'Y' then all rows will display city,descrip and if last row flag value is 'N' then it will display 'inactive' irrespective of the flag column result for that row.

Resources