Memorizzazione di un elenco riordinabile in un database


54

Sto lavorando a un sistema di lista dei desideri, in cui gli utenti possono aggiungere articoli alle loro varie liste dei desideri e intendo consentire agli utenti di riordinare gli articoli in un secondo momento. Non sono davvero sicuro del modo migliore per archiviarlo in un database rimanendo veloce e senza passare a un pasticcio (questa app verrà utilizzata da una base di utenti abbastanza grande, quindi non voglio che vada giù per pulire le cose).

Inizialmente ho provato una positioncolonna, ma sembra che sarebbe abbastanza inefficiente dover cambiare il valore di posizione di ogni altro oggetto quando li sposti.

Ho visto persone che usano un riferimento personale per riferirsi al valore precedente (o successivo), ma sembra che dovresti aggiornare un sacco di altri elementi nell'elenco.

Un'altra soluzione che ho visto è usare i numeri decimali e semplicemente attaccare gli elementi negli spazi tra loro, che sembra la soluzione migliore finora, ma sono sicuro che ci debba essere un modo migliore.

Direi che un elenco tipico conterrebbe fino a circa 20 articoli e probabilmente lo limiterò a 50. Il riordino utilizzerà il trascinamento della selezione e verrà probabilmente eseguito in batch per impedire le condizioni di gara e simili dal richieste Ajax. Sto usando Postgres (su Heroku) se è importante.

Qualcuno ha qualche idea?

Saluti per qualsiasi aiuto!


Puoi fare un po 'di benchmarking e dirci se IO o Database saranno un collo di bottiglia?
rwong,

Domanda correlata su stackoverflow .
Jordão,

1
Con l'auto-riferimento, quando si sposta un elemento da un punto dell'elenco all'altro, è necessario aggiornare solo 2 elementi. Vedi en.wikipedia.org/wiki/Linked_list
Pieter B,

Hmm, non sono sicuro del motivo per cui le liste collegate non ricevono quasi alcuna attenzione nelle risposte.
Christiaan Westerbeek,

Risposte:


32

Innanzitutto, non provare a fare nulla di intelligente con i numeri decimali, perché ti disprezzeranno. REALe DOUBLE PRECISIONsono inesatti e potrebbero non rappresentare correttamente ciò che ci metti. NUMERICè esatto, ma la giusta sequenza di mosse ti farà perdere precisione e la tua implementazione si interromperà male.

Limitare i passaggi ai singoli alti e bassi rende l'intera operazione molto semplice. Per un elenco di elementi numerati in sequenza, è possibile spostare un elemento verso l'alto diminuendo la sua posizione e incrementando il numero di posizione di qualunque cosa il decremento precedente abbia avuto origine. (In altre parole, l'oggetto 5diventerebbe 4e ciò che era l'oggetto 4diventa 5, in effetti uno scambio come descritto da Morons nella sua risposta.) Spostarlo verso il basso sarebbe l'opposto. Indicizza la tabella in base a ciò che identifica in modo univoco un elenco e una posizione e puoi farlo con due UPDATEsecondi all'interno di una transazione che verrà eseguita molto rapidamente. A meno che i tuoi utenti non stiano riorganizzando i loro elenchi a velocità sovrumane, questo non causerà molto carico.

Le mosse di trascinamento della selezione (ad esempio, spostare l'oggetto 6per sedersi tra gli oggetti 9e 10) sono un po 'più complicate e devono essere fatte in modo diverso a seconda che la nuova posizione sia sopra o sotto quella precedente. Nell'esempio sopra, devi aprire un buco aumentando tutte le posizioni maggiori di 9, aggiornando 6la posizione dell'oggetto in modo che sia la nuova 10e quindi decrementando la posizione di tutto ciò che è maggiore di 6riempire il punto vuoto. Con la stessa indicizzazione che ho descritto prima, questo sarà veloce. Puoi effettivamente farlo andare un po 'più veloce di quanto ho descritto minimizzando il numero di righe toccate dalla transazione, ma questa è una microottimizzazione che non ti serve finché non puoi provare che c'è un collo di bottiglia.

In ogni caso, provare a superare il database con una soluzione prodotta in casa, troppo intelligente dalla metà non porta di solito al successo. Banche dati degne di nota sono state accuratamente scritte per fare queste operazioni molto, molto rapidamente da persone molto, molto brave in questo.


Questo è esattamente il modo in cui l'ho gestito in un sistema di preparazione delle offerte di progetto che abbiamo avuto un milione di anni fa. Anche in Access, l'aggiornamento è stato diviso rapidamente.
HLGEM,

Grazie per la spiegazione, Blrfl! Ho tentato di fare quest'ultima opzione, ma ho scoperto che se avessi eliminato gli elementi dalla metà dell'elenco, avrebbe lasciato delle lacune nelle posizioni (era un'implementazione piuttosto ingenua). C'è un modo semplice per evitare di creare lacune come questa, o dovrei farlo manualmente ogni volta che ho riordinato qualcosa (se devo gestirlo davvero)?
Tom Brunoli,

3
@TomBrunoli: dovrei pensare un po 'sull'implementazione prima di dirlo con certezza, ma potresti essere in grado di realizzare la maggior parte o tutta la rinumerazione automagicamente con i trigger. Ad esempio, se si elimina l'elemento 7, il trigger decrementa tutte le righe dello stesso elenco con un numero maggiore di 7 dopo l'eliminazione. Gli inserti farebbero la stessa cosa (l'inserimento di un elemento 7 aumenterebbe tutte le righe 7 o successive). L'attivazione di un aggiornamento (ad esempio, spostare l'elemento 3 tra 9 e 10) sarebbe moderatamente più complessa, ma rientra certamente nell'ambito di fattibilità.
Blrfl,

1
In realtà non avevo mai esaminato i trigger prima, ma sembra un buon modo per farlo.
Tom Brunoli,

1
@TomBrunoli: mi viene in mente che l'uso dei trigger per fare ciò può causare cascate. Le procedure memorizzate con tutte le modifiche in una transazione potrebbero essere la strada migliore per questo.
Blrfl,

18

Stessa risposta da qui https://stackoverflow.com/a/49956113/10608


Soluzione: crea indexuna stringa (perché le stringhe, in sostanza, hanno una "precisione arbitraria" infinita). Oppure, se si utilizza un int, incrementare indexdi 100 anziché 1.

Il problema di prestazioni è questo: non esistono valori "in mezzo" tra due elementi ordinati.

item      index
-----------------
gizmo     1
              <<------ Oh no! no room between 1 and 2.
                       This requires incrementing _every_ item after it
gadget    2
gear      3
toolkit   4
box       5

Invece, fai così (soluzione migliore di seguito):

item      index
-----------------
gizmo     100
              <<------ Sweet :). I can re-order 99 (!) items here
                       without having to change anything else
gadget    200
gear      300
toolkit   400
box       500

Ancora meglio: ecco come Jira risolve questo problema. Il loro "rango" (quello che tu chiami indice) è un valore di stringa che consente una tonnellata di respiro tra gli oggetti classificati.

Ecco un vero esempio di un database jira con cui lavoro

   id    | jira_rank
---------+------------
 AP-2405 | 0|hzztxk:
 ES-213  | 0|hzztxs:
 AP-2660 | 0|hzztzc:
 AP-2688 | 0|hzztzk:
 AP-2643 | 0|hzztzs:
 AP-2208 | 0|hzztzw:
 AP-2700 | 0|hzztzy:
 AP-2702 | 0|hzztzz:
 AP-2411 | 0|hzztzz:i
 AP-2440 | 0|hzztzz:r

Notare questo esempio hzztzz:i. Il vantaggio di un rango di stringa è che si esaurisce lo spazio tra due elementi, è ancora non c'è bisogno di ri-rank qualsiasi altra cosa. Basta iniziare ad aggiungere più caratteri alla stringa per restringere il focus.


1
Stavo cercando di trovare un modo per farlo aggiornando solo un singolo record, e questa risposta spiega molto bene la soluzione che stavo pensando nella mia testa.
NSjonas,

13

Ho visto persone che usano un riferimento personale per riferirsi al valore precedente (o successivo), ma sembra che dovresti aggiornare un sacco di altri elementi nell'elenco.

Perché? Supponi di adottare un approccio di tabella con elenchi collegati con colonne (listID, itemID, nextItemID).

L'inserimento di un nuovo articolo in un elenco costa un inserimento e una riga modificata.

Il riposizionamento di un articolo costa tre modifiche di riga (l'oggetto che viene spostato, l'oggetto precedente e l'articolo prima della nuova posizione).

La rimozione di un articolo costa una riga eliminata e una riga modificata.

Questi costi rimangono gli stessi indipendentemente dal fatto che l'elenco contenga 10 articoli o 10.000 articoli. In tutti e tre i casi c'è una modifica in meno se la riga di destinazione è la prima voce dell'elenco. Se stai operando più spesso sull'ultimo elemento dell'elenco, potrebbe essere utile memorizzare prevItemID anziché il successivo.


10

"ma sembra che sarebbe abbastanza inefficiente"

L'hai misurato ? O è solo una supposizione? Non fare simili assunzioni senza alcuna prova.

"Da 20 a 50 articoli per elenco"

Onestamente, questo non è "un sacco di oggetti", per me sembra solo molto poco.

Ti suggerisco di seguire l'approccio "position position" (se questa è l'implementazione più semplice per te). Per elenchi di dimensioni così ridotte, non iniziare l'ottimizzazione non necessaria prima di riscontrare problemi di prestazioni reali


6

Questa è davvero una questione di scala e caso d'uso ..

Quanti articoli ti aspetti in un elenco? Se milioni, penso che la strada decimale sia quella ovvia.

Se 6 allora la rinumerazione di numeri interi è la scelta ovvia. s Anche la domanda è come gli elenchi o riorganizzati. Se si utilizzano le frecce su e giù (spostandosi verso l'alto o verso il basso di uno slot alla volta), l'i userebbe numeri interi e quindi cambierebbe con il precedente (o successivo) in movimento.

Inoltre, quanto spesso ti impegni, se l'utente può apportare 250 modifiche, esegui il commit in una sola volta, quindi dico numeri interi con rinumerazione di nuovo ...

tl; dr: hai bisogno di maggiori informazioni.


Modifica: "Liste dei desideri" suona come un sacco di piccole liste (ipotesi, questo può essere falso) .. Quindi dico Intero con rinumerazione. (Ogni elenco contiene la propria posizione)


Aggiornerò la domanda con qualche altro contesto
Tom Brunoli,

i decimali non funzionano, poiché la precisione è limitata e ogni elemento inserito richiede potenzialmente 1 bit
njzk2

3

Se l'obiettivo è ridurre al minimo il numero di operazioni del database per operazione di riordino:

Supponendo che

  • Tutti gli articoli della spesa possono essere elencati con numeri interi a 32 bit.
  • Esiste un limite di dimensione massima per la lista dei desideri di un utente. (Ho visto alcuni siti Web popolari utilizzare 20 - 40 articoli come limite)

Memorizza la lista dei desideri ordinata dell'utente come una sequenza impacchettata di numeri interi (array di numeri interi) in una colonna. Ogni volta che la lista dei desideri viene riordinata, l'intero array (riga singola; colonna singola) viene aggiornato, che deve essere eseguito con un singolo aggiornamento SQL.

https://www.postgresql.org/docs/current/static/arrays.html


Se l'obiettivo è diverso, attenersi all'approccio "Posiziona colonna".


Per quanto riguarda la "velocità", assicurarsi di confrontare l'approccio della procedura memorizzata. Mentre l'emissione di oltre 20 aggiornamenti separati per un riordino della lista dei desideri potrebbe essere lenta, potrebbe esserci un modo rapido utilizzando la procedura memorizzata.


3

OK, ho affrontato questo problematico problema di recente e tutte le risposte in questo post di domande e risposte hanno dato molta ispirazione. Per come la vedo io, ogni soluzione ha i suoi pro e contro.

  • Se il positioncampo deve essere sequenziale senza spazi vuoti, dovrai sostanzialmente riordinare l'intero elenco. Questa è un'operazione O (N). Il vantaggio è che il lato client non avrebbe bisogno di alcuna logica speciale per ottenere l'ordine.

  • Se vogliamo evitare l'operazione O (N) MA ANCORA mantenere una sequenza precisa, uno degli approcci consiste nell'utilizzare "auto-riferimento per fare riferimento al valore precedente (o successivo)". Questo è uno scenario dell'elenco collegato al libro di testo. In base alla progettazione, NON comporterà "molti altri elementi nell'elenco". Tuttavia, ciò richiede al lato client (un servizio Web o forse un'app mobile) di implementare la logica di attraversamento dell'elenco collegato per ricavare l'ordine.

  • Alcune varianti non utilizzano riferimenti, ad esempio l'elenco collegato. Scelgono di rappresentare l'intero ordine come un BLOB autonomo, ad esempio un array JSON in una stringa [5,2,1,3,...]; tale ordine verrà quindi archiviato in un luogo separato. Questo approccio ha anche l'effetto collaterale di richiedere al codice lato client di mantenere quel BLOB di ordini separato.

  • In molti casi, non abbiamo davvero bisogno di memorizzare l'ordine esatto, dobbiamo solo mantenere un rango relativo tra ogni record. Pertanto, possiamo consentire spazi vuoti tra i record sequenziali. Le variazioni includono: (1) l'utilizzo di numeri interi con spazi vuoti come 100, 200, 300 ... ma si esauriranno rapidamente gli spazi vuoti e quindi sarà necessario il processo di recupero; (2) usando il decimale che viene fornito con lacune naturali, ma dovrai decidere se puoi vivere con l'eventuale limitazione della precisione; (3) usando il rango basato su stringhe come descritto in questa risposta, ma fai attenzione alle trappole dell'implementazione .

  • La vera risposta può essere "dipende". Rivedi i tuoi requisiti aziendali. Ad esempio, se si tratta di un sistema di lista dei desideri, personalmente utilizzerei felicemente un sistema organizzato in pochi gradi come "must-have", "good-to-have", "forse-later", e quindi presentare elementi senza particolari ordine all'interno di ogni rango. Se si tratta di un sistema di consegna, puoi benissimo usare i tempi di consegna come un rango approssimativo che viene fornito con gap naturali (e prevenzione dei conflitti naturali poiché nessuna consegna avverrebbe allo stesso tempo). Il tuo chilometraggio può variare.


2

Utilizzare un numero in virgola mobile per la colonna della posizione.

È quindi possibile riordinare l'elenco modificando solo la colonna della posizione nella riga "spostata".

Fondamentalmente se l'utente desidera posizionare "rosso" dopo "blu" ma prima di "giallo"

Quindi devi solo calcolare

red.position = ((yellow.position - blue.position) / 2) + blue.position

Dopo alcuni milioni di riposizionamenti, potresti ottenere numeri in virgola mobile così piccoli che non c'è "tra" - ma questo è circa probabile come avvistare un unicorno.

Potresti implementarlo usando un campo intero con uno spazio iniziale di dire 1000. Quindi il tuo oringring iniziale sarebbe 1000-> blu, 2000-> Giallo, 3000-> Rosso. Dopo aver "spostato" il rosso dopo il blu, avrai 1000-> blu, 1500-> rosso, 2000-> giallo.

Il problema è che con un gap iniziale apparentemente ampio di 1000, solo 10 mosse ti porteranno in una situazione come 1000-> blu, 1001-puce, 1004-> biege ...... dove non sarai più in grado per inserire qualcosa dopo "blu" senza ricomporre l'intero elenco. Usando i numeri in virgola mobile ci sarà sempre un punto "a metà" tra le due posizioni.


4
L'indicizzazione e l'ordinamento in una base di dati basata su float è più costoso degli ints. Gli ints sono anche un bel tipo ordinale ... non è necessario che vengano inviati come bit per poter essere ordinati sul client (la differenza tra due numeri che rendono lo stesso quando vengono stampati, ma hanno valori di bit diversi).

Ma qualsiasi schema che utilizza ints significa che è necessario aggiornare tutte / la maggior parte delle righe nell'elenco ogni volta che l'ordine cambia. Usando i float aggiorni solo la riga spostata. Anche "galleggia più costoso degli ints" dipende molto dall'implementazione e dall'hardware utilizzato. Certamente la cpu extra in questione è insignificante rispetto alla cpu richiesta per aggiornare una riga e i relativi indici associati.
James Anderson,

5
Per gli oppositori, questa soluzione è esattamente ciò che fa Trello ( trello.com ). Apri il tuo debugger di Chrome e diff l'output json da prima / dopo un riordino (trascina / rilascia una carta) e ottieni - "pos": 1310719, + "pos": 638975.5. Per essere onesti, la maggior parte delle persone non fa elenchi di trello con 4 milioni di voci al loro interno, ma le dimensioni dell'elenco e il caso d'uso di Trello sono piuttosto comuni per i contenuti ordinabili dall'utente. E qualsiasi cosa ordinabile dall'utente non ha praticamente nulla a che fare con le alte prestazioni, la velocità di ordinamento int vs float è discutibile per questo, soprattutto considerando che i database sono per lo più vincolati dalle prestazioni di I / O.
zelk,

1
@PieterB Quanto a "perché non usare un numero intero a 64 bit", direi soprattutto ergonomia per lo sviluppatore. C'è una profondità di bit circa <1,0 rispetto a> 1,0 per il float medio, quindi puoi impostare la colonna 'position' su 1,0 e inserire 0,5, 0,25, 0,75 con la stessa facilità con il raddoppio. Con i numeri interi, il valore predefinito dovrebbe essere 2 ^ 30 o giù di lì, rende un po 'difficile pensare a quando si esegue il debug. 4073741824 è più grande di 496359787? Inizia a contare le cifre.
zelk,

1
Inoltre, se hai mai incontrato un caso in cui rimani senza spazio tra i numeri ... non è così difficile da risolvere. Sposta uno di loro. Ma la cosa importante è che funziona nel modo migliore, che gestisce molte modifiche simultanee di parti diverse (ad esempio trello). Puoi dividere due numeri, forse anche cospargere un po 'di rumore casuale, e voilà, anche se qualcun altro ha fatto la stessa cosa allo stesso tempo, c'è ancora un ordine globale e non è necessario INSERIRE all'interno di una transazione per ottenere Là.
zelk,

1

Mentre l'OP ha brevemente toccato l'idea di utilizzare un Elenco collegato per memorizzare l'ordinamento, presenta molti vantaggi per i casi in cui gli articoli verranno riordinati frequentemente.

Ho visto persone che usano un riferimento personale per riferirsi al valore precedente (o successivo), ma sembra che dovresti aggiornare un sacco di altri elementi nell'elenco.

Il fatto è che non lo fai ! Quando si utilizza un elenco collegato, l'inserimento, l'eliminazione e il riordino sono O(1)operazioni e l'integrità referenziale imposta dal database assicura che non vi siano riferimenti interrotti, record orfani o loop.

Ecco un esempio:

CREATE TABLE Wishlists (
  WishlistId int           NOT NULL IDENTITY(1,1) PRIMARY KEY,
  [Name]     nvarchar(200) NOT NULL
);

CREATE TABLE WishlistItems (
  ItemId     int           NOT NULL IDENTITY(1,1),
  WishlistId int           NOT NULL,
  Text       nvarchar(200) NOT NULL,
  SortAfter  int               NULL,

  CONSTRAINT PK_WishlistItem PRIMARY KEY ( ItemId, WishlistId ),
  CONSTRAINT FK_Wishlist_WishlistItem FOREIGN KEY ( WishlistId ) REFERENCES Wishlists ( WishlistId ),
  CONSTRAINT FK_Sorting FOREIGN KEY ( SortAfter, WishlistId ) REFERENCES WishlistItems ( ItemId, WishlistId )
);

CREATE UNIQUE INDEX UX_Sorting ON WishlistItems ( SortAfter, WishlistId );

 -----

SET IDENTITY_INSERT Wishlists ON;

INSERT INTO Wishlists ( WishlistId, [Name] ) VALUES
  ( 1, 'Wishlist 1' ),
  ( 2, 'Wishlist 2' );

SET IDENTITY_INSERT Wishlists OFF;

SET IDENTITY_INSERT WishlistItems ON;

INSERT INTO WishlistItems ( ItemId, WishlistId, [Text], SortAfter ) VALUES
( 1, 1, 'One', NULL ),
( 2, 1, 'Two', 1 ),
( 3, 1, 'Three', 2 ),
( 4, 1, 'Four', 3 ),
( 5, 1, 'Five', 4 ),
( 6, 1, 'Six', 5 ),
( 7, 1, 'Seven', 6 ),
( 8, 1, 'Eight', 7 );

SET IDENTITY_INSERT WishlistItems OFF;

Nota quanto segue:

  • Utilizzo di una chiave primaria composita e una chiave esterna FK_Sortingper impedire che gli articoli si riferiscano accidentalmente all'elemento padre errato.
  • Il UNIQUE INDEX UX_Sortingesegue due funzioni:
    • Poiché consente un singolo NULLvalore, ogni elenco può contenere solo 1 elemento "head".
    • Impedisce a due o più articoli di dichiarare di trovarsi nello stesso posto di ordinamento (impedendo SortAftervalori duplicati ).

I principali vantaggi di questo approccio:

  • Non richiede mai il ribilanciamento o la manutenzione, come nel caso degli ordini di ordinamento basati su into realche alla fine esauriscono lo spazio tra gli oggetti dopo frequenti riordini.
  • Solo gli articoli riordinati (e i loro fratelli) devono essere aggiornati.

Questo approccio presenta degli svantaggi, tuttavia:

  • È possibile ordinare questo elenco solo in SQL utilizzando un CTE ricorsivo perché non è possibile eseguire un'operazione semplice ORDER BY.
    • Per ovviare al problema, è possibile creare un wrapper VIEWo TVF che utilizza un CTE per aggiungere un derivato contenente un ordinamento incrementale, ma questo sarebbe costoso da utilizzare in operazioni di grandi dimensioni.
  • È necessario caricare l'intero elenco nel programma per visualizzarlo: non è possibile operare su un sottoinsieme delle righe perché la SortAftercolonna farà riferimento agli elementi che non sono stati caricati nel programma.
    • Tuttavia, caricare tutti gli elementi per un elenco è facile grazie alla chiave primaria composita (ovvero basta SELECT * FROM WishlistItems WHERE WishlistId = @wishlistToLoad).
  • L'esecuzione di qualsiasi operazione mentre UX_Sortingè abilitata richiede il supporto DBMS per i vincoli differiti.
    • ovvero l'implementazione ideale di questo approccio non funzionerà in SQL Server fino a quando non aggiungeranno il supporto per vincoli e indici differibili.
    • Una soluzione alternativa consiste nel rendere l'Indice univoco un indice filtrato che consenta più NULLvalori nella colonna, il che sfortunatamente significa che un elenco può contenere più elementi HEAD.
      • Una soluzione alternativa per questa soluzione alternativa consiste nell'aggiungere una terza colonna Stateche è un semplice flag per dichiarare se un elemento dell'elenco è "attivo" o meno - e l'indice univoco ignora gli elementi inattivi.
    • Questo è qualcosa che SQL Server ha usato per supportare negli anni '90 e quindi ha rimosso inspiegabilmente il supporto per esso.

Soluzione 1: è necessaria la capacità di eseguire un'operazione banale ORDER BY.

Ecco una VISTA che utilizza un CTE ricorsivo che aggiunge una SortOrdercolonna:

CREATE VIEW OrderableWishlistItems AS 

    WITH c ( ItemId, WishlistId, [Text], SortAfter, SortOrder )
    AS
    (
        SELECT
              ItemId, WishlistId, [Text], SortAfter, 1 AS SortOrder
        FROM
              WishlistItems
        WHERE
              SortAfter IS NULL

        UNION ALL

        SELECT
              i.ItemId, i.WishlistId, i.[Text], i.SortAfter, c.SortOrder + 1
        FROM
              WishlistItems AS i
              INNER JOIN c ON
                  i.WishlistId = c.WishlistId
                  AND
                  i.SortAfter = c.ItemId
    )
    SELECT
        ItemId, WishlistId, [Text], SortAfter, SortOrder
    FROM
        c;

È possibile utilizzare questa VISTA in altre query in cui è necessario ordinare i valori utilizzando ORDER BY:

Query:

    SELECT * FROM OrderableWishlistItems

Results:

    ItemId  WishlistId  Text        SortAfter   SortOrder
    1       1           One         (null)      1
    2       1           Two             1       2
    3       1           Three           2       3
    4       1           Four            3       4
    5       1           Five            4       5
    6       1           Six             5       6
    7       1           Seven           6       7
    8       1           Eight           7       8

Soluzione 2: prevenzione dei UNIQUE INDEXvincoli di violazione durante l'esecuzione delle operazioni:

Aggiungi una Statecolonna alla WishlistItemstabella. La colonna è contrassegnata in HIDDENmodo tale che la maggior parte degli strumenti ORM (come Entity Framework) non la includerà durante la generazione di modelli, ad esempio.

CREATE TABLE WishlistItems (
  ItemId     int           NOT NULL IDENTITY(1,1),
  WishlistId int           NOT NULL,
  Text       nvarchar(200) NOT NULL,
  SortAfter  int               NULL,
  [State]    bit           NOT NULL HIDDEN,

  CONSTRAINT PK_WishlistItem PRIMARY KEY ( ItemId, WishlistId ),
  CONSTRAINT FK_Wishlist_WishlistItem FOREIGN KEY ( WishlistId ) REFERENCES Wishlists ( WishlistId ),
  CONSTRAINT FK_Sorting FOREIGN KEY ( SortAfter, WishlistId ) REFERENCES WishlistItems ( ItemId, WishlistId )
);

CREATE UNIQUE INDEX UX_Sorting ON WishlistItems ( SortAfter, WishlistId ) WHERE [State] = 1;

operazioni:

Aggiunta di un nuovo elemento alla fine dell'elenco:

  1. Caricare prima l'elenco per determinare l' ItemIdultimo dell'ultimo elemento nell'elenco e archiviarlo in @tailItemId- o utilizzare SELECT MAX( SortOrder ) FROM OrderableWishlistItems WHERE WishlistId = @listId.
  2. INSERT INTO WishlistItems ( WishlistId, [Text], SortAfter ) VALUES ( @listId, @text, @tailItemId ).

Riordinare l'articolo 4 in modo che sia inferiore all'articolo 7

BEGIN TRANSACTION

    DECLARE @itemIdToMove int = 4
    DECLARE @itemIdToMoveAfter int = 7

    DECLARE @prev int = ( SELECT SortAfter FROM WishlistItems WHERE ItemId = @itemIdToMove )

    UPDATE WishlistItems SET [State] = 0 WHERE ItemId IN ( @itemIdToMove , @itemIdToMoveAfter )

    UPDATE WishlistItems SET [SortAfter] = @itemIdToMove WHERE ItemId = @itemIdToMoveAfter 

    UPDATE WishlistItems SET [SortAfter] = @prev WHERE SortAfter = @itemIdToMove 

    UPDATE WishlistItems SET [State] = 1 WHERE ItemId IN ( @itemIdToMove, @itemIdToMoveAfter )

COMMIT;

Rimozione dell'elemento 4 dal centro dell'elenco:

Se un elemento si trova alla fine della lista (cioè dove NOT EXISTS ( SELECT 1 FROM WishlistItems WHERE SortAfter = @itemId )), allora puoi fare un singolo DELETE.

Se un articolo ha un oggetto ordinato dopo di esso, esegui gli stessi passaggi del riordino di un oggetto, tranne te DELETEin seguito invece di impostare State = 1;.

BEGIN TRANSACTION

    DECLARE @itemIdToRemove int = 4

    DECLARE @prev int = ( SELECT SortAfter FROM WishlistItems WHERE ItemId = @itemIdToRemove )

    UPDATE WishlistItems SET [State] = 0 WHERE ItemId = @itemIdToRemove

    UPDATE WishlistItems SET [SortAfter] = @prev WHERE SortAfter = @itemIdToRemove

    DELETE FROM WishlistItems WHERE ItemId = @itemIdToRemove

COMMIT;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.