想象一下:
我有一个表Customers
,每个客户可以有0个或更多PhoneNumbers
。电话号码已在Customers
'CusId
及其自己的ListIx
(因此(CusId, ListIx)
)上编入索引。
我想选择一个客户列表及其第一个(min(ListIx)
)电话号码。
问题,最优雅的方法是什么?
例如:
select
c.FirstName, c.LastName, pn.PhoneNo
from Customers c
left join PhoneNumbers pn
on c.CusId = pn.CusId
and pn.ListIx = (select min(ListIx) from PhoneNumbers where CusId = c.CusId)
这样可行,但在此示例中,WHERE
子句和JOIN
足够简单。
但想象一个更复杂的例子:
while 1=1 begin
select
-- Combine all phone numbers along with their type into one string
@PhoneComp = @PhoneComp + ' ' + PhoneNo
+ case when PhTp is not null then '/' + PhTp end,
@ListIx = p.ListIx
from PhoneNumbers
where
CusId = @CusId and ListIx > @ListIx and
ListIx = (select min(ListIx) from PhoneNumbers where CusId = @CusId and ListIx > @ListIx)
end
即使这是简化的,但我担心子查询可能过于复杂。
您可以在Sybase ASE中看到以下内容:
while 1=1 begin
select
-- Combine all phone numbers along with their type into one string
@PhoneComp = @PhoneComp + ' ' + PhoneNo
+ case when PhTp is not null then '/' + PhTp end,
@ListIx = p.ListIx
from PhoneNumbers
where
CusId = @CusId and ListIx > @ListIx
having
CusId = @CusId and ListIx > @ListIx and ListIx = min(ListIx)
end
即使你真的想到它,它也没有意义,但你知道它意味着什么(幸运的是Sybase)。但该代码让MS SQL Server抱怨:
Column 'PhoneNumbers.CusId' is invalid in the HAVING clause because it is not contained in either an aggregate function or the GROUP BY clause.
Column 'PhoneNumbers.ListIx' is invalid in the HAVING clause because it is not contained in either an aggregate function or the GROUP BY clause.
你可能会问,为什么WHERE
条款基本上重复HAVING
条款?这是因为当存在WHERE
子句时,Sybase ASE会忽略HAVING
子句。
例如在Sybase ASE中:
select
ListIx, PhoneNo
from PhoneNumbers
where
CusId = @CusId
having
ListIx=min(ListIx)
无论客户(@CusId
)如何,都会获得ListIx最低的所有电话号码。编辑:我应该提到,通过添加group by CusId
结果是预期的,但Sybase ASE甚至允许这样做的事实是值得怀疑的。
我在这里提到了Sybase ASE,因为我正在努力转换许多旧的Sybase ASE SQL代码以使用MS SQL Server。虽然子查询确实有效,但我想知道我是否缺少一些明显的解决方案。
答案 0 :(得分:1)
回答直接问题
最优雅的方式是什么?
我会在SQL Server上使用OUTER APPLY
:
select
c.FirstName
,c.LastName
,pn.PhoneNo
from
Customers c
OUTER APPLY
(
SELECT TOP(1) PhoneNumbers.PhoneNo
FROM PhoneNumbers
WHERE PhoneNumbers.CusId = c.CusId
ORDER BY PhoneNumbers.ListIx
) AS ph
如果您需要一个适用于SQL Server和Sybase的代码,那就是另一个故事。
在我看来,OUTER APPLY
是最优雅,最有效的方式。它还非常清楚地显示了您所追求的内容:对于每个客户,我们正在寻找一个电话号码,这是列表中的第一个,当按ListIx
订购时。
此处我们应该使用OUTER APPLY
,而不是CROSS APPLY
,因为客户可能没有任何电话号码。使用OUTER APPLY
,客户将被包含在结果中,并且电话号码为NULL。使用CROSS APPLY
此类客户不会包含在结果中。
实际上,这取决于你真正需要的是什么,所以CROSS APPLY
可能是正确的选择。
答案 1 :(得分:1)
select
FirstName,
LastName,
PhoneNo
from (
select
ROW_NUMBER() OVER (
PARTITION BY
c.CusId
ORDER BY
pn.ListIx DESC
) r,
c.FirstName,
c.LastName,
pn.PhoneNo
from Customers c
left join PhoneNumbers pn on
c.CusId = pn.CusId
) T
where
r = 1
答案 2 :(得分:0)
以下内容适用于SQL Server:
select c.FirstName, c.LastName,
(select top 1 pn.PhoneNo
from PhoneNumbers pn
where c.CusId = pn.CusId
order by ListIx
) as phoneNo
from Customers c;
对于这两个数据库,我认为这有效:
select c.FirstName, c.LastName,
(select PhoneNo
from (select pn.PhoneNo, row_number() over (partition by CusId order by LIstIx) as seqnum
from PhoneNumbers pn
where c.CusId = pn.CusId
) t
where seqnum = 1
) as phoneNo
from Customers c;
当然,并非所有版本的Sybase都支持窗口函数。