2010-07-30 31 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