开发者

How to get in one query an item and another item having one of its value nearest of the former one?

Imagine I have the following table :

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

(no specific rule applies to the order value).

I want to "move up" ou "move down" items by swapping order values.

Ex: a call to MoveItemUp(4) will results in this new table values :

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

I want to do this in a single query, but I was not yet successful.

The following query works if items order are sequential, with no "hole" (steps of 1 :)

UPDAT开发者_C百科E 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

However, I was not able to change this query to support hole. I'm trying to do :

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 

However, this does not works as expected (the query updates all items having a greater order instead of the two orders to swap).

PS: I'm working with C# 2.0 on Sybase ASE 4.5, but I assume this question is not specific to this platform. If you have a MSSQL, MySql or Oracle equivalent, I'll put effort to convert it ;)


NOTE All below solutions assume that ItemOrder is unique

EDIT Adding a solution that is more like what OP was trying, and may be more portable to Sybase, this time on Microsoft SQL Server 2008. (See below for solutions using Oracle's analytic functions, that may be more efficient if available.)

First the select to get our row selection criteria correct:

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)

Then the update based on the above:

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

Set @MoveUpId to 20 and rerun above query results in:

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

but I assume this question is not specific to this platform. The question may not be specific, but the answer probably is. For example, using Oracle, first, a table and some test data:

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;

Next create a query that returns just the rows we want to update with their new values for Order. (Which I named ItemOrder, Order being a reserved word and all.) In Oracle this is simpliest using the analytic functions lag and lead:

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

Convert that into an update statement. However the above query will not create an updateable view (in Oracle), so use merge instead:

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

Unfortunately, I do not believe lag and lead are widely implemented. Microsoft SQL Server, as far as I know, has yet to implement them. No experience with ASE, it they have them great.

Row_number() is more widely implemented. Row_number() can be used to get something that is gap free. (Row_number() is refered to as an analytic function on Oracle and a windowed function on SQL Server.) First the query:

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

Doing the update, again with merge instead of update. (Oracle does allow the update ... from ... join ... syntax, one may be able to get away with update instead of merge on other platforms.)

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

NOTE Note the solutions above will write null over OrderItems if matching against the Id for the first row.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜