Ordinamento arbitrario di record in una tabella


28

Una necessità comune quando si utilizza un database è di accedere ai record in ordine. Ad esempio, se ho un blog, voglio essere in grado di riordinare i post del mio blog in ordine arbitrario. Queste voci hanno spesso molte relazioni, quindi un database relazionale sembra avere senso.

La soluzione comune che ho visto è quella di aggiungere una colonna intera order:

CREATE TABLE AS your_table (id, title, sort_order)
AS VALUES
  (0, 'Lorem ipsum',   3),
  (1, 'Dolor sit',     2),
  (2, 'Amet, consect', 0),
  (3, 'Elit fusce',    1);

Quindi, possiamo ordinare le righe orderper farle nell'ordine corretto.

Tuttavia, questo sembra goffo:

  • Se voglio spostare il record 0 all'inizio, devo riordinare ogni record
  • Se voglio inserire un nuovo record nel mezzo, devo riordinare ogni record dopo di esso
  • Se voglio rimuovere un record, devo riordinare ogni record dopo di esso

È facile immaginare situazioni come:

  • Due record hanno lo stesso order
  • Ci sono degli spazi vuoti ordertra i record

Questi potrebbero accadere abbastanza facilmente per una serie di motivi.

Questo è l'approccio adottato da applicazioni come Joomla:

Esempio dell'approccio di Joomla all'ordinazione

Potresti sostenere che qui l'interfaccia non funziona e che invece di modificare direttamente i numeri dagli umani, dovrebbero usare le frecce o trascinare e rilasciare, e probabilmente avresti ragione. Ma dietro le quinte, sta succedendo la stessa cosa.

Alcune persone hanno proposto di utilizzare un decimale per archiviare l'ordine, in modo che sia possibile utilizzare "2.5" per inserire un record tra i record nell'ordine 2 e 3. E mentre ciò aiuta un po ', è probabilmente anche più disordinato perché si può finire con strani decimali (dove ti fermi? 2,75? 2,875? 2,8125?)

Esiste un modo migliore per archiviare l'ordine in un tavolo?


5
Solo così lo sai. . . "La ragione per cui tali sistemi sono chiamati" relazionali "è che il termine relazione è fondamentalmente solo un termine matematico per una tabella ..." - Un'introduzione ai sistemi di database , CJ Date, 7 ° ed. p 25
Mike Sherrill 'Cat Recall'


@ MikeSherrill'CatRecall 'che non ho capito, ho risolto la domanda con il vecchio orderse il ddl.
Evan Carroll,

Risposte:


17

Se voglio spostare il record 0 all'inizio, devo riordinare ogni record

No, c'è un modo più semplice.

update your_table
set order = -1 
where id = 0;

Se voglio inserire un nuovo record nel mezzo, devo riordinare ogni record dopo di esso

È vero, a meno che non si utilizzi un tipo di dati che supporti i valori "tra". I tipi float e numerici consentono di aggiornare un valore, diciamo 2.5. Ma varchar (n) funziona anche. (Pensa 'a', 'b', 'c'; poi pensa 'ba', 'bb', 'bc'.)

Se voglio rimuovere un record, devo riordinare ogni record dopo di esso

No, c'è un modo più semplice. Elimina la riga. Le righe rimanenti verranno comunque ordinate correttamente.

È facile immaginare situazioni come:

Due record hanno lo stesso ordine

Un vincolo unico può impedirlo.

Vi sono lacune nell'ordine tra i record

Gli spazi vuoti non hanno alcun effetto sul modo in cui un dbms ordina i valori in una colonna.

Alcune persone hanno proposto di utilizzare un decimale per archiviare l'ordine, in modo che sia possibile utilizzare "2.5" per inserire un record tra i record nell'ordine 2 e 3. E mentre ciò aiuta un po ', è probabilmente anche più disordinato perché si può finire con strani decimali (dove ti fermi? 2,75? 2,875? 2,8125?)

Non si fermano fino a quando si ha a. Il dbms non ha problemi a ordinare valori che hanno 2, 7 o 15 posizioni dopo il punto decimale.

Penso che il tuo vero problema sia che ti piacerebbe vedere i valori in ordine ordinato come numeri interi. Ce la puoi fare.

create table your_table (
  id int primary key, 
  title varchar(13), 
  sort_order float
);

insert into your_table values
(0, 'Lorem ipsum', 2.0),
(1, 'Dolor sit', 1.5),
(2, 'Amet, consect', 0.0),
(3, 'Elit fusce', 1.0);

-- This windowing function will "transform" the floats into sorted integers.
select id, title,
       row_number() over (order by sort_order)
from your_table

Per motivi di pulizia, potresti finire il lavoro con qualcosa del generewith cte as (select *,row_number() over (order by sort_order desc) as row from test) update cte set sort_order=row;
Manngo,

Ecco un suggerimento aggiuntivo: se vuoi che sia davvero perfetto, dovresti controllare se stai spostando più righe e non intaccare. Se è così, allora aggiorna quelli meno numerosi - quelli "intatti"; D
Ruben Boeck,

7

È molto semplice. È necessario disporre di una struttura "foro di cardinalità":

Devi avere 2 colonne:

  1. pk = 32 bit integer
  2. ordine = 64 bit bigint( non double )

Inserimento / aggiornamento

  1. Quando si inserisce il primo nuovo record, impostare order = round(max_bigint / 2).
  2. Quando si inserisce all'inizio della tabella, impostare order = round("order of first record" / 2)
  3. Quando si inserisce alla fine del tavolo, impostare order = round("max_bigint - order of last record" / 2) 4) Quando si inserisce nel mezzo, impostareorder = round("order of record before - order of record after" / 2)

Questo metodo ha una cardinalità molto grande. Se si riscontra un errore di vincolo o se si pensa a una cardinalità ridotta, è possibile ricostruire la colonna dell'ordine (normalizzare).

Nella situazione massima con la normalizzazione (con questa struttura) è possibile avere un "buco di cardinalità" a 32 bit.

Ricorda di non usare tipi a virgola mobile: l'ordine deve avere un valore preciso!


4

Generalmente, l'ordinamento viene effettuato in base ad alcune informazioni nei registri, titolo, ID o qualsiasi cosa sia appropriata per quella particolare situazione.

Se hai bisogno di un ordine speciale, usare una colonna intera non è così male come potrebbe sembrare. Ad esempio, per fare spazio affinché un disco vada al 5 ° posto, potresti fare qualcosa del tipo:

update table_1 set place = place + 1 where place > 5.

Si spera che tu possa dichiarare che la colonna è uniquee forse avere una procedura per rendere "atomici" i riarrangiamenti. I dettagli dipendono dal sistema ma questa è l'idea generale.


4

... è probabilmente anche più disordinato perché puoi finire con strani decimali (dove ti fermi? 2,75? 2,875? 2,8125?)

Che importa? Questi numeri sono lì solo per il computer, quindi non importa quante cifre frazionarie hanno o quanto brutti ci appaiono.

L'uso di valori decimali significa che per spostare l'elemento F tra gli articoli J e K tutto ciò che devi fare è selezionare i valori dell'ordine per J e K, quindi fare una media di questi, quindi aggiornare F. Due istruzioni SELECT e un'istruzione UPDATE (probabilmente eseguite usando l'isolamento serializzabile per evitare deadlock).

Se si desidera visualizzare numeri interi anziché frazioni nell'output, calcolare i numeri interi nell'applicazione client o utilizzare le funzioni ROW_NUMBER () o RANK () (se RDBMS li include).


1

Nel mio progetto, sto pianificando di provare una soluzione simile alla soluzione dei numeri decimali, ma usando invece array di byte:

def pad(x, x_len, length):
    if x_len >= length:
        return x
    else:
        for _ in range(length - x_len):
            x += b"\x00"
        return x

def order_index(_from, _to, count, length=None):
    assert _from != _to
    assert _from < _to

    if not length:
        from_len = len(_from)
        to_len = len(_to)
        length = max(from_len, to_len)

        _from = pad(_from, from_len, length)
        _to = pad(_to, to_len, length)

    from_int = int.from_bytes(_from, "big")
    to_int = int.from_bytes(_to, "big")
    inc = (to_int - from_int)//(count + 1)
    if not inc:
        length += 1
        _from += b"\x00"
        _to += b"\x00"
        return order_index(_from, _to, count, length)

    return (int.to_bytes(from_int + ((x+1)*inc), length, "big") for x in range(count))
>>> index = order_index(b"A", b"Z", 24)
>>> [x for x in index]
[b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y']
>>> 
>>> index = order_index(b"A", b"Z", 25)
>>> [x for x in index]
[b'A\xf6', b'B\xec', b'C\xe2', b'D\xd8', b'E\xce', b'F\xc4', b'G\xba', b'H\xb0', b'I\xa6', b'J\x9c', b'K\x92', b'L\x88', b'M~', b'Nt', b'Oj', b'P`', b'QV', b'RL', b'SB', b'T8', b'U.', b'V$', b'W\x1a', b'X\x10', b'Y\x06']

L'idea è che non si può mai esaurire i possibili valori intermedi perché e si aggiunge semplicemente un b"\x00"record ai record coinvolti se sono necessari più valori. ( intè illimitato in Python 3, altrimenti dovresti scegliere una porzione di byte alla fine da confrontare, supponendo che, tra due valori adiacenti, le differenze sarebbero impacchettate verso la fine.)

Ad esempio, supponiamo che tu abbia due record b"\x00"e b"\x01"che tu voglia un record tra loro. Non ci sono valori disponibili tra 0x00e 0x01, quindi aggiungi b"\x00"a entrambi, e ora hai un sacco di valori tra loro che puoi usare per inserire nuovi valori.

>>> records = [b"\x00", b"\x01", b"\x02"]
>>> values = [x for x in order_index(records[0], records[1], 3)]
>>> records = records + values
>>> records.sort()
>>> records
[b'\x00', b'\x00@', b'\x00\x80', b'\x00\xc0', b'\x01', b'\x02']

Il database può facilmente ordinarlo perché tutto finisce in ordine lessicografico. Se elimini un record, è ancora in ordine. Nel mio progetto, ho creato b"\x00"e b"\xff"come FIRSTe LASTrecord, tuttavia, al fine di utilizzare quelli come valori "da" e "a" virtuali per anteporre / aggiungere nuovi record:

>>> records = []
>>> value = next(order_index(FIRST, LAST, 1))
>>> value
b'\x7f'
>>> records.append(value)
>>> value = next(order_index(records[0], LAST, 1))
>>> value
b'\xbf'
>>> records.append(value)
>>> records.sort()
>>> records
[b'\x7f', b'\xbf']
>>> value = next(order_index(FIRST, records[0], 1))
>>> value
b'?'
>>> records.append(value)
>>> records.sort()
>>> records
[b'?', b'\x7f', b'\xbf']

0

Ho trovato questa risposta molto meglio. Citandolo interamente:

I database sono ottimizzati per alcune cose. L'aggiornamento rapido di molte righe è uno di questi. Ciò diventa particolarmente vero quando si lascia che il database faccia il suo lavoro.

Ritenere:

order song
1     Happy Birthday
2     Beat It
3     Never Gonna Give You Up
4     Safety Dance
5     Imperial March

E vuoi passare Beat Italla fine, avresti due domande:

update table 
  set order = order - 1
  where order >= 2 and order <= 5;

update table
  set order = 5
  where song = 'Beat It'

E questo è tutto. Questo si espande molto bene con numeri molto grandi. Prova a mettere qualche migliaio di brani in un'ipotetica playlist nel tuo database e vedi quanto tempo impiega a spostare un brano da una posizione a un'altra. Poiché questi hanno forme molto standardizzate:

update table 
  set order = order - 1
  where order >= ? and order <= ?;

update table
  set order = ?
  where song = ?

Hai due dichiarazioni preparate che puoi riutilizzare in modo molto efficiente.

Ciò offre alcuni vantaggi significativi: l'ordine della tabella è qualcosa su cui puoi ragionare. La terza canzone ha un order3, sempre. L'unico modo per garantire ciò è utilizzare numeri interi consecutivi come ordine. L'uso di elenchi pseudo-collegati o numeri decimali o numeri interi con spazi vuoti non ti garantirà questa proprietà; in questi casi l'unico modo per ottenere l'ennesimo brano è ordinare l'intero tavolo e ottenere l'ennesimo disco.

E davvero, questo è molto più facile di quanto pensi. È semplice capire cosa vuoi fare, generare le due dichiarazioni di aggiornamento e per gli altri guardare quelle due dichiarazioni di aggiornamento e rendersi conto di ciò che viene fatto.

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.