【问题标题】:MySQL trigger to update a column by reordering its valuesMySQL 触发器通过重新排序其值来更新列
【发布时间】:2017-10-08 18:40:19
【问题描述】:

抱歉,如果标题误导了,我想不出与我的问题相关的更好的标题。

我一直在尝试解决这个问题,但我找不到解决方案。

我有一张桌子categories

+----+--------+----------+
| ID |  Name  | Position |
+----+--------+----------+
|  1 | Dogs   |        4 |
|  2 | Cats   |        3 |
|  3 | Birds  |       10 |
|  4 | Others |        2 |
+----+--------+----------+

我需要保持 Position 列井然有序,以免错过任何值,因此最终表格应如下所示:

+----+--------+----------+
| ID |  Name  | Position |
+----+--------+----------+
|  1 | Dogs   |        3 |
|  2 | Cats   |        2 |
|  3 | Birds  |        4 |
|  4 | Others |        1 |
+----+--------+----------+

我尝试做的是在 UPDATE 和 INSERT 上创建一个触发器,以防止这种情况发生。我创建的触发器(在 INSERT 之前相同):

DELIMITER //
CREATE TRIGGER sortPostions BEFORE UPDATE ON categories
FOR EACH ROW BEGIN
  SET @max_pos = 0;
  SET @min_pos = 0;
  SET @max_ID = 0;
  SET @min_ID = 0;
  SELECT position, id INTO @max_pos,@max_ID FROM categories WHERE position = ( SELECT MAX(position) FROM categories);
  SELECT position, id INTO @min_pos,@min_ID FROM categories WHERE position = ( SELECT MIN(position) FROM categories);

  IF NEW.position >= @max_pos AND NEW.id != @max_ID THEN 
    SET NEW.position = @max_pos + 1; 
  END IF;

  IF NEW.position <= @min_pos AND NEW.id != @min_ID THEN 
    SET NEW.position = @min_pos - 1; 
  END IF;

  IF NEW.position < 0 THEN
    SET NEW.position = 0;
  END IF;

END//
DELIMITER ;

但不幸的是,它没有按预期工作。它没有修复缺失值,我认为这不是一个完美的解决方案。

我继续创建了一个程序:

BEGIN
    SET @n = 0;
    UPDATE categories
    SET position = @n:=@n+1
    ORDER BY position ASC;
END

但我无法从触发器调用此过程,因为 MySQL 似乎不允许这样做。我收到以下错误:

#1442 - Can't update table 'categories' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.

mysql -V 输出:

mysql  Ver 14.14 Distrib 5.5.57, for debian-linux-gnu (x86_64) using readline 6.3

解决这个问题的完美解决方案是什么? 非常感谢!

【问题讨论】:

  • 我不知道 MySQL 对从触发器调用存储过程的限制。
  • @GordonLinoff 我收到以下错误:#1442 - 无法更新存储函数/触发器中的表“类别”,因为它已被调用此存储函数/触发器的语句使用。
  • @GordonLinoff 我认为这是因为触发器/程序进入无限循环,程序再次触发触发器。
  • MySQL 不允许您在同一个表中更改与执行触发器的行不同的行。所以你不能用触发器做你想做的事情(在更新时调整每隔一行)。您必须在更新行后执行此操作(例如,通过在单独的查询中调用您的过程,或者通过在过程中对该表进行每次更改)。或者:根本不这样做,但使用例如order by position, id。它会产生同样的效果,你可以用它做同样的事情。如果您希望它看起来不错,则必须分两步完成(或不使用 MySQL)。
  • 只是为了确保您没有误解我的意思:您不必在 php 中做太多事情(尽管您显然可以在那里做)。我的意思是:您可以将“update table ...”(您希望自动执行重新排序过程,这是不可能的)替换为“update table ...; call reorder_procedure();” (插入和删除也一样),所以不是执行一个insert,而是执行两个查询,一个insert 和一个过程。

标签: mysql stored-procedures database-trigger


【解决方案1】:

您不能在触发器中执行此操作。 MySQL 不允许您在触发器中(或在触发器调用的过程中)针对生成触发器的同一个表执行 INSERT/UPDATE/DELETE。

原因是它可能导致无限循环(您的更新产生触发器,它更新表,它再次产生触发器,它再次更新表......)。也因为如果这些请求中的一个以上同时发生,它可能会产生锁冲突。

如果您必须重新编号行的位置,您应该在应用程序代码中执行此操作。完成初始查询后,使用单独的语句执行此操作。

另一种选择是不必担心使位置连续。只要确保它们的顺序正确。然后在查询表时,按需生成行号。

SELECT (@n:=@n+1) AS row_num, c.*
FROM (SELECT @n:=0 AS n) AS _init
CROSS JOIN categories AS c
ORDER BY c.position ASC;

+---------+----+--------+----------+
| row_num | id | name   | position |
+---------+----+--------+----------+
|       1 |  4 | Others |        2 |
|       2 |  2 | Cats   |        3 |
|       3 |  1 | Dogs   |        4 |
|       4 |  3 | Birds  |       10 |
+---------+----+--------+----------+

在 MySQL 8.0 中,您将能够使用 ROW_NUMBER() 使用更标准的语法来执行此操作。

SELECT ROW_NUMBER() OVER w AS row_num, c.*
FROM categories AS c 
WINDOW w AS (ORDER BY c.position ASC);

使用用户变量提供与查询相同的输出。

【讨论】:

  • 感谢您的回答!您认为我可以创建一个动态添加 row_num 列的 SELECT 触发器(您提供的查询)?我应该把它放在 SELECT 查询之后还是之前?刚刚意识到你是 Oracle 总监,我很幸运 :)
  • 没有 SELECT 触发器。您可能想要的是VIEW。这是一种存储复杂查询的方式,类似于宏。然后你可以像查询一个简单的表一样查询视图。但请注意阅读有关如何在视图中使用 ORDER BY 的信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-21
  • 2012-05-22
  • 1970-01-01
相关资源
最近更新 更多