Mysql
 sql >> Datenbank >  >> RDS >> Mysql

mysql-Prozedur zum Aktualisieren der numerischen Referenz in vorherigen Zeilen, wenn eine aktualisiert wird

Ich denke, es sind zwei Fälle zu berücksichtigen:

  1. Eine Zeile verschieben, damit sie in der Reihenfolge weiter vorne erscheint.
  2. Eine Zeile verschieben, damit sie später in der Reihenfolge erscheint.

Es ist so oder so nicht trivial. Es ist nicht klar, ob es eine eindeutige Einschränkung für die Spalte „Reihenfolge“ gibt; das Endergebnis soll auf jeden Fall eine eindeutige Ordnung haben.

Schreibweise:

  • 'On' bezieht sich auf die Zeile mit dem Wert 'order =n' in den alten Werten
  • 'Nn' bezieht sich auf die Zeile mit 'order =n' in den neuen Werten

Im Beispiel (zur Veranschaulichung von Fall 1):

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

Ziehen Sie als Alternative in Erwägung, id =2 so zu verschieben, dass es order =4 hat:

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

Sie addieren oder subtrahieren im Grunde eine von den „anderen“ Zeilen, wobei dies die Zeilen in der alten Reihenfolge zwischen der alten Position der verschobenen Zeile und der neuen Position der verschobenen Zeile sind. Verwenden Sie in einem Pseudocode $old und $new, um die Vorher- und Nachher-Positionen der verschobenen Zeile zu identifizieren, und behandeln Sie Fall 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;

Der entsprechende Code für Fall 2 ($alt <$neu) lautet:

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;

Angesichts der WHERE-Klausel im UPDATE als Ganzes können Sie möglicherweise das zweite WHEN im CASE entfernen und durch ein einfaches ELSE ersetzen.

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;

Ich denke, eine gespeicherte Prozedur ist in Ordnung - die Wahl zwischen den beiden Anweisungen basierend auf den Eingabeparametern $old, $new. Vielleicht können Sie etwas mit einer vernünftigen Mischung aus Ausdrücken wie '($old - $new) / ABS($old - $new) machen ' und 'MIN($old, $new) ' und 'MAX($old, $new) ' wobei MIN/MAX keine Aggregate, sondern Komparatorfunktionen für ein Wertepaar sind (wie sie unter anderem in Fortran zu finden sind).

Beachten Sie, dass ich davon ausgehe, dass während der Ausführung einer einzelnen SQL-Anweisung die Eindeutigkeitsbeschränkung (falls vorhanden) nicht erzwungen wird, wenn jede Zeile geändert wird – nur wenn die Anweisung abgeschlossen ist. Dies ist notwendig, da Sie die Reihenfolge, in der die Zeilen verarbeitet werden, nicht wirklich steuern können. Ich kenne DBMS, wo dies Probleme verursachen würde; Ich kenne andere, bei denen dies nicht der Fall wäre.

Es kann alles in einer einzigen SQL-Anweisung erledigt werden - aber Sie möchten, dass eine gespeicherte Prozedur die Parameter für die Anweisung sortiert. Ich verwende IBM Informix Dynamic Server (11.50.FC6 unter MacOS X 10.6.2), und das ist eines der DBMS, das die eindeutige Einschränkung für die Spalte „Reihenfolge“ am Ende der Anweisung erzwingt. Ich habe die Entwicklung von SQL ohne die UNIQUE-Einschränkung durchgeführt; das hat natürlich auch funktioniert. (Und ja, IDS erlaubt es Ihnen, DDL-Anweisungen wie CREATE TABLE und CREATE PROCEDURE rückgängig zu machen. Was haben Sie gesagt? Ihr DBMS tut das nicht? Wie kurios!)

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;

Die Aufrufpaare der gespeicherten Prozedur mit umgekehrten Nummern stellten jedes Mal die ursprüngliche Reihenfolge wieder her. Natürlich könnte ich den v_inc neu definieren Variable, sodass es statt nur ±1 'LET v_inc = v_inc - v_min + v_gap; war ' und dann wäre der MOD-Ausdruck einfach 'MOD(order + v_inc, v_gap) '. Ob das mit negativen Zahlen funktioniert, habe ich nicht überprüft.

Die Anpassung an MySQL oder andere DBMS bleibt dem Leser als Übung überlassen.