2014-12-19 160 views
1

想象以下具有最小值(一些列的)的行:选择根据WHERE子句

我有一个表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 s为很简单。

但想象一个更复杂的例子:

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. 

你可能会问,为什么是HAVING子句中基本上重复了WHERE条款?这是因为在存在HAVING子句时,Sybase ASE会忽略WHERE子句。

例如Sybase ASE中:

select 
    ListIx, PhoneNo 
    from PhoneNumbers 
    where 
    CusId = @CusId 
    having 
    ListIx=min(ListIx) 

会得到具有最低ListIx,不管客户(@CusId)的所有电话号码。编辑:我应该提到,通过添加group by CusId的结果如预期的那样,但Sybase ASE甚至允许这样的事实值得怀疑。

我在这里提到Sybase ASE,因为我正在转换很多旧的Sybase ASE SQL代码以便与MS SQL Server一起使用。虽然子查询确实有效,但我确实怀疑我是否缺少一些明显的解决方案。

+0

搜索ROW_NUMBER的计算器。 ROW_NUMBER()OVER(PARTITION BY CustId ORDER BY ListIx desc)r(..)其中r = 1(对于MSSQL,即sybase加入子查询时由CustId以min(ListIx)组) – mxix 2014-12-19 11:03:16

+0

@mxix:不是Sybase 15.x应该有'ROW_NUMBER()'?尽管这是一个有趣的解决方案,但我担心子查询将成为我现在使用的,以确保代码在Sybase和MS SQL中都能正常工作。 – Svip 2014-12-19 11:06:33

+0

我不太了解Sybase。只是想指出一个交叉的数据库解决方案。如果sybase也有ROW_NUMBER试试看,那么它是一个简单的解决方案。 – mxix 2014-12-19 11:21:53

回答

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
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 
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支持窗口并不是所有版本功能。

+0

Sybase的语法不正确。 – WhyGeeEx 2015-08-28 16:30:07