I have a SQL query that pulls a list of contacts from a small database; it pulls the data just fine. However the problem with the SQL is the fact that it will list the same contact multiple times depending on the number of emails and address associated with the contact.
It's only supposed to list it once. Here's the SQL I've written:
SELECT DISTINCT
Contact_Main.contactid, first_name, last_name, p_title,
prefix, suffix, email, address1, address2,
city, state, zip, Contact_Address_Type.addresstype,
Contact_Email_Types.emailtype,
addressid, emailid, Contact_Address_Type.addresstypeid,
Contact_Email_Types.emailtypeid,
(SELECT COUNT(addressid)
FROM Contact_Address
WHERE contactid = Contact_Main.contactid) as num_addresses,
(SELECT COUNT(emailid)
FROM Contact_Email
WHERE contactid = Contact_Main.contactid) as num_emails
FROM Contact_Main
LEFT JOIN Contact_Email ON Contact_Main.contactid =
Contact_Email.contactid
LEFT JOIN Contact_Email_Types ON Contact_Email_Types.emailtypeid =
Contact_Email.emailtypeid
LEFT JOIN Contact_Address ON Contact_Address.contactid =
Contact_Main.contactid
LEFT JOIN Contact_Address_Type ON Contact_Address_Type.addresstypeid =
Contact_Address.addresstypeid
ORDER BY last_name, first_name;
I'm sure it's a simple fix and that I need to make some part of this a sub query but for some reason I can't figure it out.
so this works fine ?
SELECT DISTINCT
cm.contactid
, first_name
, last_name
, p_title
, prefix
, suffix
, email
, address1
, address2
, city
, state
, zip
, cat.addresstype
, cet.emailtype
, addressid
, emailid
, cat.addresstypeid
, cet.emailtypeid
--, (SELECT COUNT(addressid)
-- FROM Contact_Address
-- WHERE contactid = cm.contactid) as num_addresses
--, (SELECT COUNT(emailid)
-- FROM Contact_Email
-- WHERE contactid = cm.contactid) as num_emails
FROM Contact_Main cm
LEFT JOIN Contact_Email ce ON cm.contactid =
ce.contactid
LEFT JOIN Contact_Email_Types cet ON cet.emailtypeid =
ce.emailtypeid
LEFT JOIN Contact_Address ON ca.contactid =
cm.contactid
LEFT JOIN Contact_Address_Type ON cat.addresstypeid =
ca.addresstypeid
ORDER BY cm.last_name, cm.first_name;
Okay, so when I was at work I asked my boss what was the deal with SQL code. We both took a look at it and he determined that it was returning every possible outcome. After messing around with a simulation in SQL Access it turns out we had to do a LEFT OUTER JOIN instead of just a LEFT JOIN.
SELECT DISTINCT Contact_Main.contactid, first_name, last_name, p_title,
prefix, suffix, email, address1, address2,
city, state, zip, Contact_Address_Type.addresstype,
Contact_Email_Types.emailtype,
addressid, emailid, Contact_Address_Type.addresstypeid,
Contact_Email_Types.emailtypeid,
(SELECT COUNT(addressid) FROM Contact_Address
WHERE contactid = Contact_Main.contactid) as num_addresses,
(SELECT COUNT(emailid) FROM Contact_Email
WHERE contactid = Contact_Main.contactid) as num_emails
FROM Contact_Main
LEFT OUTER JOIN Contact_Email
ON Contact_Main.contactid = Contact_Email.contactid
LEFT OUTER JOIN Contact_Email_Types
ON Contact_Email_Types.emailtypeid = Contact_Email.emailtypeid
LEFT OUTER JOIN Contact_Address
ON Contact_Address.contactid = Contact_Main.contactid
LEFT OUTER JOIN Contact_Address_Type
ON Contact_Address_Type.addresstypeid = Contact_Address.addresstypeid
ORDER BY last_name, first_name;
And that did the trick. Thanks for all the suggestions!
Related
I get this error: Not unique table/alias: 'postcode'
"SELECT sub.city AS city, sub.postalcode AS postalcode FROM
(SELECT postcode.city AS city, customers.postalcode AS postalcode, COUNT(customers.postalcode) AS postcode_numbers
FROM orders, postcode
INNER JOIN customers ON orders.userID = customers.ID
INNER JOIN postcode ON customers.postalcode = postcode.city
GROUP BY customers.postalcode) sub
WHERE sub.postcode_numbers > 3";
Remove postcode table in the from statement
SELECT sub.city AS city, sub.postalcode AS postalcode FROM
(SELECT postcode.city AS city, customers.postalcode AS postalcode, COUNT(customers.postalcode) AS postcode_numbers
FROM orders
INNER JOIN customers ON orders.userID = customers.ID
INNER JOIN postcode ON customers.postalcode = postcode.city
GROUP BY customers.postalcode) sub
WHERE sub.postcode_numbers > 3
You have postcode in both your from clause and your second join clause. Remove it from the from clause and you should be OK:
SELECT sub.city AS city, sub.postalcode AS postalcode FROM
(SELECT postcode.city AS city, customers.postalcode AS postalcode, COUNT(customers.postalcode) AS postcode_numbers
FROM orders -- Removed postcode from here
INNER JOIN customers ON orders.userID = customers.ID
INNER JOIN postcode ON customers.postalcode = postcode.city
GROUP BY customers.postalcode) sub
WHERE sub.postcode_numbers > 3
You can write this as:
SELECT p.city, c.postalcode , COUNT(*) AS num_orders
FROM orders o JOIN
customers c
ON o.userID = c.ID JOIN
postcode p
ON c.postalcode = p.city
GROUP BY p.city, c.postalcode
HAVING COUNT(*) > 3;
It is unclear whether you want one row per city or per city/postalcode. This assumes the latter. If you want one row per city, remove postalcode from both the SELECT and the GROUP BY.
Notes:
Never use commas in the FROM clause. Always use proper, explicit, standard JOIN syntax. That is your specific problem.
Table aliases make the query easier to write and to read.
The unaggregated columns in the SELECT should match the GROUP BY keys.
A subquery is not necessary.
You are using postcode table in both FROM clause and and INNER JOIN clause, neither of which have an alias, when you do an INNER JOIN you don't need to have that table in the FROM clause, and other way around of course.
If you want to join the tables in FROM clause you would do something like this:
SELECT
sub.city AS city, sub.postalcode AS postalcode
FROM
(SELECT
postcode.city AS city, customers.postalcode AS postalcode,
COUNT(customers.postalcode) AS postcode_numbers
FROM
orders, postcode
INNER JOIN
customers ON orders.userID = customers.ID
--INNER JOIN postcode ON customers.postalcode = postcode.city
WHERE
postcode.city = customers.postalcode
GROUP BY
customers.postalcode) sub
WHERE
sub.postcode_numbers > 3;
So basically you would lose the INNER JOIN postcode... line
The other way to do this is to use only the INNER JOIN clause, which is my preferred way. To do this you only need to lose the postcode in FROM clause, like so:
SELECT
sub.city AS city, sub.postalcode AS postalcode
FROM
(SELECT
postcode.city AS city, customers.postalcode AS postalcode,
COUNT(customers.postalcode) AS postcode_numbers
FROM
orders
INNER JOIN
customers ON orders.userID = customers.ID
INNER JOIN
postcode ON customers.postalcode = postcode.city
GROUP BY
customers.postalcode) sub
WHERE
sub.postcode_numbers > 3;
Hope this helps!
I have two tables:
Contact (columns are: ContactId, FirstName, LastName, EmailAddress)
Address (columns are: ContactId, AddressLine1, City, CountryCode)
Some EmailAddresses have multiple ContactId, FirstName, LastName and AddressLine1
I'm trying to get a list of DISTINCT EmailAddress and ANY name/details related to that email address.
I'm ALSO trying to SORT all email addresses by domain name (eg; all gmail.com together, all Hotmail.com together and so on)
My code so far:
SELECT DISTINCT(EmailAddress),
FirstName,
LastName,
AD.AddressLine1, AD.City, AD.CountryCode
FROM
[Contact] C
INNER JOIN
Address AD ON C.ContactId = AD.ContactId
WHERE
(set of conditions)
-------Code works fine until here but sorting by domain name doesn't work---
ORDER BY
SUBSTRING(EMailAddress,(CHARINDEX('#', EMailAddress) + 1), 1)
Error message I get is:
ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
If you want one row per email, use row_number():
SELECT *
FROM (SELECT EmailAddress, FirstName, LastName,
AD.AddressLine1, AD.City, AD.CountryCode,
ROW_NUMBER() OVER (PARTITION BY EmailAddress ORDER BY EmailAddress) as seqnum
FROM [Contact] C INNER JOIN
Address AD
ON C.ContactId = AD.ContactId
WHERE (set of conditions)
-------Code works fine until here but Sorting by domain name doesn't
) x
WHERE seqnum = 1
ORDER BY SUBSTRING(EMailAddress,(CHARINDEX('#', EMailAddress) + 1), 1)
try this, using derived table
select distinct *
from (
SELECT EmailAddress
,SUBSTRING(EMailAddress,(CHARINDEX('#',EMailAddress)+1),1) [Domain]
,FirstName
,LastName
,AD.AddressLine1
,AD.City
,AD.CountryCode
FROM [Contact] C
INNER JOIN Address AD ON C.ContactId = AD.ContactId
) x
order by domain
This should work:
SELECT distinct
c.EmailAddress
, c.SUBSTRING(EMailAddress,(CHARINDEX('#',EMailAddress)+1),1) AS [Domain]
, c.FirstName
, c.LastName
, ad.AddressLine1
, ad.City
, ad.CountryCode
FROM Contacts c
JOIN Address ad
on c.ContactId = ad.ContactId
GROUP
BY c.EmailAddress
, c.SUBSTRING(EMailAddress,(CHARINDEX('#',EMailAddress)+1),1)
, c.FirstName
, c.LastName
, ad.AddressLine1
, ad.City
, ad.CountryCode
ORDER
BY c.SUBSTRING(EMailAddress,(CHARINDEX('#',EMailAddress)+1),1)
Using this relational schema, patient ID and staff ID are foreign keys with id.staff and pid.patient being unique keys:
staff(ID, fname, lname, role)
patient(pID, pFname, pLname, bdate, address, phone)
appointment(aptID, patientID, staffID, aptDate, aptTime)
procedures(procNum, pName, price)
aptDetail(aptID, procNo)
So say if I wanted to list the names of patients with appointments with a specific staff member, i.e John Smith, how would I do that explicitly?
I've managed implicitly, but I know this is kind of frowned upon, but I can't reach it without using WHERE statements.
Any help would be appreciated, any time I try and use INNER JOIN I seem to hit a wall if it's not a simple join.
Is this the type of query you're looking for?
select distinct pFname, pLname
from patient p
join appointment a on p.pID = a.patientID
join staff s on a.staffID = s.ID
where s.fname = 'John' and s.lname = 'Smith'
You can use inner join
select
a.pID
, a.pFname
, a.pLname
, a.bdate
, a.address
, a.phone
, b.aptDate
, b.aptTime
, c.fname
, c.lname
, c.role
from patient a
INNER JOIN appointment b on b.patientID = a.pID
INNER JOIN staff c on b.staffID = c.ID on concat(fname, ' ', lname ) ='John Smith'
Something like the following should work fine:
SELECT p.*
FROM appointment AS a
INNER JOIN staff AS s ON a.staffID = s.pID
INNER JOIN patient AS p ON a.patientID = p.pID
WHERE s.ID = <yourstaffid>;
select staff.fname, staff.role,
patient.pfname, plname, appoitment.aotdate
from staff, patient, appointment
where patient.pid=appointment.patientId
and staff.id=appointment.staffid
I've looked at many recursive CTE examples on this site already and I've tried applying this to my own data. I seem to have a few more joins than most examples but I think I'm getting there. The problem I want help with is that I get an error saying:
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
I have run the first select statement of the CTE and I've repeatedly run the second select statement (with different actual position codes) and for this employee there are six levels so I can't see why I get the error.
My one area of suspicion is that I have to apply the criteria 'relationship_type_id = 0' as this is the 'Reports To' relationship type. I had tried making this a left outer join but they aren't allowed. This means I don't get the top level manager using this query (i.e. the person with no manager themselves).
I've posted the code below and any help would be much appreciated.
WITH EmployeeHierarchy (EmployeeID, LastName, FirstName, PositionCode, ReportsTo, HierarchyLevel) AS
(
SELECT
p.employee_id as EmployeeID,
p.last_name as LastName,
p.first_name as FirstName,
pos.position_code as PositionCode,
r.to_position_code as ReportsTo,
1 as HierarchyLevel
FROM
--JOIN: Personal details
dbo.person p
--JOIN: Employment links a person to a post (could have more than one)
INNER JOIN
dbo.employment e ON e.employee_id = p.employee_id
--JOIN: details of the position held
INNER JOIN
dbo.position pos ON pos.position_code = e.position_code
--JOIN: Relationships between the positions, one position reports to another position etc.
-- There are several 'relationship types', we are only interested in relationship_type_id = 0
-- as this is the 'Reports to' relationship code. Others types include 'Managed by' etc.
INNER JOIN
dbo.relationship r ON r.from_position_code = pos.position_code AND r.relationship_type_id = 0
WHERE
--CRITERIA: Use my employee Id as a starting point for testing
p.employee_id = '10076395'
UNION ALL
-- Recursive step
SELECT
p2.employee_id as EmployeeID,
p2.last_name as LastName,
p2.first_name as FirstName,
pos2.position_code as PositionCode,
r2.to_position_code as ReportsTo,
eh.HierarchyLevel + 1 AS HierarchyLevel
FROM
dbo.person p2
INNER JOIN
dbo.employment e2 ON e2.employee_id = p2.employee_id
INNER JOIN
dbo.position pos2 ON pos2.position_code = e2.position_code
INNER JOIN
dbo.relationship r2 ON r2.from_position_code = pos2.position_code AND r2.relationship_type_id = 0
--JOIN: Link this query back to the base query
INNER JOIN
EmployeeHierarchy eh ON r2.from_position_code = eh.PositionCode
)
SELECT *
FROM EmployeeHierarchy
ORDER BY HierarchyLevel, LastName, FirstName
Give this a try.
I've moved the complex joins to a separate CTE to keep the recursive section as clean and as simple as possible. If nothing else this approach should help you pinpoint your issue.
; WITH employees AS (
SELECT person.employee_id
, person.last_name
, person.first_name
, position.position_code
, relationship.to_position_code
FROM dbo.person
INNER
JOIN dbo.employment
ON employment.employee_id = person.employee_id
INNER
JOIN dbo.position
ON position.position_code = employment.position_code
INNER
JOIN dbo.relationship
ON relationship.from_position_code = position.position_code
AND relationship.relationship_type_id = 0
)
, recursive_bit AS (
SELECT employee_id
, last_name
, first_name
, position_code
, to_position_code
FROM employees
WHERE employee_id = '10076395'
UNION ALL
SELECT employees.employee_id
, employees.last_name
, employees.first_name
, employees.position_code
, employees.to_position_code
FROM recursive_bit
INNER
JOIN employees
ON employees.position_code = recursive_bit.to_position_code
)
SELECT *
FROM recursive_bit
Please try :
WITH EmployeeHierarchy (EmployeeID, LastName, FirstName, PositionCode, ReportsTo, HierarchyLevel) AS
(
SELECT
p.employee_id as EmployeeID,
p.last_name as LastName,
p.first_name as FirstName,
pos.position_code as PositionCode,
r.to_position_code as ReportsTo,
1 as HierarchyLevel
FROM
--JOIN: Personal details
dbo.person p
--JOIN: Employment links a person to a post (could have more than one)
INNER JOIN dbo.employment e
ON e.employee_id = p.employee_id
--JOIN: details of the position held
INNER JOIN dbo.position pos
ON pos.position_code = e.position_code
--JOIN: Relationships between the positions, one position reports to another position etc.
-- There are several 'relationship types', we are only interested in relationship_type_id = 0
-- as this is the 'Reports to' relationship code. Others types include 'Managed by' etc.
INNER JOIN dbo.relationship r
ON r.from_position_code = pos.position_code
and r.relationship_type_id = 0
WHERE
--CRITERIA: Use my employee Id as a starting point for testing
p.employee_id = '10076395'
UNION ALL
-- Recursive step
SELECT
p2.employee_id as EmployeeID,
p2.last_name as LastName,
p2.first_name as FirstName,
pos2.position_code as PositionCode,
r2.to_position_code as ReportsTo,
eh.HierarchyLevel + 1 AS HierarchyLevel
FROM
dbo.person p2
INNER JOIN dbo.employment e2
ON e2.employee_id = p2.employee_id
INNER JOIN dbo.position pos2
ON pos2.position_code = e2.position_code
INNER JOIN dbo.relationship r2
ON r2.from_position_code = pos2.position_code
and r2.relationship_type_id = 0
--JOIN: Link this query back to the base query
INNER JOIN EmployeeHierarchy eh
ON r2.from_position_code = eh.ReportsTo
)
SELECT *
FROM EmployeeHierarchy
ORDER BY HierarchyLevel, LastName, FirstName
I have a bit of SQL that is almost doing what I want it to do. I'm working with three tables, a Users, UserPhoneNumbers and UserPhoneNumberTypes. I'm trying to get a list of users with their phone numbers for an export.
The database itself is old and has some integrity issues. My issue is that there should only ever be 1 type of each phone number in the database but thats not the case. When I run this I get multi-line results for each person if they contain, for example, two "Home" numbers.
How can I modify the SQL to take the first phone number listed and ignore the remaining numbers? I'm in SQL Server and I know about the TOP statement. But if I add 'TOP 1' to the LEFT JOIN select statement its just giving me the 1st entry in the database, not the 1st entry for each User.
This is for SQL Server 2000.
Thanks,
SELECT Users.UserID,
Users.FirstName, Users.LastName,
HomePhone, WorkPhone, FaxNumber
FROM Users
LEFT JOIN
(SELECT UserID, PhoneNumber AS HomePhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Home') AS tmpHomePhone
ON tmpHomePhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, PhoneNumber AS WorkPhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Work') AS tmpWorkPhone
ON tmpWorkPhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, PhoneNumber AS FaxNumber
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Fax') AS tmpFaxNumber
ON tmpFaxNumber.UserID = Users.UserID
Whenever you want to select only a top row from a left table for each row in the right table you should consider using the APPLY operator instead of join, and move the join condition inside the left join:
SELECT u.UserID,
u.FirstName, u.LastName,
hn.PhoneNumber AS HomePhone
FROM Users u
OUTER APPLY (
SELECT TOP(1) PhoneNumber
FROM UserPhoneNumbers upn
LEFT JOIN UserPhoneNumberTypes upt
ON upn.UserPhoneNumberTypeID=upt.UserPhoneNumberTypeID
WHERE upt.PhoneNumberType='Home'
AND upn.UserID = u.UserID
ORDER BY ...) as hn
...
Assuming SQL Server 2005+, use ROW_NUMBER:
LEFT JOIN (SELECT UserID,
PhoneNumber AS HomePhone,
ROW_NUMBER() OVER (PARTITION BY userid ORDER BY what?) AS rank
FROM UserPhoneNumbers upn
LEFT JOIN UserPhoneNumberTypes upnt ON upnt.UserPhoneNumberTypeID = upn.UserPhoneNumberTypeID
AND upnt.PhoneNumberType='Home') AS tmpHomePhone
ON tmpHomePhone.UserID = Users.UserID
AND tmpHomePhone.rank = 1
Mind the what? placeholder for determining the first number. Omit the ORDER BY if you don't care at all...
Since it's SQL Server 2000 and ranking functions are out, you could make your subquery SELECTs aggregate:
SELECT UserID, MAX(PhoneNumber) AS HomePhone FROM [...] GROUP BY UserID
iff you don't care WHICH of a user's Home numbers are returned...
Hold on, just to understand the question.
You've got two tables:
Users (UserID --> x) UserPhones (UserID, PHoneType --> Phone Number)
and UserID / PhoneType isn't unique.
First off there's no need for temp tables:
Select
x
from
Users
inner join
(
Select
top 1 y
from
FoneTypes
where
UserID = users.UseriD
and phoneType = 'typex'
) as PhoneTypex on phonetypex.UserID = users.userID
Add inner joins as necessary.
Or am I missing something?
Select Users.UserID, Users.FirstName, Users.LastName
, PhoneNumbers.HomePhone
, PhoneNumbers.WorkPhone
, PhoneNumbers.FaxNumber
From Users
Left Join (
Select UPN.UserId
, Min ( Case When PN.PhoneNumberType = 'Home' Then UPN.PhoneNumber End ) As HomePhone
, Min ( Case When PN.PhoneNumberType = 'Work' Then UPN.PhoneNumber End ) As WorkPhone
, Min ( Case When PN.PhoneNumberType = 'Fax' Then UPN.PhoneNumber End ) As FaxPhone
From UserPhoneNumbers As UPN
Join (
Select Min(UPN1.UserPhoneNumberId) As MinUserPhoneNumberId
, UPNT1.PhoneNumberType
From UserPhoneNumbers As UPN1
Join UserPhoneNumberTypes As UPNT1
On UPNT1.UserPhoneNumberTypeID = UPN1.UserPhoneNumberTypeID
Where UPNT1.PhoneNumberType In('Home', 'Work', 'Fax')
Group By UPN1.UserID, UPNT.PhoneNumberType
) As PN
On PN.MinUserPhoneNumberId = UPN.UserPhoneNumberId
Group By UPN.UserId
) As PhoneNumbers
On PhoneNumbers.UserId = Users.UserId
In this solution, for each user and phone number type, I'm picking the lowest primary key value from the UserPhoneNumbers table (I guessed that the column was named UserPhoneNumberId).
I assume you have some primary key field on each joined table, since UserID is not unique. I'll assume your primary key is called ID. We'll take the records with the lowest ID. This meets your "first" criteria.
SELECT Users.UserID, Users.FirstName, Users.LastName, hp.HomePhone,
wp.WorkPhone, fn.FaxNumber
FROM Users
LEFT JOIN HomePhone hp ON hp.UserID = Users.UserID
LEFT JOIN HomePhone hp2 ON hp2.UserID = Users.UserID AND hp2.ID < hp.ID
LEFT JOIN WorkPhone wp ON wp.UserID = Users.UserID
LEFT JOIN WorkPhone wp2 ON wp2.UserID = Users.UserID AND wp2.ID < wp.ID
LEFT JOIN FaxNumber fn ON fn.UserID = Users.UserID
LEFT JOIN FaxNumber fn2 ON fn2.UserID = Users.UserID AND fn2.ID < fn.ID
WHERE hp2.ID IS NULL AND wp2.ID IS NULL AND fn2.ID IS NULL
There is a whole chapter on this type of issue, called "Ambiguous Gruops", in the book SQL Antipatterns.
You have to define what you mean by "first" when there are two numbers of the same type, and then add a condition to your join so that only the correct record meets the criteria. There's no other shortcut for this.
You could just use GROUP BY:
SELECT Users.UserID,
Users.FirstName, Users.LastName,
HomePhone, WorkPhone, FaxNumber
FROM Users
LEFT JOIN
(SELECT UserID, min(PhoneNumber) AS HomePhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Home'
GROUP BY userID) AS tmpHomePhone
ON tmpHomePhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, min(PhoneNumber) AS WorkPhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Work'
GROUP BY userID) AS tmpWorkPhone
ON tmpWorkPhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, min(PhoneNumber) AS FaxNumber
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Fax'
GROUP BY userID) AS tmpFaxNumber
ON tmpFaxNumber.UserID = Users.UserID
Instead of min(), you could use max() as well.
Or you could do it in one group by:
SELECT Users.UserID,
Users.FirstName, Users.LastName,
max(HomePhone) as HomePhone,
max(WorkPhone) as WorkPhone,
max(FaxNumber) as FaxNumber
FROM Users
LEFT JOIN
(SELECT UserID, PhoneNumber AS HomePhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Home') AS tmpHomePhone
ON tmpHomePhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, PhoneNumber AS WorkPhone
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Work') AS tmpWorkPhone
ON tmpWorkPhone.UserID = Users.UserID
LEFT JOIN
(SELECT UserID, PhoneNumber AS FaxNumber
FROM UserPhoneNumbers LEFT JOIN UserPhoneNumberTypes ON UserPhoneNumbers.UserPhoneNumberTypeID=UserPhoneNumberTypes.UserPhoneNumberTypeID
WHERE UserPhoneNumberTypes.PhoneNumberType='Fax') AS tmpFaxNumber
ON tmpFaxNumber.UserID = Users.UserID