MySQL 触发器通过重新排序其值来更新列
MySQL trigger to update a column by reordering its values
抱歉,如果标题是 miss-leading,我想不出一个与我的问题相关的更好的标题。
我已经尝试解决这个问题一段时间了,但找不到解决方案。
我有一个 table categories
:
+----+--------+----------+
| ID | Name | Position |
+----+--------+----------+
| 1 | Dogs | 4 |
| 2 | Cats | 3 |
| 3 | Birds | 10 |
| 4 | Others | 2 |
+----+--------+----------+
我需要保持 Position 列的顺序,同时也不会遗漏任何值,因此最终的 table 应该如下所示:
+----+--------+----------+
| 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 不允许您在触发器(或触发器调用的过程中)针对生成触发器的同一个 table 执行 INSERT/UPDATE/DELETE。
原因是它会导致无限循环(您的更新生成触发器,它更新 table,它再次生成触发器,它再次更新 table...) .还因为如果多个请求同时发生,它可能会产生锁冲突。
如果必须对行的位置重新编号,则应在应用程序代码中执行此操作。在初始查询完成后使用单独的语句执行此操作。
另一种选择是不用担心位置是否连续。只要确保它们的顺序正确即可。然后当你查询table时,按需生成行号。
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);
使用用户变量给出与查询相同的输出。
抱歉,如果标题是 miss-leading,我想不出一个与我的问题相关的更好的标题。
我已经尝试解决这个问题一段时间了,但找不到解决方案。
我有一个 table categories
:
+----+--------+----------+
| ID | Name | Position |
+----+--------+----------+
| 1 | Dogs | 4 |
| 2 | Cats | 3 |
| 3 | Birds | 10 |
| 4 | Others | 2 |
+----+--------+----------+
我需要保持 Position 列的顺序,同时也不会遗漏任何值,因此最终的 table 应该如下所示:
+----+--------+----------+
| 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 不允许您在触发器(或触发器调用的过程中)针对生成触发器的同一个 table 执行 INSERT/UPDATE/DELETE。
原因是它会导致无限循环(您的更新生成触发器,它更新 table,它再次生成触发器,它再次更新 table...) .还因为如果多个请求同时发生,它可能会产生锁冲突。
如果必须对行的位置重新编号,则应在应用程序代码中执行此操作。在初始查询完成后使用单独的语句执行此操作。
另一种选择是不用担心位置是否连续。只要确保它们的顺序正确即可。然后当你查询table时,按需生成行号。
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);
使用用户变量给出与查询相同的输出。