Mysql
 sql >> Base de données >  >> RDS >> Mysql

procédure mysql pour mettre à jour la référence numérique dans les lignes précédentes lorsqu'une est mise à jour

Il y a deux cas à considérer, je pense :

  1. Déplacez une ligne pour qu'elle apparaisse plus tôt dans l'ordre.
  2. Déplacez une ligne pour qu'elle apparaisse plus tard dans l'ordre.

Ce n'est pas trivial de toute façon. Il n'est pas clair s'il existe une contrainte unique sur la colonne 'ordre' ; le résultat final est certainement censé avoir un ordre unique.

Notation :

  • 'On' fait référence à la ligne avec la valeur 'order =n' dans les anciennes valeurs
  • 'Nn' fait référence à la ligne avec 'order =n' dans les nouvelles valeurs

Dans l'exemple (illustratif du cas 1) :

  • O3 --> N1
  • O1 --> N2
  • O2 --> N3

Comme alternative, envisagez de déplacer id =2 pour qu'il ait order =4 :

  • O2 --> N4
  • O3 --> N2
  • O4 --> N3

Vous ajoutez ou soustrayez essentiellement une des "autres" lignes, où ce sont les lignes dans l'ancien ordre entre l'ancienne position de la ligne déplacée et la nouvelle position de la ligne déplacée. Dans un pseudo-code, utiliser $old et $new pour identifier les positions avant et après de la ligne déplacée, et traiter le cas 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;

Le code correspondant pour le cas 2 ($old <$new) est :

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;

Étant donné la clause WHERE sur l'UPDATE dans son ensemble, vous pourrez peut-être supprimer le deuxième WHEN dans le CASE et le remplacer par un simple 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;

Je pense qu'une procédure stockée est en ordre - en choisissant entre les deux instructions en fonction des paramètres d'entrée $old, $new. Vous pourrez peut-être faire quelque chose avec un mélange judicieux d'expressions telles que '($old - $new) / ABS($old - $new) ' et 'MIN($old, $new) ' et 'MAX($old, $new) ' où les MIN/MAX ne sont pas des agrégats mais des fonctions comparatrices pour une paire de valeurs (comme en Fortran, entre autres langages de programmation).

Notez que je suppose que pendant l'exécution d'une seule instruction SQL, la contrainte d'unicité (le cas échéant) n'est pas appliquée lorsque chaque ligne est modifiée - uniquement lorsque l'instruction se termine. Ceci est nécessaire car vous ne pouvez pas réellement contrôler l'ordre dans lequel les lignes sont traitées. Je connais des SGBD où cela causerait des problèmes ; J'en connais d'autres où ce ne serait pas le cas.

Tout peut être fait dans une seule instruction SQL - mais vous voulez qu'une procédure stockée trie les paramètres de l'instruction. J'utilise IBM Informix Dynamic Server (11.50.FC6 sur MacOS X 10.6.2), et c'est l'un des SGBD qui applique la contrainte unique sur la colonne 'order' à la fin de l'instruction. J'ai fait le développement du SQL sans la contrainte UNIQUE; cela a fonctionné aussi, bien sûr. (Et oui, IDS vous permet d'annuler les instructions DDL telles que CREATE TABLE et CREATE PROCEDURE. Qu'avez-vous dit ? Votre SGBD ne le permet pas ? Comme c'est étrange !)

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;

Les paires d'invocations de la procédure stockée avec les numéros inversés rétablissaient à chaque fois l'ordre d'origine. Clairement, je pourrais redéfinir le v_inc variable de sorte qu'au lieu d'être juste ±1, c'était 'LET v_inc = v_inc - v_min + v_gap; ' et alors l'expression MOD serait juste 'MOD(order + v_inc, v_gap) '. Je n'ai pas vérifié si cela fonctionne avec des nombres négatifs.

L'adaptation à MySQL ou à un autre SGBD est laissée en exercice au lecteur.