sql >> Base de Datos >  >> RDS >> Mysql

procedimiento mysql para actualizar la referencia numérica en filas anteriores cuando se actualiza una

Hay dos casos a considerar, creo:

  1. Mueva una fila para que aparezca antes en el orden.
  2. Mueva una fila para que aparezca más tarde en el orden.

No es trivial de ninguna manera. No está claro si existe una restricción única en la columna 'orden'; ciertamente se supone que el resultado final tiene un orden único.

Notación:

  • 'On' se refiere a la fila con el valor 'order =n' en los valores anteriores
  • 'Nn' se refiere a la fila con 'order =n' en los nuevos valores

En el ejemplo (ilustrativo del caso 1):

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

Como alternativa, considere mover id =2 para que tenga order =4:

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

Básicamente, está sumando o restando una de las 'otras' filas, donde esas son las filas en el orden anterior entre la posición anterior de la fila movida y la nueva posición de la fila movida. En un pseudocódigo, usando $antiguo y $nuevo para identificar las posiciones anterior y posterior de la fila movida, y tratando el caso 1 ($antiguo> $nuevo):

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;

El código correspondiente para el caso 2 ($antiguo <$nuevo) es:

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;

Dada la cláusula WHERE en la ACTUALIZACIÓN como un todo, es posible que pueda eliminar el segundo CUANDO en el CASO y reemplazarlo con un ELSE simple.

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;

Creo que un procedimiento almacenado está en orden:elegir entre las dos declaraciones en función de los parámetros de entrada $old, $new. Es posible que pueda hacer algo con una combinación juiciosa de expresiones como '($old - $new) / ABS($old - $new) ' y 'MIN($old, $new) ' y 'MAX($old, $new) ' donde MIN/MAX no son agregados sino funciones de comparación para un par de valores (como se encuentra en Fortran, entre otros lenguajes de programación).

Tenga en cuenta que asumo que mientras se ejecuta una sola instrucción SQL, la restricción de unicidad (si la hay) no se aplica a medida que se cambia cada fila, solo cuando se completa la instrucción. Esto es necesario ya que en realidad no puede controlar el orden en que se procesan las filas. Sé de DBMS donde esto causaría problemas; Sé de otros en los que no lo haría.

Todo se puede hacer en una sola declaración de SQL, pero desea un procedimiento almacenado para ordenar los parámetros de la declaración. Uso IBM Informix Dynamic Server (11.50.FC6 en MacOS X 10.6.2), y ese es uno de los DBMS que impone la restricción única en la columna "orden" al final de la declaración. Hice el desarrollo del SQL sin la restricción ÚNICA; eso también funcionó, por supuesto. (Y sí, IDS le permite revertir declaraciones DDL como CREAR TABLA y CREAR PROCEDIMIENTO. ¿Qué dijo? ¿Su DBMS no lo hace? ¡Qué pintoresco!)

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;

Los pares de invocaciones del procedimiento almacenado con los números invertidos restablecieron el orden original cada vez. Claramente, podría redefinir el v_inc variable para que en lugar de ser solo ±1, fuera 'LET v_inc = v_inc - v_min + v_gap; ' y luego la expresión MOD sería simplemente 'MOD(order + v_inc, v_gap) '. No he comprobado si esto funciona con números negativos.

La adaptación a MySQL u otro DBMS se deja como ejercicio para el lector.