2010-07-30 103 views
18

我有一點SQL幾乎正在做我想做的事情。我正在使用三個表,用戶,UserPhoneNumbers和UserPhoneNumberTypes。我正在嘗試通過電話號碼獲取用戶列表以進行導出。如何將LEFT JOIN限制爲SQL Server中的第一個結果?

數據庫本身是舊的並且有一些完整性問題。我的問題是,數據庫中每個電話號碼應該只有一種類型,但情況並非如此。當我運行這個時,如果他們包含兩個「家庭」號碼,我會得到每個人的多行結果。

如何修改SQL以取出列出的第一個電話號碼並忽略剩餘的數字?我在SQL Server中,並且我知道TOP語句。但是,如果我將'TOP 1'添加到LEFT JOIN select語句中,它只是給我數據庫中的第一個條目,而不是每個用戶的第一個條目。

這是SQL Server 2000

感謝,

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 
+0

取決於您所談論的SQL版本。如果它是SQL Server 2005+,則有許多選項,包括RANK查詢。 – 2010-07-30 21:23:30

+0

SQL Server是版本2000. – Justin808 2010-07-30 21:27:34

+0

解決方案是[這裏] [1],只需用左連接代替連接。 [1]:http://stackoverflow.com/questions/2043259/sql-server-how-to-join-to-first-row – qub1n 2014-05-13 19:48:19

回答

5

因爲它是SQL Server 2000和排序功能都出來了,你可以讓你的子查詢的SELECT彙總:

SELECT UserID, MAX(PhoneNumber) AS HomePhone FROM [...] GROUP BY UserID 

當且僅當你不護理返回用戶的家庭號碼......

+0

這絕對很簡單,我經常使用它。 – 2010-07-30 21:53:49

+0

謝謝,對我來說,結果編號不是問題,只有一個。 – Justin808 2010-07-30 22:16:58

0

你必須定義你的「第一次」時,有相同類型的兩個數字,然後添加一個條件是什麼意思加入到您的加入中,以便只有正確的記錄符合標準。這沒有其他捷徑。

+0

首先 - 由SELECT語句返回的第一行。表中沒有其他條件來限制結果集。 – Justin808 2010-07-30 21:26:46

7

假設的SQL Server 2005+,使用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 

心靈的what?佔位符確定的第一個數字。省略ORDER BY,如果你不小心在所有...

1

我假設您在每個連接表上都有一些主鍵字段,因爲UserID不唯一。我假設你的主鍵叫做ID。我們將採用最低ID的記錄。這符合你的「第一」標準。

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 

有關於此類問題的整整一章,名爲「曖昧Gruops」,在書中SQL Antipatterns

0

堅持,只是爲了理解這個問題。

你有兩個表:

用戶(用戶名 - > X)UserPhones(用戶名,PHONETYPE - >電話號碼) 和用戶ID/PHONETYPE不是唯一的。

首先沒有必要對臨時表:

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 

添加內部連接是必要的。

或者我錯過了什麼?

+0

好吧,我想我現在更瞭解你了... 手機桌上有ID字段嗎?你可以加入max(id)我知道我在 – Gary 2010-07-30 21:47:24

+0

之前完成了類似的工作。這些並不是真正的臨時表,只是子查詢,但你是對的,你不需要它們。 – 2010-07-30 21:51:55

0

你可以只使用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 

相反的MIN(),你可以使用MAX()爲好。

或者,你可以通過做一組:

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 
1
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 

在這個解決方案,爲每一個用戶和電話號碼類型,我是從UserPhoneNumbers桌上拿起最低的主鍵值(我猜想這個專欄被命名爲UserPhoneNumberId)。

7

每當你想,只選擇左表頂行的右表中的每一行你應該考慮使用應用運算符,而不是加入,和移動連接條件的左連接:

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 
... 
+0

這在MS SQL 2000中有效嗎? – Justin808 2010-07-30 22:10:39

+0

不,是SQL 2005及以後版本。我錯過了你只有SQL 2K的要求。 – 2010-07-31 01:02:07