开发者

How do I limit a LEFT JOIN to the 1st result in SQL Server?

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜