How to get one to many relationship data? - sql

These are two tables having one-to-many relationship:
Employee[Table]:
---------------------------------------------------
EmpId | Name | Country | Salary | Email
--------------------------------------------------
1 John USA 875847 john#test.com
2 Mike USA 785487 mike#test.com
Lincense[Table]
----------------------------------------
EmpId | LicenseType | LincenseNumber
----------------------------------------
1 LincenseType1 12345678
1 LincenseType2 87654321
1 LincenseType3 78945613
2 LincenseType1 12345678
2 LincenseType2 87654321
2 LincenseType3 78945613
EmployeeDetails[Expected ResulSet]
-----------------------------------------------------------------------------------------------
EmpId | Name | Country | LicenseType | LicenseNumber | Salary | Email
-----------------------------------------------------------------------------------------------
1 John USA LincenseType1 12345678 875847 john#test.com
LincenseType2 87654321
LincenseType3 78945613
2 Mike USA LincenseType1 12345678 785487 mike#test.com
LincenseType2 87654321
LincenseType3 78945613
----------------------------------------------------------------------------------------------
To get result in above expected format what would be the best way to achieve that so that result contain only one row for Employee detail and all associated License details?

This here will do the trick for you. Remember if your empid and salary are int columns you can only null them or set 0 into it. Otherwise it need to be of type string
SQL Code
declare #emp table (empid int,[name] nvarchar(50),Country nvarchar(50),Salary int,[Email] nvarchar(50)
)
insert into #emp
values
(1 ,'John', 'USA', 875847, 'john#test.com'),
(2 ,'Mike', 'USA', 785487, 'mike#test.com')
declare #lic table (empid int, licensetype nvarchar(50),licencenumber int)
insert into #lic
values
(1 ,'LincenseType1', 12345678),
(1 ,'LincenseType2', 87654321),
(1 ,'LincenseType3', 78945613),
(2 ,'LincenseType1', 12345678),
(2 ,'LincenseType2', 87654321),
(2 ,'LincenseType3', 78945613)
select
empid = case when rn > 1 then null else x.empid end,
[name] = case when rn > 1 then '' else [name] end,
Country = case when rn > 1 then '' else country end,
licensetype = licensetype,
licencenumber = licencenumber,
Salary = case when rn > 1 then '' else Salary end,
Email = case when rn> 1 then '' else Email end
from (
select a.empid,[name],country,licensetype,licencenumber,Salary,Email,ROW_NUMBER() over(partition by a.empid order by licensetype) as rn from #emp a left join #lic b on a.empid = b.empid
)x
SQL Update
If Lincencetype should always be 1 on first row you can just do it like this. This will be faster
select
empid = case when licensetype !='LincenseType1' then null else a.empid end,
[name] = case when licensetype !='LincenseType1' then '' else [name] end,
Country = case when licensetype !='LincenseType1' then '' else country end,
licensetype = licensetype,
licencenumber = licencenumber,
Salary = case when licensetype !='LincenseType1' then '' else Salary end,
Email = case when licensetype !='LincenseType1' then '' else Email end
from #emp a inner join #lic b on a.empid = b.empid
Result

How about this?
SELECT Employee.EmpId, Employee.Name, Employee.Country,
Lincense.LicenseType, Lincense.LincenseNumber,
Employee.Salary, Employee.Email
FROM Employee JOIN Lincense
ON (Employee.EmpId = Lincense.EmpId);
If you are looking for empty values as you have shown, the best approach is to do that in your reporting tool rather than trying to do that in SQL

This is not what you want but close enough:
SELECT A.EMPID, A.NAME, A.COUNTRY, STUFF((SELECT ','+ b.LicenseType FROM License b WHERE A.EMPID = B.EMPID FOR XML PATH('')),1,1,'') AS LicenseType, STUFF((SELECT ','+ C.LicenseNumber FROM License C WHERE A.EMPID = C.EMPID FOR XML PATH('')),1,1,'') AS LicenseNumber, A.Salary, A.Email
FROM Employee A
It will not give you blank rows but will put LicenseType and LicenseNumber in same columns delimited by commas.

SELECT Employee.EmpId, Employee.Name, Employee.Country,
Lincense.LicenseType, Lincense.LincenseNumber,
Employee.Salary, Employee.Email
FROM Employee
JOIN Lincense ON Employee.EmpId = Lincense.EmpId;
Will give you simmilar result but all rows will be filled with data.
SELECT Employee.EmpId, Employee.Name, Employee.Country,
Lincense.LicenseType, Lincense.LincenseNumber,
Employee.Salary, Employee.Email
FROM Employee
LEFT OUTER JOIN Lincense ON Employee.EmpId = Lincense.EmpId;
Will give you result for all employees, even if it has no licence, you will notice those rows by having nulls on Lincence's properties
SELECT Employee.EmpId, Employee.Name, Employee.Country,
Lincense.LicenseType, Lincense.LincenseNumber,
Employee.Salary, Employee.Email
FROM Employee
RIGHT OUTER JOIN Lincense ON Employee.EmpId = Lincense.EmpId;
Will give you result for all licences, even if employee is not present, you will notice those rows by having nulls on Employee's properties
If you need the empty rows, it's bad practice to do that in DB. Handle that in backend code once you obtain result. Or even only when displaying the result in front end.
NOTE it's Licence or License, depending in what English you prefer.

Related

How to merge two rows having null values into one row replacing null values?

I'm having the following results from my sql query:
id
sp_firstname
sp_lastname
member_firstname
member_lastname
1
NULL
NULL
John
Smith
2
Dejuan
McLaughlin
NULL
NULL
2
NULL
NULL
Jack
Sparrow
3
John
Walker
NULL
NULL
3
NULL
NULL
Sherlock
Holmes
4
Mellie
Durgan
NULL
NULL
4
NULL
NULL
John
Waston
5
Lucy
Snider
NULL
NULL
Whereas what I need to achieve is this:
id
sp_firstname
sp_lastname
member_firstname
member_lastname
1
NULL
NULL
John
Smith
2
Dejuan
McLaughlin
Jack
Sparrow
3
John
Walker
Sherlock
Holmes
4
Mellie
Durgan
John
Waston
5
Lucy
Snider
NULL
NULL
Basically, I need to somehow merge pairs of rows that sort of have nulls crosswise.
After looking through SO answers, I could only find variants of this problem when NULL values needed to be substituted by numbers, and in that case people used max function combined with group by.
However I have several joins in my table and my NULL values need to be substituted with strings, not numbers, so max wouldn't really work here (as I thought).
Here's my sql code:
select
meeting.id,
(case when salesprofile.userid = "user".id then "user".firstname end) as sp_firstname,
(case when salesprofile.userid = "user".id then "user".lastname end) as sp_lastname,
(case when business.userid = "user".id then "user".firstname end) as member_firstname,
(case when business.userid = "user".id then "user".lastname end) as member_lastname
from
meeting
join project on project.id = meeting.projectid
left join business on business.id = project.businessid
left join salesprofile on salesprofile.id = meeting.salesprofileid
join "user" on "user".id = business.userid or "user".id = salesprofile.userid
group by
"user".id,
business.userid,
meeting.id,
salesprofile.userid;
These firstnames and lastnames come from the exact same user table, but they are taken based on different relations found in the same meeting table.
Basically, one meeting has two users: member and sp. And I needed a way to get meetings along with its member and sp users in one row.
How can I modify my sql query so that it would merge these pairs of rows with crosswise nulls into one row with data and without nulls?
Just use aggregation:
select meeting.id,
max(case when salesprofile.userid = "user".id then "user".firstname end) as sp_firstname,
max(case when salesprofile.userid = "user".id then "user".lastname end) as sp_lastname,
max(case when business.userid = "user".id then "user".firstname end) as member_firstname,
max(case when business.userid = "user".id then "user".lastname end) as member_lastname
from meeting join
project
on project.id = meeting.projectid left join
business
on business.id = project.businessid left join
salesprofile
on salesprofile.id = meeting.salesprofileid left join
"user"
on "user".id = business.userid or "user".id = salesprofile.userid
group by meeting.id;
Note the change to the group by as well.
You can wrap your results with an outer query to aggregate the columns using max and group by the id
select id, Max(sp_firstname) as sp_firstname, Max(sp_lastname) as sp_lastname...
from (
<inner query>
)x
group by id

SQL query to output all results but only one specific value from a list without duplicating

I have a table of people and a table of client types (linked by a 3rd table called client type details) which these people are linked to. Client types could be 'Friend', 'Enemy', 'Alien', or 'Monster'. Some people can have more than one of these and some people have none.
What I'm really trying to get is an output something like:
ID | Name | Friend | Enemy | Alien | Monster |
35 | John | Friend | -blank- | -blank- | Monster |
42 | Eric | -blank- | -blank- | -blank- | -blank- |
So John is both a Friend and a Monster whereas Eric isn't any. With the query I have tried creating (just with a column for the Friends in the first instant) I am getting a row for everyone but for those who are Friends I am getting 2 rows for them - one to say they are a Friend and one to say NULL
Does any of this make sense?
Query below:
SELECT DISTINCT
cl.ClientID,
cl.dcFirstName,
(SELECT Dwh.DimClientTypes.dctName
WHERE (Dwh.DimClientTypes.dctGuid IN ('52CD80A6-D4D7-4FD3-8AE8-644A40FEC108'))
) AS Friend
FROM Dwh.DimClientTypeDetails
LEFT OUTER JOIN Dwh.DimClientTypes ON Dwh.DimClientTypeDetails.dctdTypeGuid = Dwh.DimClientTypes.dctGuid
LEFT OUTER JOIN Dwh.DimClients AS cl ON Dwh.DimClientTypeDetails.dctdClientGuid = cl.dcClientGUID
I'm really not sure the best way of approaching it so any help/advice would be very gratefully received.
Thanks
Lee
You are basically looking for a pivot with strings, and you could write it using the pivot or apply but this seems simpler.
select
Id = cl.ClientId
, Name = cl.dcFirstName
, Friend = max(case when ct.dctName = 'Friend' then ct.dctName else null end)
, Enemy = max(case when ct.dctName = 'Enemy' then ct.dctName else null end)
, Alien = max(case when ct.dctName = 'Alien' then ct.dctName else null end)
, Monster = max(case when ct.dctName = 'Monster' then ct.dctName else null end)
from Dwh.DimClients as cl
left join Dwh.DimClientTypeDetails ctd on ctd.dctdClientGuid = cl.dcClientguid
left join Dwh.DimClientTypes ct on ct.dctGuid = ctd.dctdTypeGuid
group by cl.ClientId, cl.dcFirstName
For a pivot version, this is a good example: http://rextester.com/XDACE35377
create table #t (Id int not null, Name varchar(32) not null, ClientType varchar(32) null)
insert into #t values
(35, 'John', 'Friend')
,(35, 'John', 'Monster')
,(42, 'Eric', null);
select
Id
, Name
, pvt.Friend
, pvt.Enemy
, pvt.Alien
, pvt.Monster
from #t
pivot (max(ClientType) for ClientType in ([Friend],[Enemy],[Alien],[Monster])) pvt
This pivot could be done on your schema with something like this:
with c as (
select
Id = cl.ClientId
, Name = cl.dcFirstName
, ClientType = ct.dctName
from Dwh.DimClients as cl
left join Dwh.DimClientTypeDetails ctd on ctd.dctdClientGuid = cl.dcClientguid
left join Dwh.DimClientTypes ct on ct.dctGuid = ctd.dctdTypeGuid
)
select
Id
, Name
, pvt.Friend
, pvt.Enemy
, pvt.Alien
, pvt.Monster
from c
pivot (max(ClientType) for ClientType in ([Friend],[Enemy],[Alien],[Monster])) pvt

Delete and Merge Records in SQL Server

I have a table as following.
id | firstname| lastname | email | homephone
-------------------------------------------------------
1 | aaa | bbb | xxx#yyy.com | 12344444
2 | aaa | bbb | null | null
3 | ccc | ddd | zzz#fff.com | null
4 | ccc | ddd | null | 34343322
The issue is I want to keep only 1 record since these are considered as duplicates and merge the nulls so that the table appears as follows
1 aaa | bbb | xxx#yyy.com | 12344444
3 ccc | ddd | zzz#fff.com | 3433322
So far I have managed to get the duplicates using the following the code
Select
max(a.id) as original id, b.id as DuplicateId,
a.firstname, b.firstname as dup_fname,
a.lastname, b.lastname as dup_lname,
a.email, b.email
From
tbl_xxx a
join
tbl_xxx b on a.firstname = b.firstname
and a.lastname = b.lastname
and a.email is null
and a.homephone is null
and b.email is null
and b.homephone is null
and v.id < v2.id
Group by
b.id, a.firstname, b.firstname, a.lastname,
b.lastname, a.homephone, b.homephone
My merge query looks like this
update tbl_xxx
SET
email = email ,
phone = phone
where
firstname = firstname
and lastname = lastname
and email is null
and phone is null
Eventually I will get distinct rows.
Is my approach correct? kindly suggest how can I make my query more efficient
update tbl_tmpdupes3 SET
email = email ,
phone = phone ,
where
firstname=firstname
and lastname=lastname
and email is null
and homephone is null
will do absolutely nothing, since the query is not comparing the table against itself, but each row against itself. Using an update won't work either, you'll still have duplicates. What you want is to remove the duplicate data entirely, AFTER doing an update comparing the table to itself. So, basically we run one query that ensures all information in duplicates that is not null is copied to the originals, then we delete the higher value pk.
One way of tackling the problem update one column at a time:
update tbl_xxx SET tbl_xxx.email = tmp.email
FROM (SELECT tbl_xxx.firstname,tbl_xxx.lastname,tbl_xxx.email FROM tbl_xxx
WHERE NOT tbl_xxx.email IS NULL LIMIT 1)
AS tmp ON tmp.firstname = tbl_xxx.firstname AND tmp.lastname = tbl_xxx.lastname
WHERE tbl_xxx.email IS NULL;
update tbl_xxx SET tbl_xxx.phone = tmp.phone
FROM (SELECT tbl_xxx.firstname,tbl_xxx.lastname,tbl_xxx.phone FROM tbl_xxx
WHERE NOT tbl_xxx.phone IS NULL LIMIT 1)
AS tmp ON tmp.firstname = tbl_xxx.firstname AND tmp.lastname = tbl_xxx.lastname
WHERE tbl_xxx.phone is NULL;
The query finds the values of each column for first name and last name, and copies over first value it finds into null fields. So, if the original data was missing, it will add it. It may not be 100% correct if two different people in the DB have the same name, you'll have to take that into consideration.
That said, follow it up with this query, which should only delete the higher-pk row that is identical.
DELETE FROM tbl_xxx WHERE tbl_xxx.id IN (
SELECT max(id) FROM tbl_xxx
GROUP BY tbl_xxx.firstname,tbl_xxx.lastname,tbl_xxx.phone,tbl_xxx.email
HAVING count(tbl_xxx.id) > 1));
Edit: if there are potentially multiple duplicates, you could do:
DELETE FROM tbl_xxx WHERE tbl_xxx.id NOT IN (
SELECT min(id) FROM tbl_xxx
GROUP BY tbl_xxx.firstname,tbl_xxx.lastname,tbl_xxx.phone,tbl_xxx.email);
you can use Merge statement for this. Try this sample.
create table temptable (id int, firstname varchar(50), lastname varchar(50), email varchar(50), homephone varchar(50))
insert into temptable values
(1,'aaa' , 'bbb', 'xxx#yyy.com', 1234444),
(2,'aaa' , 'bbb', null, null),
(3,'ccc' , 'ddd', 'abc#ddey.com', null),
(4,'ccc' , 'ddd', null, 34343322 )
select * from temptable
;with cte as
(
select firstname, lastname
,(select top 1 id from temptable b where b.firstname = a.firstname and b.lastname = a.lastname and ( b.email is not null or b.homephone is not null)) tid
,(select top 1 email from temptable b where b.firstname = a.firstname and b.lastname = a.lastname and b.email is not null ) email
,(select top 1 homephone from temptable b where b.firstname = a.firstname and b.lastname = a.lastname and b.homephone is not null ) homephone
from temptable a
group by firstname , lastname
)
--select * from cte
merge temptable as a
using cte as b
on ( a.id = b.tid )
when matched
then
update set a.email = b.email , a.homephone = b.homephone
when not matched by source then
delete ;
select * from temptable
drop table temptable

SQL query normalize multiple row values into one row one column field

Maybe the title of the question is not the appropiate but here is the explanation:
I have the following tables:
There are only 5 benefit codes available:
So, one employee can have associated 1 to 5 benefits, but also employees without any benefit.
What I need to return in a query is a list of employees with a coded column for the benefits associated, like the following example:
So the column "benefits" is a coded column from the associated benefits of the employee.
If Peter has associated Medical and Education benefits then the coded value for "benefits" column should be as shown in the table "01001", where 0 means no association and 1 means associaton.
Right now im doing the follogin and is working but takes too long to process and Im sure there is a better way and faster:
SELECT emp.employee_id, emp.name, emp.lastname,
CASE WHEN lif.benefitcode IS NULL THEN '0' ELSE '1' END +
CASE WHEN med.benefitcode IS NULL THEN '0' ELSE '1' END +
CASE WHEN opt.benefitcode IS NULL THEN '0' ELSE '1' END +
CASE WHEN uni.benefitcode IS NULL THEN '0' ELSE '1' END +
CASE WHEN edu.benefitcode IS NULL THEN '0' ELSE '1' END as benefits
FROM employee emp
-- life
LEFT JOIN (
SELECT c.benefitcode, c.employee_id
FROM employee_benefit c
WHERE c.isactive = 1
and c.benefitcode = 'lf'
) lif on lif.employee_id = emp.employee_id
-- medical
LEFT JOIN (
SELECT c.benefitcode, c.employee_id
FROM employee_benefit c
WHERE c.isactive = 1
and c.benefitcode = 'md'
) med on med.employee_id = emp.employee_id
-- optical
LEFT JOIN (
SELECT c.benefitcode, c.employee_id
FROM employee_benefit c
WHERE c.isactive = 1
and c.benefitcode = 'op'
) opt on opt.employee_id = emp.employee_id
-- uniform
LEFT JOIN (
SELECT c.benefitcode, c.employee_id
FROM employee_benefit c
WHERE c.isactive = 1
and c.benefitcode = 'un'
) uni on uni.employee_id = emp.employee_id
-- education
LEFT JOIN (
SELECT c.benefitcode, c.employee_id
FROM employee_benefit c
WHERE c.isactive = 1
and c.benefitcode = 'ed'
) edu on edu.employee_id = emp.employee_id
Any clue on what is the best way with best performance?
Thanks a lot.
Why not just join to a table that codes the benefits to an integer (Life -> 10000, Medical -> 1000, ..., education -> 1; and then
Sum the benefit code integer;
convert the sum to a string;
prepend the string '0000' and take the right-most 5 characters.
Update:
select
EmployeeID,
right('0000' + convert(varchar(5),sum(map.value)),5)
from (
select value=10000, benefit = 'Lif' union all
select value= 1000, benefit = 'Med' union all
select value= 100, benefit = 'Uni' union all
select value= 10, benefit = 'Opt' union all
select value= 1, benefit = 'Edu'
) map
join
blah blah
group by EmployeeID
First off, note that this operation actually de-normalizes the model and I would keep the normalized table design if it were my decision. I'm not sure what "pressure" mandates this situation, but I find that such approaches make the model harder to maintain and use. Such de-normalization may slow down queries that could otherwise use indexing on the join table.
That being said, one approach is to use PIVOT, which is an SQL Server (2005+) extension. I've worked up an example on sqlfiddle. The example needs to be tailored some the actual table schema and benefit values - the pivot is just over the linking (employee_benefit) table in this case. Note the pre-filter on the benefit status to avoid the columns (and thus implicit grouping) from creeping through the PIVOT.
Query
select pvt.*
from (
select emp, benefitcode
from benefits
where isactive = 1
) as b
pivot (
-- implicit group by on other columns!
count (benefitcode)
-- the set of all values (to turn into columns)
for benefitcode in ([lf], [md], [op])
) as pvt
Definition
create table benefits (
emp int,
isactive tinyint,
benefitcode varchar(10)
)
go
insert into benefits (emp, isactive, benefitcode)
values
(1, 0, 'lf'), (1, 1, 'md'), (1, 1, 'op'),
(2, 1, 'lf'),
(3, 1, 'lf'), (3, 1, 'md'), (3, 1, 'md')
go
Result
EMP LF MD OP
1 0 1 1 -- excludes inactive LF for emp #1
2 1 0 0
3 1 2 0 -- counts multiple benefits
Note that, just like with the many left-joins, the benefit data is now column oriented over a fixed set of values. Then just manipulate the data from the resulting query (e.g. as done in the original code, but checking for >= 1) to build the desired bit array value.
Will it perform better?
I'm not sure. However, my "initial hunch" is that the query may perform much better if the employee column is indexed but the benefit is not; and it may perform worse given the inverse - inspect the query plan of both approaches to know what SQL Server is really doing.
I like the summing suggestion but I would do it inline like this:
Select
e.employee_id,
e.Name,
e.Lastname,
right('0000' + convert(varchar(5),sum(
case
when eb.benefitcode is null then 0
when eb.benefitcode = 'lf' then 10000
when eb.benefitcode = 'md' then 1000
when eb.benefitcode = 'op' then 100
when eb.benefitcode = 'un' then 10
when eb.benefitcode = 'ed' then 1
end )),5) benefits
from
Employee e
LEFT OUTER JOIN Employee_Benefit eb
on ( eb.Employee_id = e.Employee_id )
group by
e.employee_id,
e.Name,
e.Lastname
Didn't get a chance to try it for syntax but that's the general idea.
WITH CTE (EMPID,EMPNAME,LASTNAME) AS (SELECT D.* FROM TABLENAME_EMP D WHERE D.EMPID=1), CTE2(BENEFITS) AS ((SELECT SUBSTRING((SELECT ''+ C.BENEFITS FROM (SELECT A.*,B.BEN_ID,CASE WHEN B.BEN_ID IS NULL THEN '0' ELSE '1' END BENEFITS FROM BEN_EMP b JOIN TABLENAME_EMP A ON A.empid=b.empid WHERE b.EMPID IN (1) AND B.ben_id IN (1,2,3,4,5)) C ORDER BY C.BENEFITS FOR XML PATH('')),1,200000) AS BENEFITS))
select * from cte,cte2

Flattening out a normalized SQL Server 2008 R2 database

I am working with SQL Server 2008 R2.
I have 3 tables the data is normalized and I am looking to grab the 'Home' and 'Cell' phone for Bob Dole. However I need to only get the highest sequence phone number of each type. (below is an example of Bob Dole having 2 cell phones and the sequence number for each is 2 and 3 respectively)
Table PersonPhoneNumber
PersonPhoneNumberId Person PhoneNumberId PhoneNumberTypeId Sequence
Guid - vvv Bob Dole Guid - A 1 1
Guid - www Bob Dole Guid - B 2 2
Guid - xxx Bob Dole Guid - C 2 3
Table PhoneNumber
PhoneNumberId Number
Guid - A 111-111-1111
Guid - B 222-222-2222
Guid - C 333-333-3333
Table PhoneNumberType
PhoneNumberTypeId PhoneNumberType
1 Home
2 Cell
My desired output would be this (notice that I only returned the first Cell number.):
Person Home Cell
Bob Dole 111-111-1111 222-222-2222
I have been having issues flattening out the data
Any help with the query would be great!
You can use a row_number() and an aggregate function with CASE expression to convert the data from rows to columns:
select person,
max(case when rn = 1 and PhoneNumberType = 'Home' then number end) home,
max(case when rn = 1 and PhoneNumberType = 'Cell' then number end) cell
from
(
select ppn.person, pn.number,
pt.PhoneNumberType,
row_number() over(partition by ppn.person, ppn.PhoneNumberTypeId
order by ppn.sequence) rn
from PersonPhoneNumber ppn
inner join PhoneNumber pn
on ppn.PhoneNumberId = pn.PhoneNumberId
inner join PhoneNumberType pt
on ppn.PhoneNumberTypeId = pt.PhoneNumberTypeId
) d
group by person;
See SQL Fiddle with Demo
This could also be done using the PIVOT function:
select person,
home,
cell
from
(
select ppn.person, pn.number,
pt.PhoneNumberType,
row_number() over(partition by ppn.person, ppn.PhoneNumberTypeId
order by ppn.sequence) rn
from PersonPhoneNumber ppn
inner join PhoneNumber pn
on ppn.PhoneNumberId = pn.PhoneNumberId
inner join PhoneNumberType pt
on ppn.PhoneNumberTypeId = pt.PhoneNumberTypeId
) d
pivot
(
max(number)
for PhoneNumberType in (Home, Cell)
) piv
where rn = 1;
See SQL Fiddle with Demo
Here's an example with a sub-query to get the first sequence number for each Type. The outer query then uses a CASE statement to create the Home and Cell Columns.
SELECT P.Person
,MAX(CASE WHEN P.PhoneNumberTypeId = 1 THEN N.Number ELSE NULL END) AS Home
,MAX(CASE WHEN P.PhoneNumberTypeId = 2 THEN N.Number ELSE NULL END) AS Cell
FROM PersonPhoneNumber P
INNER JOIN
PhoneNumber N
ON P.PhoneNumberId = N.PhoneNumberId
INNER JOIN
(
SELECT Person
,PhoneNumberTypeId
,MIN(Sequence) AS FIRST_NUM
FROM PersonPhoneNumber
GROUP BY
Person
,PhoneNumberTypeId
) SQ1
ON P.Person = SQ1.Person
AND P.PhoneNumberTypeId = SQ1.PhoneNumberTypeId
AND P.Sequence = SQ1.FIRST_NUM
GROUP BY
P.PERSON