如何将 LEFT JOIN 限制为 SQL Server 中的第一个结果?
Posted
技术标签:
【中文标题】如何将 LEFT JOIN 限制为 SQL Server 中的第一个结果?【英文标题】:How do I limit a LEFT JOIN to the 1st result in SQL Server? 【发布时间】:2011-03-23 11:18:51 【问题描述】:我有一点 SQL 几乎可以做我想做的事。我正在使用三个表,即用户、UserPhoneNumbers 和 UserPhoneNumberTypes。我正在尝试获取用户列表及其电话号码以进行导出。
数据库本身很旧并且存在一些完整性问题。我的问题是数据库中每个电话号码应该只有一种类型,但事实并非如此。当我运行这个时,如果每个人包含例如两个“家庭”数字,我会得到多行结果。
如何修改 SQL 以获取列出的第一个电话号码并忽略其余号码?我在 SQL Server 中,我知道 TOP 语句。但是,如果我将“TOP 1”添加到 LEFT JOIN 选择语句中,它只会给我数据库中的第一个条目,而不是每个用户的第一个条目。
这适用于 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
【问题讨论】:
取决于您正在谈论的 SQL 版本。如果是 SQL Server 2005+,你有很多选择,包括 RANK 查询。 解决方法是[这里][1],将join替换为left join即可。 [1]:***.com/questions/2043259/… 【参考方案1】:当您只想从左表中选择第一行为右表中的每一行时,您应该考虑使用 APPLY 运算符而不是连接,并将连接条件 移到内部 左连接:
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
...
【讨论】:
这在 MS SQL 2000 中有效吗? 不,是 SQL 2005 及之后的版本。我想念你只要求 SQL 2K。【参考方案2】:由于它是 SQL Server 2000 并且排名功能已经出来,你可以让你的子查询 SELECTs 聚合:
SELECT UserID, MAX(PhoneNumber) AS HomePhone FROM [...] GROUP BY UserID
如果您不在乎返回用户的哪个家庭号码...
【讨论】:
这个很简单,我也经常用这个。 谢谢,对我来说,结果数字不是问题,所以只有一个。【参考方案3】:假设 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...
【讨论】:
【参考方案4】:稍等,只是为了理解问题。
你有两张桌子:
用户 (UserID --> x) UserPhones (UserID, PHoneType --> 电话号码) 并且 UserID / 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
根据需要添加内部连接。
还是我错过了什么?
【讨论】:
好的,我想我现在更了解你了……电话桌上有 ID 字段吗?你也许可以加入我知道我以前做过类似的 max(id) 那些不是真正的临时表,只是子查询,但你是对的,你不需要它们。 @Gary - 我在内部连接中使用了“Select top 1”的变体,这帮助了我。谢谢!【参考方案5】: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
)。
【讨论】:
【参考方案6】:我假设您在每个连接表上都有一些主键字段,因为 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
在SQL Antipatterns一书中有一整章关于这类问题,称为“Ambiguous Gruops”。
【讨论】:
【参考方案7】:当有两个相同类型的数字时,您必须定义“第一个”的含义,然后将条件添加到您的联接中,以便只有正确的记录符合条件。没有其他捷径可做。
【讨论】:
First - select 语句返回的第一行。表中没有其他标准可以限制结果集。【参考方案8】:您可以只使用 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
你也可以使用 max() 来代替 min()。
或者您可以通过以下方式在一组中完成:
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
【讨论】:
以上是关于如何将 LEFT JOIN 限制为 SQL Server 中的第一个结果?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 LEFT JOIN 查询将 RAW SQL 转换为 DQL
sql中的inner join ,left join ,right join