Há dois casos a considerar, penso eu:
- Mova uma linha para que ela apareça mais cedo na ordenação.
- Mova uma linha para que ela apareça mais tarde na ordenação.
Não é trivial de qualquer maneira. Não está claro se há uma restrição exclusiva na coluna 'ordem'; o resultado final certamente deve ter uma ordenação única.
Notação:
- 'On' refere-se à linha com valor 'order =n' nos valores antigos
- 'Nn' refere-se à linha com 'ordem =n' nos novos valores
No exemplo (ilustrativo do caso 1):
- O3 --> N1
- O1 --> N2
- O2 --> N3
Como alternativa, considere mover id =2 para que tenha ordem =4:
- O2 --> N4
- O3 --> N2
- O4 --> N3
Você está basicamente adicionando ou subtraindo uma das 'outras' linhas, onde essas são as linhas na ordem antiga entre a posição antiga da linha movida e a nova posição da linha movida. Em um pseudo-código, usando $old e $new para identificar as posições antes e depois da linha movida e lidando com o caso 1 ($old> $new):
UPDATE AnonymousTable
SET order = CASE
WHEN order = $old THEN $new
WHEN order >= $new AND order < $old THEN order + 1
END CASE
WHERE order BETWEEN $new AND $old;
O código correspondente para o caso 2 ($old <$new) é:
UPDATE AnonymousTable
SET order = CASE
WHEN order = $old THEN $new
WHEN order > $new AND order <= $old THEN order - 1
END CASE
WHERE order BETWEEN $old AND $new;
Dada a cláusula WHERE no UPDATE como um todo, você pode remover o segundo WHEN no CASE e substituí-lo por um ELSE simples.
UPDATE AnonymousTable
SET order = CASE
WHEN order = $old THEN $new
ELSE order + 1
END CASE
WHERE order BETWEEN $new AND $old;
UPDATE AnonymousTable
SET order = CASE
WHEN order = $old THEN $new
ELSE order - 1
END CASE
WHERE order BETWEEN $old AND $new;
Eu acho que um procedimento armazenado está em ordem - escolher entre as duas instruções com base nos parâmetros de entrada $old, $new. Você pode fazer algo com uma mistura judiciosa de expressões como '
($old - $new) / ABS($old - $new)
' e 'MIN($old, $new)
' e 'MAX($old, $new)
' onde os MIN/MAX não são agregados, mas funções de comparação para um par de valores (como encontrado em Fortran, entre outras linguagens de programação). Observe que estou assumindo que, enquanto uma única instrução SQL está em execução, a restrição de exclusividade (se houver) não é aplicada à medida que cada linha é alterada - somente quando a instrução é concluída. Isso é necessário porque você não pode realmente controlar a ordem em que as linhas são processadas. Eu conheço DBMS onde isso causaria problemas; Conheço outros em que não.
Tudo isso pode ser feito em uma única instrução SQL - mas você deseja que um procedimento armazenado classifique os parâmetros da instrução. Eu uso o IBM Informix Dynamic Server (11.50.FC6 no MacOS X 10.6.2), e esse é um dos DBMS que impõe a restrição exclusiva na coluna 'order' no final da instrução. Fiz o desenvolvimento do SQL sem a restrição UNIQUE; que funcionou também, é claro. (E sim, o IDS permite reverter instruções DDL como CREATE TABLE e CREATE PROCEDURE. O que você disse? Seu DBMS não? Que estranho!)
BEGIN WORK;
CREATE TABLE AnonymousTable
(
id INTEGER NOT NULL PRIMARY KEY,
title VARCHAR(10) NOT NULL,
order INTEGER NOT NULL UNIQUE
);
INSERT INTO AnonymousTable VALUES(1, 'test1', 1);
INSERT INTO AnonymousTable VALUES(2, 'test2', 2);
INSERT INTO AnonymousTable VALUES(3, 'test3', 3);
INSERT INTO AnonymousTable VALUES(4, 'test4', 4);
SELECT * FROM AnonymousTable ORDER BY order;
CREATE PROCEDURE move_old_to_new(old INTEGER, new INTEGER)
DEFINE v_min, v_max, v_gap, v_inc INTEGER;
IF old = new OR old IS NULL OR new IS NULL THEN
RETURN;
END IF;
LET v_min = old;
IF new < old THEN
LET v_min = new;
END IF;
LET v_max = old;
IF new > old THEN
LET v_max = new;
END IF;
LET v_gap = v_max - v_min + 1;
LET v_inc = (old - new) / (v_max - v_min);
UPDATE AnonymousTable
SET order = v_min + MOD(order - v_min + v_inc + v_gap, v_gap)
WHERE order BETWEEN v_min AND v_max;
END PROCEDURE;
EXECUTE PROCEDURE move_old_to_new(3,1);
SELECT * FROM AnonymousTable ORDER BY order;
EXECUTE PROCEDURE move_old_to_new(1,3);
SELECT * FROM AnonymousTable ORDER BY order;
INSERT INTO AnonymousTable VALUES(5, 'test5', 5);
INSERT INTO AnonymousTable VALUES(6, 'test6', 6);
INSERT INTO AnonymousTable VALUES(7, 'test7', 7);
INSERT INTO AnonymousTable VALUES(8, 'test8', 8);
EXECUTE PROCEDURE move_old_to_new(3,6);
SELECT * FROM AnonymousTable ORDER BY order;
EXECUTE PROCEDURE move_old_to_new(6,3);
SELECT * FROM AnonymousTable ORDER BY order;
EXECUTE PROCEDURE move_old_to_new(7,2);
SELECT * FROM AnonymousTable ORDER BY order;
EXECUTE PROCEDURE move_old_to_new(2,7);
SELECT * FROM AnonymousTable ORDER BY order;
ROLLBACK WORK;
Os pares de invocações do procedimento armazenado com os números invertidos restabeleceram a ordem original a cada vez. Claramente, eu poderia redefinir o
v_inc
variável para que, em vez de ser apenas ±1, fosse 'LET v_inc = v_inc - v_min + v_gap;
' e então a expressão MOD seria apenas 'MOD(order + v_inc, v_gap)
'. Eu não verifiquei se isso funciona com números negativos. A adaptação ao MySQL ou outro SGBD fica como exercício para o leitor.