sql >> Database >  >> RDS >> Mysql

mysql-procedure om de numerieke verwijzing in vorige rijen bij te werken wanneer er een wordt bijgewerkt

Ik denk dat er twee gevallen zijn om te overwegen:

  1. Verplaats één rij zodat deze eerder in de volgorde verschijnt.
  2. Verplaats één rij zodat deze later in de volgorde verschijnt.

Het is hoe dan ook niet triviaal. Het is niet duidelijk of er een unieke restrictie is op de kolom 'order'; het eindresultaat moet zeker een unieke volgorde hebben.

Notatie:

  • 'Aan' verwijst naar de rij met waarde 'order =n' in de oude waarden
  • 'Nn' verwijst naar de rij met 'order =n' in de nieuwe waarden

In het voorbeeld (illustratief geval 1):

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

Overweeg als alternatief om id =2 te verplaatsen, zodat het volgorde =4:

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

U voegt in feite een rij toe aan of trekt deze af van de 'andere' rijen, waarbij dat de rijen zijn in de oude volgorde tussen de oude positie van de verplaatste rij en de nieuwe positie van de verplaatste rij. In een pseudo-code, $old en $new gebruiken om de voor en na posities van de verplaatste rij te identificeren, en case 1 ($oud> $nieuw) af te handelen:

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;

De corresponderende code voor case 2 ($old <$new) is:

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;

Gezien de WHERE-clausule op de UPDATE als geheel, kunt u mogelijk de tweede WHEN in de CASE verwijderen en vervangen door een eenvoudige 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;

Ik denk dat een opgeslagen procedure in orde is - kiezen tussen de twee verklaringen op basis van de invoerparameters $old, $new. Misschien kun je iets doen met een oordeelkundige mix van uitdrukkingen zoals '($old - $new) / ABS($old - $new) ' en 'MIN($old, $new) ' en 'MAX($old, $new) ' waarbij de MIN/MAX geen aggregaten zijn maar comparatorfuncties voor een paar waarden (zoals gevonden in Fortran, naast andere programmeertalen).

Merk op dat ik aanneem dat terwijl een enkele SQL-instructie wordt uitgevoerd, de uniciteitsbeperking (indien aanwezig) niet wordt afgedwongen als elke rij wordt gewijzigd - alleen wanneer de instructie is voltooid. Dit is nodig omdat u de volgorde waarin de rijen worden verwerkt niet echt kunt bepalen. Ik ken DBMS waar dit problemen zou veroorzaken; Ik ken anderen waar dat niet zou gebeuren.

Het kan allemaal in een enkele SQL-instructie, maar u wilt wel een opgeslagen procedure om de parameters van de instructie te sorteren. Ik gebruik IBM Informix Dynamic Server (11.50.FC6 op MacOS X 10.6.2), en dat is een van de DBMS die de unieke beperking op de 'order'-kolom aan het einde van de verklaring afdwingt. Ik deed de ontwikkeling van de SQL zonder de UNIEKE beperking; dat werkte natuurlijk ook. (En ja, met IDS kun je DDL-statements zoals CREATE TABLE en CREATE PROCEDURE terugdraaien. Wat zei je? Je DBMS niet? Hoe vreemd!)

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;

De paren aanroepingen van de opgeslagen procedure met de nummers omgekeerd herstelden telkens de oorspronkelijke volgorde. Het is duidelijk dat ik de v_inc . opnieuw zou kunnen definiëren variabele zodat het in plaats van slechts ±1 was 'LET v_inc = v_inc - v_min + v_gap; ' en dan is de MOD-expressie gewoon 'MOD(order + v_inc, v_gap) '. Ik heb niet gecontroleerd of dit werkt met negatieve getallen.

Aanpassing aan MySQL of andere DBMS wordt overgelaten als een oefening voor de lezer.



  1. SQLite bestellen op

  2. Hoe maak ik een CSV-bestand uit de database in Python?

  3. 5 Tekenen dat je Excel bent ontgroeid

  4. Hoe de functie Onthoud mij te implementeren?