MySQL触发器通过重新排序其值来更新列

时间:2017-10-08 18:40:20

标签: mysql stored-procedures database-trigger

对不起,如果标题错过了,我无法想出一个与我的问题相关的更好的标题。

我已经尝试解决这个问题一段时间了,我找不到解决方案。

我有一张表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

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

1 个答案:

答案 0 :(得分: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);

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