Myślę, że należy rozważyć dwa przypadki:
- Przesuń jeden wiersz, aby pojawił się wcześniej w kolejności.
- Przesuń jeden wiersz, aby pojawił się później w kolejności.
Tak czy inaczej, to nie jest trywialne. Nie jest jasne, czy istnieje jednoznaczne ograniczenie w kolumnie „porządek”; wynik końcowy z pewnością ma mieć unikalną kolejność.
Notacja:
- „On” odnosi się do wiersza z wartością „order =n” w starych wartościach
- „Nn” odnosi się do wiersza z „kolejność =n” w nowych wartościach
W przykładzie (ilustrującym przypadek 1):
- O3 --> N1
- O1 --> N2
- O2 --> N3
Jako alternatywę rozważ przeniesienie id =2, aby miało porządek =4:
- O2 --> N4
- O3 --> N2
- O4 --> N3
Zasadniczo dodajesz lub odejmujesz jeden z „innych” wierszy, gdzie są to wiersze w starej kolejności między starą pozycją przeniesionego wiersza a nową pozycją przeniesionego wiersza. W pseudokodzie, użycie $old i $new do zidentyfikowania pozycji przed i po przeniesionym wierszu oraz radzenie sobie z przypadkiem 1 ($stary> $nowy):
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;
Odpowiedni kod dla przypadku 2 ($stary <$nowy) to:
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;
Biorąc pod uwagę klauzulę WHERE w UPDATE jako całości, możesz usunąć drugie WHEN w CASE i zastąpić je prostym ELSE.
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;
Myślę, że procedura składowana jest w porządku — wybór między dwoma instrukcjami na podstawie parametrów wejściowych $stary, $nowy. Możesz być w stanie zrobić coś za pomocą rozsądnej kombinacji wyrażeń, takich jak „($old - $new) / ABS($old - $new)
' i 'MIN($old, $new)
' i 'MAX($old, $new)
' gdzie MIN/MAX nie są agregacjami, ale funkcjami porównawczymi dla pary wartości (jak w Fortran, między innymi językami programowania).
Zauważ, że zakładam, że podczas wykonywania pojedynczej instrukcji SQL ograniczenie unikalności (jeśli istnieje) nie jest wymuszane, ponieważ każdy wiersz jest zmieniany — tylko po zakończeniu instrukcji. Jest to konieczne, ponieważ nie możesz kontrolować kolejności przetwarzania wierszy. Wiem o DBMS, gdzie mogłoby to spowodować kłopoty; Znam inne, w których by nie było.
Wszystko to można wykonać w jednej instrukcji SQL — ale potrzebujesz procedury składowanej, aby uporządkować parametry instrukcji. Używam IBM Informix Dynamic Server (11.50.FC6 na MacOS X 10.6.2) i jest to jeden z DBMS, który wymusza unikatowe ograniczenie w kolumnie „order” na końcu instrukcji. Zrobiłem rozwój SQL bez ograniczenia UNIQUE; to też oczywiście zadziałało. (I tak, IDS pozwala na wycofywanie instrukcji DDL, takich jak CREATE TABLE i CREATE PROCEDURE. Co powiedziałeś? Twój DBMS nie? Jakie to osobliwe!)
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;
Pary wywołań procedury składowanej z odwróconymi numerami za każdym razem przywracały pierwotną kolejność. Oczywiście mógłbym przedefiniować v_inc
zmienna tak, aby zamiast być tylko ±1, była 'LET v_inc = v_inc - v_min + v_gap;
' i wtedy wyrażenie MOD będzie po prostu 'MOD(order + v_inc, v_gap)
„. Nie sprawdziłem, czy działa to z liczbami ujemnymi.
Adaptacja do MySQL lub innego DBMS jest pozostawiona jako ćwiczenie dla czytelnika.