2011-09-28 95 views
0

想象我有如下表:如何在一個查詢中獲得一個項目和另一個項目的值最接近前一個?

ID || Order 
----------- 
1 || 1 
2 || 2 
3 || 5 
4 || 20 
5 || 100 
6 || 4000 

(沒有具體的規則同樣適用於訂單價值)。

我想通過交換訂單值「向上移動」或「向下移動」項目。

例如:以MoveItemUp(4)結果將在這個新的表值調用:

ID || Order 
----------- 
1 || 1 
2 || 2 
3 || 20 <-- swapped order value 
4 || 5 <-- swapped order value 
5 || 100 
6 || 4000 

我想這樣做在一個單一的查詢,但我還沒有成功。

下面的查詢工作,如果項目順序是連續的,沒有「洞」(1 :)

UPDATE dbo.ITEMS 
set ORDER = case when c.ORDER = c2.ORDER then c.ORDER +1 else c.ORDER -1 end 

from dbo.ITEMS c 
    inner join dbo.ITEMS c2 on c.ORDER = c2.ORDER or c.ORDER = c2.ORDER + 1 
where c2.ID=4 

步驟但是,我不能夠改變這個查詢支持孔。我試圖做的事:

UPDATE dbo.ITEMS 
    set case when c.ORDER = c2.ORDER then min(c2.ORDER) else c2.ORDER end 
FROM dbo.ITEMS c 
    inner join ITEMS c2 on c2.ORDER >= c.ORDER 
    where c2.ID=4 
group by c.CAT_ID, c.ORDER 
having c.ORDER = min(c2.ORDER) or c.ORDER = c2.ORDER 

然而,這並不能按預期工作(查詢更新具有更大的訂單,而不是兩個數量級交換的所有項目)。 PS:我在Sybase ASE 4.5上使用C#2.0,但我認爲這個問題並不是特定於此平臺的。如果你有一個MSSQL中,MySQL或Oracle等效,我就把精力把它轉換;)

+0

「ID」序列中是否有空洞?機會是肯定的,但如果沒有,請將連接標準更改爲'c2.ORDER = c.ORDER + 1'。 –

+0

不幸的是,是的......實際上,我已經邏輯刪除了所有項目(一個從所有查詢中排除的標誌,簡單)和類別。 –

回答

0

注意以下所有解決方案假定ItemOrder是唯一

編輯添加一個解決方案,更像是什麼OP正在嘗試,並且可能更容易移植到Sybase,這次是在Microsoft SQL Server 2008上。(請參閱下面的解決方案,使用Oracle的分析功能,如果可用,可能會更有效。)

首先選擇讓我們行選擇標準正確:

declare @MoveUpId int 
set @MoveUpId = 4 

select current_row.Id 
    , current_row.ItemOrder 
    , prior_row.id as PriorRowId 
    , prior_row.ItemOrder as PriorItemOrder 
    , next_row.id as NextRowId 
    , next_row.ItemOrder as NextItemOrder 
from #Items current_row 
left outer join #Items prior_row 
    on prior_row.ItemOrder = (select max(ItemOrder) 
     from #Items 
     where ItemOrder < current_row.ItemOrder) 
left outer join #Items next_row 
    on next_row.ItemOrder = (select min(ItemOrder) 
     from #Items 
     where ItemOrder > current_row.ItemOrder) 
where @MoveUpId in (current_row.id, next_row.id) 

然後根據上面的更新:

update current_row 
set ItemOrder = case 
    when current_row.Id = @MoveUpId then prior_row.ItemOrder 
    else next_row.ItemOrder end 
from #Items current_row 
left outer join #Items prior_row 
    on prior_row.ItemOrder = (select max(ItemOrder) 
     from #Items 
     where ItemOrder < current_row.ItemOrder) 
left outer join #Items next_row 
    on next_row.ItemOrder = (select min(ItemOrder) 
     from #Items 
     where ItemOrder > current_row.ItemOrder) 
where @MoveUpId in (current_row.id, next_row.id) 

Id ItemOrder 
1 1 
2 2 
3 20 
4 5 
5 100 
6 4000 
10 -1 
20 -2 

設置@MoveUpId到20,然後重新運行上面的查詢結果:

Id ItemOrder 
1 1 
2 2 
3 20 
4 5 
5 100 
6 4000 
10 -2 
20 -1 

但我認爲這個問題是不特定於該平臺。這個問題可能並不具體,但答案可能是。例如,使用Oracle,首先,一張桌子和一些測試數據:

create table Items (Id number(38) not null 
    , ItemOrder number); 

insert into items values (1, 1); 
insert into items values (2, 2); 
insert into items values (3, 5); 
insert into items values (4, 20); 
insert into items values (5, 100); 
insert into items values (6, 4000); 
insert into items values (10, -1); 
insert into items values (20, -2); 
commit; 

接下來創建一個返回只是我們要與他們的新值更新爲Order該行的查詢。 (這點我叫ItemOrder,訂單是保留字,全部)在Oracle中,這是simpliest使用分析功能laglead

select * 
from (select Id 
     , ItemOrder 
     , lead(Id) over (order by Id) as LeadId 
     , lead(ItemOrder) over (order by Id) as LeadItemOrder 
     , lag(ItemOrder) over (order by Id) as LagItemOrder 
    from Items) 
where 4 in (Id, LeadId) 
order by Id; 

     ID ITEMORDER  LEADID LEADITEMORDER LAGITEMORDER 
---------- ---------- ---------- ------------- ------------ 
     3   5   4   20   2 
     4   20   5   100   5 

轉換到這update語句。但是上面的查詢將不會創建(在Oracle)的更新視圖,所以使用合併改爲:

merge into Items TRGT 
using (select Id 
     , ItemOrder 
     , lead(Id) over (order by Id) as LeadId 
     , lead(ItemOrder) over (order by Id) as LeadItemOrder 
     , lag(ItemOrder) over (order by Id) as LagItemOrder 
    from Items) SRC 
on (SRC.Id = TRGT.Id) 
when matched then update 
set ItemOrder = case TRGT.Id 
    when 4 then SRC.LagItemOrder 
    else SRC.LeadItemOrder end 
where 4 in (SRC.Id, SRC.LeadId); 

select * from Items order by Id; 

     ID ITEMORDER 
---------- ---------- 
     1   1 
     2   2 
     3   20 
     4   5 
     5  100 
     6  4000 
     10   -1 
     20   -2 

不幸的是,我不相信滯後和鉛被廣泛實施。據我所知,Microsoft SQL Server尚未實現它們。沒有ASE的經驗,他們擁有很棒的體驗。

Row_number()更廣泛地實現。 Row_number()可以用來獲得沒有間隙的東西。 (ROW_NUMBER()被refered爲在Oracle的解析函數和SQL Server上的窗口函數)。首先查詢:

with t as (select Id 
     , ItemOrder 
     , row_number() over (order by Id) as RN 
    from Items) 
select current_row.id 
    , current_row.ItemOrder 
    , next_row.Id as NextId 
    , next_row.ItemOrder NextItemOrder 
    , prior_row.ItemOrder PriorItemOrder 
from t current_row 
left outer join t next_row on next_row.RN = current_row.RN + 1 
left outer join t prior_row on prior_row.RN = current_row.RN - 1 
where 4 in (current_row.id, next_row.id); 

     ID ITEMORDER  NEXTID NEXTITEMORDER PRIORITEMORDER 
---------- ---------- ---------- ------------- -------------- 
     3   5   4   20    2 
     4   20   5   100    5 

做了更新,再次合併,而不是更新。 (甲骨文確實允許update ... from ... join ...語法,一個可能能夠逃脫更新,而不是在其他平臺上的合併。)

merge into Items TRGT 
using (with t as (select Id 
      , ItemOrder 
      , row_number() over (order by Id) as RN 
     from Items) 
    select current_row.id 
     , current_row.ItemOrder 
     , next_row.Id as NextId 
     , next_row.ItemOrder as NextItemOrder 
     , prior_row.ItemOrder as PriorItemOrder 
    from t current_row 
    left outer join t next_row on next_row.RN = current_row.RN + 1 
    left outer join t prior_row on prior_row.RN = current_row.RN - 1 
    where 4 in (current_row.id, next_row.id)) SRC 
on (TRGT.Id = SRC.Id) 
when matched then update 
set ItemOrder = case 
    when TRGT.Id = 4 then SRC.PriorItemOrder 
    else SRC.NextItemOrder end; 

select * 
from Items 
order by Id; 

     ID ITEMORDER 
---------- ---------- 
     1   1 
     2   2 
     3   20 
     4   5 
     5  100 
     6  4000 
     10   -1 
     20   -2 

注意注意上面的解決方案將改寫的OrderItems空,如果反對的ID匹配第一行。

+0

您的更新解決方案就像一個魅力。謝謝 ! –

相關問題