2017-10-08 8 views
1

Désolé si le titre manque de précisions, je ne pourrais pas en trouver une meilleure en rapport avec mon problème.MySQL déclenche la mise à jour d'une colonne en réorganisant ses valeurs

J'ai essayé de résoudre ceci pendant un moment maintenant, et je n'ai pas pu trouver la solution.

J'ai une table categories:

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

J'ai besoin de garder la colonne de position dans l'ordre, de manière à ne pas manquer une valeur aussi bien, de sorte que la table finale devrait ressembler à:

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

Ce que j'ai essayé de faire, est de créer un déclencheur sur UPDATE et sur INSERT qui tenterait d'empêcher cela. Le déclencheur que j'ai créé (le même avant 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 ; 

Malheureusement, cela ne fonctionne pas comme prévu. Cela ne corrige pas les valeurs manquantes et je pense que ce n'est pas une solution parfaite.

je suis allé de l'avant et créé une procédure:

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

Mais je ne pouvais pas appeler cette procédure à partir d'un déclencheur, car il semble que MySQL ne permet pas. Je reçois l'erreur suivante:

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

mysql sortie -V:

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

Quelle est la solution idéale pour résoudre ce problème? Merci beaucoup!

+0

Je ne suis pas au courant d'une restriction dans MySQL contre l'appel de procédures stockées à partir de déclencheurs. –

+0

@GordonLinoff Je reçois l'erreur suivante: # 1442 - Impossible de mettre à jour la table 'categories' dans la fonction/trigger stockée car elle est déjà utilisée par une instruction qui a appelé cette fonction/trigger stockée. – Lambasoft

+0

@GordonLinoff Je pense que cela se produit parce que le déclencheur/la procédure se passe dans une boucle infinie, où la procédure déclenche à nouveau le déclenchement. – Lambasoft

Répondre

1

Vous ne pouvez pas faire cela dans un déclencheur. MySQL ne vous permet pas de faire un INSERT/UPDATE/DELETE dans un trigger (ou dans une procédure appelée par le trigger) contre la même table pour laquelle le trigger a été généré.

La raison en est qu'il peut en résulter des boucles infinies (votre mise à jour engendre le déclencheur, qui met à jour la table, qui génère de nouveau le trigger, qui met à jour la table à nouveau ...). Aussi parce qu'il peut créer des conflits de verrouillage si plus d'une de ces demandes se produit simultanément.

Vous devez le faire dans le code de l'application si vous devez renuméroter la position des lignes. Faites-le avec une déclaration distincte après la fin de votre requête initiale.

Une autre option est de ne pas vous soucier de rendre les positions consécutives. Assurez-vous simplement qu'ils sont dans le bon ordre. Ensuite, lorsque vous interrogez la table, générez des numéros de ligne à la demande.

SELECT (@n:[email protected]+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 | 
+---------+----+--------+----------+ 

En MySQL 8.0, vous serez en mesure de le faire avec plus de syntaxe standard en utilisant ROW_NUMBER(). Donne la même sortie que la requête à l'aide de la variable utilisateur.

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

+0

Merci pour votre réponse! Vous pensez que je peux créer un déclencheur SELECT qui ajoute dynamiquement la colonne row_num (la requête que vous avez fournie)? Je devrais le placer après ou avant la requête SELECT? Je viens de réaliser que vous êtes un directeur Oracle, j'ai de la chance :) – Lambasoft

+0

Il n'y a pas de déclencheurs SELECT. Probablement ce que vous voulez est un [VIEW] (https://dev.mysql.com/doc/refman/5.7/en/create-view.html). C'est un moyen de stocker une requête complexe comme une macro. Ensuite, vous pouvez interroger la vue comme vous le feriez d'une simple table. Mais attention à lire sur la façon d'utiliser ORDER BY dans une vue. –