sql >> Databasteknik >  >> RDS >> Mysql

mysql-procedur för att uppdatera numerisk referens i tidigare rader när en uppdateras

Det finns två fall att överväga, tror jag:

  1. Flytta en rad så att den visas tidigare i beställningen.
  2. Flytta en rad så att den visas senare i beställningen.

Det är icke-trivialt hur som helst. Det är inte klart om det finns en unik begränsning för kolumnen "ordning"; slutresultatet är verkligen tänkt att ha en unik ordning.

Notering:

  • 'På' hänvisar till raden med värdet 'order =n' i de gamla värdena
  • 'Nn' hänvisar till raden med 'order =n' i de nya värdena

I exemplet (illustrerande av fall 1):

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

Som ett alternativ, överväg att flytta id =2 så att den har ordning =4:

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

Du lägger i princip till eller subtraherar en från de "andra" raderna, där dessa är raderna i den gamla ordningen mellan den gamla positionen för den flyttade raden och den nya positionen för den flyttade raden. I en pseudokod, använd $old och $new för att identifiera före och efter positionerna för den flyttade raden, och hantera 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;

Motsvarande kod för fall 2 ($old <$new) är:

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;

Med tanke på WHERE-satsen på UPDATEN som helhet, kanske du kan ta bort den andra WHEN i CASE och ersätta den med en enkel 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;

Jag tror att en lagrad procedur är i sin ordning - att välja mellan de två satserna baserat på indataparametrarna $old, $new. Du kanske kan göra något med en klok blandning av uttryck som '($old - $new) / ABS($old - $new) ' och 'MIN($old, $new) ' och 'MAX($old, $new) ' där MIN/MAX inte är aggregat utan komparatorfunktioner för ett par värden (som finns i Fortran, bland andra programmeringsspråk).

Notera att jag antar att medan en enskild SQL-sats körs, upprätthålls inte unikhetsbegränsningen (om någon finns) när varje rad ändras - bara när satsen är klar. Detta är nödvändigt eftersom du faktiskt inte kan kontrollera i vilken ordning raderna bearbetas. Jag känner till DBMS där detta skulle orsaka problem; Jag vet om andra där det inte skulle göra det.

Allt kan göras i en enda SQL-sats - men du vill ha en lagrad procedur för att sortera ut parametrarna till satsen. Jag använder IBM Informix Dynamic Server (11.50.FC6 på MacOS X 10.6.2), och det är en av de DBMS som upprätthåller den unika begränsningen för kolumnen 'order' i slutet av uttalandet. Jag gjorde utvecklingen av SQL utan den UNIKA begränsningen; det funkade också såklart. (Och ja, IDS tillåter dig att återställa DDL-satser som CREATE TABLE och CREATE PROCEDURE. Vad sa du? Din DBMS gör det inte? Hur pittoresk!)

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;

Paren av anrop av den lagrade proceduren med siffrorna omvända återställde den ursprungliga ordningen varje gång. Helt klart skulle jag kunna omdefiniera v_inc variabel så att istället för att bara vara ±1, var det 'LET v_inc = v_inc - v_min + v_gap; ' och då skulle MOD-uttrycket bara vara 'MOD(order + v_inc, v_gap) '. Jag har inte kontrollerat om detta fungerar med negativa tal.

Anpassning till MySQL eller annat DBMS lämnas som en övning för läsaren.



  1. Best Practices för PostgreSQL-replikering - Del 2

  2. MySql Single Table, Välj de senaste 7 dagarna och inkludera tomma rader

  3. Hur tillämpar man bindValue-metoden i LIMIT-satsen?

  4. PHP ser inte mysql-tillägget