Как получить в одном запросе элемент, а другой элемент, одно из значений которого является ближайшим к предыдущему? - PullRequest
0 голосов
/ 28 сентября 2011

Представьте, что у меня есть следующая таблица:

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: я работаю с C # 2.0 на Sybase ASE 4.5, но я предполагаю, что этот вопрос не относится к этой платформе. Если у вас есть эквивалент MSSQL, MySql или Oracle, я приложу усилия для его преобразования;)

1 Ответ

0 голосов
/ 28 сентября 2011

ПРИМЕЧАНИЕ Все приведенные ниже решения предполагают, что 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 - зарезервированное слово и все.) В Oracle проще всего использовать аналитические функции lag и 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

Преобразуйте это в оператор обновления. Однако приведенный выше запрос не создаст обновляемое представление (в 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 () упоминается как аналитическая функция в 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

Выполнение обновления, снова с слиянием вместо обновления. (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

ПРИМЕЧАНИЕ Обратите внимание, что в приведенных выше решениях будет записано значение NULL для OrderItems при совпадении с Id для первой строки.

...