2014-12-19 217 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