如何在一个查询中获得一个项目和另一个项目,其中一个项目的值最接近前者?

时间:2011-09-28 15:32:34

标签: sql sybase-ase

想象一下,我有下表:

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等价物,我会努力转换它;)

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,Order是一个保留字和all。)在Oracle中,使用分析函数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

将其转换为更新语句。但是,上述查询不会创建可更新的视图(在Oracle中),因此请改用merge:

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()被称为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

使用merge而不是update进行更新。 (Oracle确实允许使用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

注意请注意,如果匹配第一行的Id,上面的解决方案将在OrderItems上写入null。