it-swarm.com.de

Beliebige Reihenfolge von Datensätzen in einer Tabelle

Bei der Verwendung einer Datenbank muss häufig auf Datensätze in der angegebenen Reihenfolge zugegriffen werden. Wenn ich beispielsweise ein Blog habe, möchte ich meine Blog-Beiträge in beliebiger Reihenfolge neu anordnen können. Diese Einträge haben oft viele Beziehungen, daher scheint eine relationale Datenbank sinnvoll zu sein.

Die übliche Lösung, die ich gesehen habe, besteht darin, eine Ganzzahlspalte order hinzuzufügen:

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);

Dann können wir die Zeilen nach order sortieren, um sie in der richtigen Reihenfolge zu erhalten.

Dies scheint jedoch ungeschickt:

  • Wenn ich Datensatz 0 an den Start verschieben möchte, muss ich jeden Datensatz neu anordnen
  • Wenn ich einen neuen Datensatz in die Mitte einfügen möchte, muss ich jeden Datensatz danach neu anordnen
  • Wenn ich einen Datensatz entfernen möchte, muss ich jeden Datensatz danach neu anordnen

Es ist leicht, sich Situationen vorzustellen wie:

  • Zwei Datensätze haben das gleiche order
  • Es gibt Lücken im order zwischen Datensätzen

Dies kann aus mehreren Gründen recht einfach passieren.

Dies ist der Ansatz, den Anwendungen wie Joomla verfolgen:

Example of Joomla's approach to ordering

Sie könnten argumentieren, dass die Benutzeroberfläche hier schlecht ist und dass Menschen, die Zahlen direkt bearbeiten, Pfeile oder Drag-and-Drop verwenden sollten - und Sie hätten wahrscheinlich Recht. Aber hinter den Kulissen passiert dasselbe.

Einige Leute haben vorgeschlagen, eine Dezimalstelle zum Speichern der Reihenfolge zu verwenden, damit Sie mit "2.5" einen Datensatz zwischen den Datensätzen in der Reihenfolge 2 und 3 einfügen können seltsame Dezimalstellen (wo hörst du auf? 2,75? 2,875? 2,8125?)

Gibt es eine bessere Möglichkeit, Bestellungen in einer Tabelle zu speichern?

30
Tom Marthenal

Wenn ich Datensatz 0 an den Start verschieben möchte, muss ich jeden Datensatz neu anordnen

Nein, es gibt einen einfacheren Weg.

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

Wenn ich einen neuen Datensatz in die Mitte einfügen möchte, muss ich jeden Datensatz danach neu anordnen

Das stimmt, es sei denn, Sie verwenden einen Datentyp, der "zwischen" Werten unterstützt. Mit Float- und numerischen Typen können Sie einen Wert auf beispielsweise 2,5 aktualisieren. Aber varchar (n) funktioniert auch. (Denken Sie an 'a', 'b', 'c'; dann denken Sie an 'ba', 'bb', 'bc'.)

Wenn ich einen Datensatz entfernen möchte, muss ich jeden Datensatz danach neu anordnen

Nein, es gibt einen einfacheren Weg. Löschen Sie einfach die Zeile. Die verbleibenden Zeilen werden weiterhin korrekt sortiert.

Es ist leicht, sich Situationen vorzustellen wie:

Zwei Datensätze haben dieselbe Reihenfolge

Eine eindeutige Einschränkung kann dies verhindern.

Es gibt Lücken in der Reihenfolge zwischen den Datensätzen

Lücken haben keinen Einfluss darauf, wie eine Datenbank Werte in einer Spalte sortiert.

Einige Leute haben vorgeschlagen, eine Dezimalstelle zum Speichern der Reihenfolge zu verwenden, damit Sie mit "2.5" einen Datensatz zwischen den Datensätzen in der Reihenfolge 2 und 3 einfügen können seltsame Dezimalstellen (wo hörst du auf? 2,75? 2,875? 2,8125?)

Sie hören nicht auf, bis Sie haben zu. Die Datenbank hat no Probleme beim Sortieren von Werten, die 2, 7 oder 15 Stellen nach dem Dezimalpunkt haben.

Ich denke, Ihr real Problem ist, dass Sie siehe Werte in sortierter Reihenfolge als Ganzzahlen möchten. Du kannst das machen.

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

Es ist sehr einfach. Sie benötigen eine "Kardinalitätsloch" -Struktur:

Sie müssen 2 Spalten haben:

  1. pk = 32bit integer
  2. order = 64bit bigint (nichtdouble)

Einfügen/aktualisieren

  1. Stellen Sie beim Einfügen des ersten neuen Datensatzes order = round(max_bigint / 2) ein.
  2. Setzen Sie beim Einfügen am Anfang der Tabelle order = round("order of first record" / 2)
  3. Wenn Sie am Ende der Tabelle einfügen, setzen Sie order = round("max_bigint - order of last record" / 2) 4) Wenn Sie in die Mitte einfügen, setzen Sie order = round("order of record before - order of record after" / 2)

Diese Methode hat eine sehr große Kardinalität. Wenn Sie einen Einschränkungsfehler haben oder wenn Sie denken, dass Sie eine kleine Kardinalität haben, können Sie die Auftragsspalte neu erstellen (normalisieren).

In maximaler Situation mit Normalisierung (mit dieser Struktur) können Sie "Kardinalitätsloch" in 32 Bit haben.

Denken Sie daran, keine Gleitkommatypen zu verwenden - die Reihenfolge muss ein genauer Wert sein!

8
user2382679

Im Allgemeinen erfolgt die Bestellung gemäß einigen Informationen in den Datensätzen, dem Titel, der ID oder was auch immer für diese spezielle Situation angemessen ist.

Wenn Sie eine spezielle Reihenfolge benötigen, ist die Verwendung einer Ganzzahlspalte nicht so schlecht, wie es scheint. Um beispielsweise Platz für eine Platte zu schaffen, die den 5. Platz belegt, können Sie Folgendes tun:

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

Hoffentlich können Sie die Spalte als unique deklarieren und möglicherweise eine Prozedur haben, um Umlagerungen "atomar" zu machen. Die Details hängen vom System ab, aber das ist die allgemeine Idee.

4
igelkott

… Es ist wohl noch chaotischer, weil Sie seltsame Dezimalstellen haben können (wo hören Sie auf? 2,75? 2,875? 2,8125?)

Wen interessiert das? Diese Zahlen sind nur für den Computer verfügbar, sodass es keine Rolle spielt, wie viele Bruchstellen sie haben oder wie hässlich sie uns erscheinen.

Die Verwendung von Dezimalwerten bedeutet, dass Sie zum Verschieben von Element F zwischen den Elementen J und K lediglich die Ordnungswerte für J und K auswählen und dann mitteln und dann F aktualisieren müssen. Zwei SELECT-Anweisungen und eine UPDATE-Anweisung (wahrscheinlich mit serialisierbarer Isolierung, um dies zu vermeiden Deadlocks).

Wenn Sie Ganzzahlen anstelle von Brüchen in der Ausgabe sehen möchten, berechnen Sie entweder Ganzzahlen in der Clientanwendung oder verwenden Sie die Funktionen ROW_NUMBER () oder RANK () (sofern Ihr RDBMS diese enthält).

4

In meinem eigenen Projekt plane ich, eine Lösung zu versuchen, die der Dezimalzahllösung ähnelt, aber stattdessen Byte-Arrays verwendet:

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']

Die Idee ist, dass Ihnen niemals die möglichen Zwischenwerte ausgehen können, weil Sie einfach einen b"\x00" An die beteiligten Datensätze anhängen, wenn Sie mehr Werte benötigen. (int ist in Python 3 unbegrenzt), andernfalls müssten Sie am Ende einen Teil der Bytes zum Vergleichen auswählen, wobei angenommen wird, dass zwischen zwei benachbarten Werten Die Unterschiede würden gegen Ende gepackt sein.)

Angenommen, Sie haben zwei Datensätze, b"\x00" Und b"\x01", Und Sie möchten, dass ein Datensatz zwischen ihnen liegt. Es gibt keine verfügbaren Werte zwischen 0x00 Und 0x01, Daher hängen Sie b"\x00" An beide an, und jetzt haben Sie eine Reihe von Werten zwischen ihnen, mit denen Sie neue einfügen können Werte.

>>> 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'\[email protected]', b'\x00\x80', b'\x00\xc0', b'\x01', b'\x02']

Die Datenbank kann es leicht sortieren, da alles in lexikografischer Reihenfolge endet. Wenn Sie einen Datensatz löschen, ist er noch in Ordnung. In meinem Projekt habe ich b"\x00" Und b"\xff" Als FIRST und LAST Datensätze erstellt, um diese als virtuelle "von" und zu verwenden "to" -Werte zum Voranstellen/Anhängen neuer Datensätze:

>>> 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']
1
tjb1982

Ich fand diese Antwort viel besser. Ganz zitiert:

Datenbanken sind für bestimmte Dinge optimiert. Das schnelle Aktualisieren vieler Zeilen ist eine davon. Dies gilt insbesondere dann, wenn Sie die Datenbank ihre Arbeit machen lassen.

Erwägen:

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

Und du willst dich bewegen Beat It bis zum Ende hätten Sie zwei Fragen:

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

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

Und das ist es. Dies lässt sich bei sehr großen Zahlen sehr gut skalieren. Versuchen Sie, ein paar tausend Songs in eine hypothetische Wiedergabeliste in Ihrer Datenbank aufzunehmen, und sehen Sie, wie lange es dauert, einen Song von einem Ort an einen anderen zu verschieben. Da diese sehr standardisierte Formen haben:

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

update table
  set order = ?
  where song = ?

Sie haben zwei vorbereitete Anweisungen, die Sie sehr effizient wiederverwenden können.

Dies bietet einige wesentliche Vorteile - die Reihenfolge der Tabelle ist etwas, über das Sie nachdenken können. Das dritte Lied hat immer ein order von 3. Die einzige Möglichkeit, dies zu gewährleisten, besteht darin, aufeinanderfolgende Ganzzahlen als Reihenfolge zu verwenden. Wenn Sie pseudo-verknüpfte Listen oder Dezimalzahlen oder Ganzzahlen mit Lücken verwenden, können Sie diese Eigenschaft nicht garantieren. In diesen Fällen besteht die einzige Möglichkeit, den n-ten Song zu erhalten, darin, die gesamte Tabelle zu sortieren und den n-ten Datensatz zu erhalten.

Und das ist wirklich viel einfacher als Sie denken. Es ist einfach herauszufinden, was Sie tun möchten, die beiden Aktualisierungsanweisungen zu generieren und für andere Personen diese beiden Aktualisierungsanweisungen zu betrachten und zu erkennen, was getan wird.

1
vedant