mysql过程在更新前行时更新前行中的数字引用

Posted

技术标签:

【中文标题】mysql过程在更新前行时更新前行中的数字引用【英文标题】:mysql procedure to update numeric reference in previous rows when one is updated 【发布时间】:2011-01-28 01:53:44 【问题描述】:

有一张这样的桌子

______________________ |编号 |标题 |订购 | |-------------------------| | 1 |测试1 | 1 | |-----|--------|--------| | 2 |测试2 | 2 | |-----|--------|--------| | 3 |测试3 | 3 | |-----|--------|--------| | 4 |测试4 | 4 | '------------'

当我在我的 mysql shell 中引入对一行的单个更新时

 $sql> UPDATE `table` SET order=1 WHERE id=3; 

然后过程或方法重新采样之前更新较低值中的订单列,以使其订单像这样更新

______________________ |编号 |标题 |订购 | |-------------------------| | 1 |测试1 | 2 | |-----|--------|--------| | 2 |测试2 | 3 | |-----|--------|--------| | 3 |测试3 | 1 | |-----|--------|--------| | 4 |测试4 | 4 | '------------'

任何帮助将不胜感激,谢谢!

【问题讨论】:

您的问题是什么?你想做什么?您是否试图让它们按 order 列的值的顺序返回? (按订单订购) 想法是(我认为):将id=3的订单号设置为1(从3开始);现在,重新排序新旧值之间的记录(order = 1, 2)以保留先前的顺序,但遵循 id=3。因此,当使用 'ORDER BY order' 选择数据时,序列将是 id = 3, 1, 2, 4。 我正在寻找一种更新订单值的过程或方法,以便根据更新后的值对其进行排序。我更新了一行,然后低于该行实际顺序且高于更新顺序的值将在其值中获得 +1 所以您正在更新 id 3 并且前面的行会在订单中添加一个?所以 id 1 是 1 + 1 现在是 2 id 2 是 2 + 1 现在是 3,但 id 4 不会改变,因为它在 id 3 之后。对吗? id 是数字和自动递增的吗? 【参考方案1】:

对于所有与 threadstarter 有相同问题但不使用 IDS 作为 DBMS(如 @Jonathan Leffler)的人: 这是 MySQL 的这些过程中的一个 pastebin http://pastebin.com/AxkJQmAH

【讨论】:

欢迎来到 ***!请在您的答案中包含代码,以便每个人都可以访问它。有关更多提示,请参阅How to Answer。【参考方案2】:

我认为有两种情况需要考虑:

    移动一行,使其出现在排序的前面。 移动一行,使其在排序后出现。

无论哪种方式,这都不是微不足道的。不清楚“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 的适应留给读者作为练习。

【讨论】:

我会尝试,但它看起来像我正在寻找的解决方案,我会在它工作时检查解决,非常感谢! 使用“between”仅对您要更改的范围内的行重新编号是个好主意。 在 mysql(他似乎正在使用)中,随着每一行的更改,唯一性约束被强制执行,但是您可以通过放置“ORDER BY”来控制处理顺序“在更新。 @David Gelhar:这将使生活变得困难-您必须“双重更新”某些行以将其“移开”(可能会否定其订单价值?)以便您可以替换其他值,然后将“不碍事”行移动到最终位置。通常,单个 SQL 语句不会将单个行更新两次。【参考方案3】:

假设你传入了要更改的行的id为change_row_id,新的订单为new_order:

current_order = SELECT order from 'table' WHERE id=change_row_id;
if (current_order > new_order)
  UPDATE `table` SET order=order+1 WHERE (order > new_order AND 
    order < current_order);
else
  [you'll have to figure out how you want to handle this.  Do you want the 
   orders to all be sequential with no breaks?]
ENDYIF;

UPDATE 'table' SET order=new_order WHERE id=change_row_id;

我不知道mysql,所以你可能需要调整sql。而且您肯定希望在单个事务中执行此操作。要么提交所有内容,要么什么都不提交。

【讨论】:

如果他想降低一个元素的顺序怎么办?即:从订单3到4【参考方案4】:

另一种方法是使用浮点数而不是整数进行排序。在此设置中,您只需在更改排序时更新一行。让我们从这个开始:

id  order
 1     1
 2     2
 3     3
 4     4

现在您想更改顺序,使项目 2 出现在项目 3 和 4 之间。您所要做的就是更新项目 2,使其新的order 是一个介于 3 和 4 之间的值,例如 3.5:

id  order
 1     1
 2    3.5
 3     3
 4     4

【讨论】:

开箱即用的有趣思考! +1 表示有趣的方法 -1 表示在按浮点数排序时糟糕的数据库性能【参考方案5】:

伪代码:

CREATE TRIGGER reorder AFTER UPDATE ON `table` 
  FOR EACH ROW BEGIN
    UPDATE `table` SET order=order+1 WHERE id < 3 ORDER BY id DESC LIMIT 1; 
  END;
|

delimiter ;

上一个 ID:

UPDATE `table` SET order=order+1 WHERE id < 3 ORDER BY id DESC LIMIT 1; 

【讨论】:

【参考方案6】:

也许做 2 条更新语句:

UPDATE `table` SET ord=ord+1 WHERE ord >= 1 order by ord desc;
UPDATE `table` SET ord=1 WHERE id=3;

(可能希望将这两个操作组合成一个事务,而不是使用自动提交)

编辑:在第一个更新中添加“order by”以控制更新顺序,避免“重复键问题”。(同时,避免列名中的“order”关键字:-)

【讨论】:

这从 4 移动到 5。如果订单有唯一约束,则违反该约束,因此第一个语句失败。 Jonathan Leffler 是对的,但我看到了一个可以尝试的模式,在 where 子句中使用 2 个约束。我会尝试一下,我会在这里告诉你结果,谢谢! 您可以通过在 UPDATE 上放置“order by”来控制更新发生的顺序。请参阅dev.mysql.com/doc/refman/5.1/en/update.html 中的示例【参考方案7】:

我猜你想要达到的效果最好不要使用数据库查询,而是使用简单的排序构造。

您应该将数据库记录保存为集合或某种结构/数组/列表中的对象,这取决于您选择的语言。

然后您创建一个简单的排序算法,根据修改其他行的所有顺序的顺序对所有记录进行排序,并创建一个在传递相同的集合后自动更新所有修改的行的过程。

【讨论】:

以上是关于mysql过程在更新前行时更新前行中的数字引用的主要内容,如果未能解决你的问题,请参考以下文章

使用 T-SQL 根据先前行的值逐行更新表

菜鸟的Xamarin.Forms前行之路——各种报错问题解决方法合集(不定时更新)

Oracle sql 错误 ora-01722 无效数字 ora-02063 前行来自

Mysql - 用多个前行(不仅仅是一个)模拟滞后

如何引用 BigQuery SQL 中先前行的列值,以便执行操作或计算?

负担过重何以轻装前行