我认为有两种情况需要考虑:
- 移动一行,使其出现在排序的前面。
- 移动一行,使其在排序后出现。
无论哪种方式,这都不是微不足道的。不清楚“order”列是否存在唯一约束;最终结果当然应该具有唯一的顺序。
符号:
- 'On' 是指旧值中值为 'order = n' 的行
- 'Nn' 指的是新值中带有 'order = n' 的行
在示例中(示例 1):
- O3 --> N1
- O1 --> N2
- O2 --> N3
作为替代方案,考虑移动 id = 2,使其具有 order = 4:
- O2 --> N4
- O3 --> N2
- O4 --> N3
您基本上是在“其他”行中添加或减去一个,其中那些是移动行的旧位置和移动行的新位置之间的旧顺序的行。在伪代码中,使用 $old 和 $new 来识别移动行的前后位置,并处理 case 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;
案例2($old
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;
鉴于整个 UPDATE 上的 WHERE 子句,您可以删除 CASE 中的第二个 WHEN 并用简单的 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;
我认为存储过程是有序的 - 根据输入参数 $old、$new 在两个语句之间进行选择。您可能可以通过明智地组合表达式来做一些事情,例如 '($old - $new) / ABS($old - $new)' 和 'MIN($old, $new)' 和 'MAX($old, $new)' 其中 MIN/MAX 不是聚合,而是一对值的比较器函数(如在 Fortran 和其他编程语言中找到)。
请注意,我假设在执行单个 SQL 语句时,不会强制执行唯一性约束(如果有),因为每一行都发生了更改 - 仅当语句完成时。这是必要的,因为您实际上无法控制处理行的顺序。我知道这会导致麻烦的 DBMS;我知道其他人不会。
这一切都可以在一条 SQL 语句中完成 - 但您确实需要一个存储过程来整理语句的参数。我使用 IBM Informix Dynamic Server(MacOS X 10.6.2 上的 11.50.FC6),这是在语句末尾对“订单”列强制执行唯一约束的 DBMS 之一。我在没有 UNIQUE 约束的情况下开发了 SQL;当然,这也有效。 (是的,IDS 确实允许您回滚 DDL 语句,例如 CREATE TABLE 和 CREATE PROCEDURE。您说什么?您的 DBMS 不允许?多么古怪!)
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;
数字反转的存储过程的调用对每次都恢复原始顺序。显然,我可以重新定义v_inc 变量,使其不再是±1,而是'LET v_inc = v_inc - v_min + v_gap;',然后MOD 表达式将只是'MOD(order + v_inc, v_gap)'。我没有检查这是否适用于负数。
对 MySQL 或其他 DBMS 的适应留给读者作为练习。