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

mysql-procedure for at opdatere numerisk reference i tidligere rækker, når en er opdateret

Der er to tilfælde at overveje, tror jeg:

  1. Flyt én række, så den vises tidligere i rækkefølgen.
  2. Flyt én række, så den vises senere i bestillingen.

Det er ikke-trivielt på nogen måde. Det er ikke klart, om der er en unik begrænsning på kolonnen 'rækkefølge'; slutresultatet formodes bestemt at have en unik bestilling.

Notation:

  • 'Til' henviser til rækken med værdien 'ordre =n' i de gamle værdier
  • 'Nn' henviser til rækken med 'ordre =n' i de nye værdier

I eksemplet (illustrerende for tilfælde 1):

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

Som et alternativ kan du overveje at flytte id =2, så den har orden =4:

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

Du tilføjer eller trækker grundlæggende en fra de 'andre' rækker, hvor disse er rækkerne i den gamle rækkefølge mellem den gamle position af den flyttede række og den nye position i den flyttede række. I en pseudo-kode, brug $old og $new til at identificere før og efter positionerne for den flyttede række, og håndtering af tilfælde 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;

Den tilsvarende kode for sag 2 ($old <$new) er:

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;

I betragtning af WHERE-klausulen på OPDATERING som helhed, kan du muligvis fjerne den anden WHEN i CASE og erstatte den med en simpel 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;

Jeg tror, ​​at en lagret procedure er i orden - at vælge mellem de to udsagn baseret på inputparametrene $old, $new. Du kan muligvis gøre noget med en fornuftig blanding af udtryk såsom '($old - $new) / ABS($old - $new) ' og 'MIN($old, $new) ' og 'MAX($old, $new) ' hvor MIN/MAX ikke er aggregater, men komparatorfunktioner for et par værdier (som fundet i Fortran, blandt andre programmeringssprog).

Bemærk, at jeg antager, at mens en enkelt SQL-sætning udføres, håndhæves entydighedsbegrænsningen (hvis nogen) ikke, da hver række ændres - kun når sætningen er fuldført. Dette er nødvendigt, da du faktisk ikke kan kontrollere rækkefølgen, som rækkerne behandles i. Jeg kender til DBMS, hvor dette ville forårsage problemer; Jeg kender andre, hvor det ikke ville.

Det hele kan gøres i en enkelt SQL-sætning - men du ønsker en lagret procedure til at sortere parametrene til sætningen. Jeg bruger IBM Informix Dynamic Server (11.50.FC6 på MacOS X 10.6.2), og det er en af ​​de DBMS, der håndhæver den unikke begrænsning på 'ordre'-kolonnen i slutningen af ​​sætningen. Jeg foretog udviklingen af ​​SQL uden den UNIKKE begrænsning; det virkede selvfølgelig også. (Og ja, IDS giver dig mulighed for at rulle tilbage DDL-sætninger som CREATE TABLE og CREATE PROCEDURE. Hvad sagde du? Din DBMS gør det ikke? Hvor hyggeligt!)

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;

Parrene af påkaldelser af den lagrede procedure med numrene omvendt genindsatte den oprindelige rækkefølge hver gang. Det er klart, jeg kunne omdefinere v_inc variabel, så det i stedet for kun at være ±1, var 'LET v_inc = v_inc - v_min + v_gap; ' og så ville MOD-udtrykket bare være 'MOD(order + v_inc, v_gap) '. Jeg har ikke tjekket, om dette virker med negative tal.

Tilpasning til MySQL eller anden DBMS efterlades som en øvelse for læseren.



  1. samtidig LÆS og SKRIV på MySQL-tabellen

  2. Sådan vises forespørgsel og resultater på separat fane i SQL Server Management Studio (SSMS) - SQL Server / TSQL-vejledning, del 15

  3. MySQL kombinere to kolonner og tilføje til en ny kolonne

  4. Sådan beregnes forskellen mellem to tidsstempler i PostgreSQL