I have a simple table schema:
Person: ID, Name
PhoneNumber: ID, Type, Number #Type can be 'home' or 'mobile'.
PersonPhoneNumber: ID, Person_ID, PhoneNumber_ID #A join table that connects the
#person to a phone number.
As data I have:
Person: 1, "Ed"
PhoneNumber: 1, "home", 1111
PhoneNumber: 2, "mobile", 2222
PersonPhoneNumber: 1, 1 /*(Person_ID)*/, 1 /*(PhoneNumber_ID*/
PersonPhoneNumber: 2, 1 /*(Person_ID)*/, 2 /*(PhoneNumber_ID*/
I want to write a view that returns:
Name |Home |Mobile
-----------------------------
"Ed" 1111 2222
"Joe" 3333 4444
... etc
Any tips on how I approach this?
Note: These tables are a snippet from a larger schema which explains why its a many to many and not more simplistic.
You could do this in a more complicated way if you have multiple numbers or might have more than home/mobile, but for now, if you only have two numbers, then this will work:
SELECT Name, HomeNumber.Number AS Home, MobileNumber.Number AS Mobile
FROM Person
LEFT JOIN PersonPhoneNumber HomeMap
ON Person.ID = HomeMap.Person_ID
LEFT JOIN PhoneNumber HomeNumber
ON HomeMap.PhoneNumber_ID = HomeNumber.ID AND HomeNumber.Type = 'home'
LEFT JOIN PersonPhoneNumber MobileMap
ON Person.ID = MobileMap.Person_ID
LEFT JOIN PhoneNumber MobileNumber
ON MobileMap.PhoneNumber_ID = MobileNumber.ID AND MobileNumber.Type = 'mobile'
BTW, if you dont want people with any numbers, then you can make the PersonPhoneNumber mapping a JOIN instead of a LEFT JOIN
1) First solution: every person can have zero/one (home/mobile) phone number (demo here).
DECLARE @Person TABLE
(
ID INT PRIMARY KEY,
Name NVARCHAR(50) NOT NULL
);
DECLARE @PhoneNumber TABLE
(
ID INT PRIMARY KEY,
[Type] VARCHAR(10) NOT NULL CHECK([Type] IN ('mobile', 'home')),
Number VARCHAR(15) NOT NULL CHECK( PATINDEX('%[^0-9]%', Number) = 0 )
);
DECLARE @PersonPhoneNumber TABLE
(
ID INT IDENTITY(2,2) PRIMARY KEY,
Person_ID INT NOT NULL, --FOREIGN KEY
PhoneNumber_ID INT NOT NULL --FOREIGN KEY
);
INSERT @Person (ID, Name)
SELECT 1, 'Ed'
UNION ALL
SELECT 2, 'Bob';
INSERT @PhoneNumber (ID, [Type], Number)
SELECT 1, 'home', '1111'
UNION ALL
SELECT 2, 'mobile', '2222'
UNION ALL
SELECT 3, 'home', '3333'
UNION ALL
SELECT 4, 'mobile', '4444';
INSERT @PersonPhoneNumber (Person_ID, PhoneNumber_ID)
SELECT 1, 1
UNION ALL
SELECT 1, 2
UNION ALL
SELECT 2, 3
UNION ALL
SELECT 2, 4;
SET ANSI_WARNINGS ON;
DECLARE @x XML;
SET @x =
(
SELECT p.ID AS Person_ID,
p.Name AS Person_Name,
pn.[Type],
pn.Number
FROM @Person p
INNER JOIN @PersonPhoneNumber ppn ON p.ID = ppn.Person_ID
INNER JOIN @PhoneNumber pn ON ppn.PhoneNumber_ID = pn.ID
WHERE p.ID IN (1,2) --Here you could write the filter for persons
FOR XML AUTO,ROOT
);
SELECT @x;
SELECT T.XMLCol.value('(@Person_ID)', 'INT') AS Person_ID,
T.XMLCol.value('(@Person_Name)', 'NVARCHAR(50)') AS Person_Name,
T.XMLCol.value('(pn[@Type="home"]/@Number)[1]', 'VARCHAR(15)') AS HomePhoneNumber,
T.XMLCol.value('(pn[@Type="mobile"]/@Number)[1]', 'VARCHAR(15)') AS MobilePhoneNumber
FROM @x.nodes('//root/p') T(XMLCol);
Results:
Person_ID Person_Name HomePhoneNumber MobilePhoneNumber
--------- ----------- --------------- -----------------
1 Ed 1111 2222
2 Bob 3333 4444
2) Second solution: if every person can have zero, one, two or more (home/mobile) phone numbers then you could use this solution (XML & FLWOR XQuery based):
DECLARE @Person TABLE
(
ID INT PRIMARY KEY,
Name NVARCHAR(50) NOT NULL
);
DECLARE @PhoneNumber TABLE
(
ID INT PRIMARY KEY,
[Type] VARCHAR(10) NOT NULL CHECK([Type] IN ('mobile', 'home')),
Number VARCHAR(15) NOT NULL CHECK( PATINDEX('%[^0-9]%', Number) = 0 )
);
DECLARE @PersonPhoneNumber TABLE
(
ID INT IDENTITY(2,2) PRIMARY KEY,
Person_ID INT NOT NULL, --FOREIGN KEY
PhoneNumber_ID INT NOT NULL --FOREIGN KEY
);
INSERT @Person (ID, Name)
SELECT 1, 'Ed'
UNION ALL
SELECT 2, 'Bob';
INSERT @PhoneNumber (ID, [Type], Number)
SELECT 1, 'home', '1111'
UNION ALL
SELECT 2, 'mobile', '2222'
UNION ALL
SELECT 3, 'mobile', '3333'
UNION ALL
SELECT 4, 'mobile', '4444';
INSERT @PersonPhoneNumber (Person_ID, PhoneNumber_ID)
SELECT 1, 1
UNION ALL
SELECT 1, 2
UNION ALL
SELECT 1, 3
UNION ALL
SELECT 2, 3;
SET ANSI_WARNINGS ON;
SELECT p.*,
CONVERT(XML, ca.XMLPhoneNumbers).query('
for $r in //root/row
where $r/@Type = "home"
order by $r/@Number
return string($r/@Number)
').value('.', 'NVARCHAR(4000)') AS HomePhoneNumbers,
CONVERT(XML, ca.XMLPhoneNumbers).query('
for $r in //root/row
where $r/@Type = "mobile"
order by $r/@Number
return string($r/@Number)
').value('.', 'NVARCHAR(4000)') AS MobilePhoneNumbers,
ca.XMLPhoneNumbers
FROM @Person p
CROSS APPLY
(
SELECT
(SELECT pn.[Type], pn.Number
FROM @PersonPhoneNumber ppn
INNER JOIN @PhoneNumber pn ON ppn.PhoneNumber_ID = pn.ID
WHERE ppn.Person_ID = p.ID
FOR XML RAW, ROOT) AS XMLPhoneNumbers --RAW generates <row ... /> elements
) ca;
Results:
ID Name HomePhoneNumbers MobilePhoneNumbers XMLPhoneNumbers
-- ----- ---------------- ------------------ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 Ed 1111 2222 3333 <root><row Type="home" Number="1111"/><row Type="mobile" Number="2222"/><row Type="mobile" Number="3333"/></root>
2 Bob 3333 <root><row Type="mobile" Number="3333"/></root>
select p.Name
, pnh.Number as Home
, pnm.Number as Mobile
from Person p
left join
PersonPhoneNumber pn
on pn.Person_ID = p.ID
left join
PhoneNumber pnh
on pnh.ID = pn.PhoneNumber_ID
and phh.Type = 'home'
left join
PhoneNumber pnm
on pnm.ID = pn.PhoneNumber_ID
and pnm.Type = 'mobile'
Hope this helps
SELECT DISTINCT P.NAME,
(SELECT DISTINCT PN1.PH_NUMBER
FROM PHONE_NUMBER PN1
WHERE PN1.ID = PN.ID
AND PN1.TYPE = 'Home') AS HOME,
(SELECT DISTINCT PN1.PH_NUMBER
FROM PHONE_NUMBER PN1
WHERE PN1.ID = PN.ID
AND PN1.TYPE = 'Mobile') AS MOBILE
FROM PERSON P, PHONE_NUMBER PN
ORDER BY P.NAME
This will help you
select * from @Person p
left join @PersonPhoneNumber ppn
on p.ID = ppn.PersonID
right join @PhoneNumber pn
on ppn.PhoneNoID = pn.ID
来源:https://stackoverflow.com/questions/10196237/sql-select-phone-numbers-from-a-many-to-many-table-with-different-types-mobile