Mysql
 sql >> Database >  >> RDS >> Mysql

procedura mysql per aggiornare il riferimento numerico nelle righe precedenti quando ne viene aggiornato uno

Ci sono due casi da considerare, credo:

  1. Sposta una riga in modo che appaia prima nell'ordine.
  2. Sposta una riga in modo che appaia più avanti nell'ordine.

Non è banale in entrambi i casi. Non è chiaro se esiste un vincolo univoco sulla colonna "ordine"; il risultato finale dovrebbe sicuramente avere un ordinamento univoco.

Notazione:

  • 'On' si riferisce alla riga con valore 'order =n' nei vecchi valori
  • 'Nn' si riferisce alla riga con 'order =n' nei nuovi valori

Nell'esempio (illustrativo del caso 1):

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

In alternativa, considera lo spostamento di id =2 in modo che abbia ordine =4:

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

In pratica ne stai aggiungendo o sottraendo uno dalle righe "altre", dove quelle sono le righe nel vecchio ordine tra la vecchia posizione della riga spostata e la nuova posizione della riga spostata. In uno pseudo-codice, utilizzando $vecchio e $nuovo per identificare le posizioni prima e dopo della riga spostata e occupandosi del caso 1 ($vecchio> $nuovo):

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;

Il codice corrispondente per il caso 2 ($vecchio <$nuovo) è:

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;

Data la clausola WHERE sull'UPDATE nel suo insieme, potresti essere in grado di rimuovere il secondo WHEN nel CASE e sostituirlo con un semplice 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;

Penso che una procedura memorizzata sia in ordine:scegliere tra le due istruzioni in base ai parametri di input $vecchio, $nuovo. Potresti essere in grado di fare qualcosa con un sapiente mix di espressioni come '($old - $new) / ABS($old - $new) ' e 'MIN($old, $new) ' e 'MAX($old, $new) ' dove MIN/MAX non sono aggregati ma funzioni di comparazione per una coppia di valori (come si trova in Fortran, tra gli altri linguaggi di programmazione).

Si noti che suppongo che mentre è in esecuzione una singola istruzione SQL, il vincolo di unicità (se presente) non viene applicato poiché ogni riga viene modificata, solo al termine dell'istruzione. Ciò è necessario poiché non puoi effettivamente controllare l'ordine in cui vengono elaborate le righe. Conosco DBMS in cui ciò causerebbe problemi; Conosco altri dove non sarebbe.

Tutto può essere fatto in un'unica istruzione SQL, ma si desidera che una procedura memorizzata ordini i parametri dell'istruzione. Uso IBM Informix Dynamic Server (11.50.FC6 su MacOS X 10.6.2), e questo è uno dei DBMS che impone il vincolo univoco sulla colonna "ordine" alla fine dell'istruzione. Ho fatto lo sviluppo dell'SQL senza il vincolo UNIQUE; anche quello ha funzionato, ovviamente. (E sì, IDS ti consente di ripristinare le istruzioni DDL come CREATE TABLE e CREATE PROCEDURE. Cosa hai detto? Il tuo DBMS non lo fa? Che strano!)

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;

Le coppie di invocazioni della stored procedure con i numeri invertiti ripristinavano ogni volta l'ordine originale. Chiaramente, potrei ridefinire il v_inc variabile in modo che invece di essere solo ±1, fosse 'LET v_inc = v_inc - v_min + v_gap; ' e quindi l'espressione MOD sarebbe solo 'MOD(order + v_inc, v_gap) '. Non ho verificato se funziona con numeri negativi.

L'adattamento a MySQL o ad altri DBMS è lasciato come esercizio per il lettore.