Beliebige Reihenfolge der Datensätze in einer Tabelle

28

Wenn Sie eine Datenbank verwenden, müssen Sie häufig in der richtigen Reihenfolge auf Datensätze zugreifen. Wenn ich beispielsweise ein Blog habe, möchte ich meine Blog-Posts in beliebiger Reihenfolge neu anordnen können. Diese Einträge haben oft viele Beziehungen, daher scheint eine relationale Datenbank sinnvoll zu sein.

Die gebräuchliche Lösung, die ich gesehen habe, ist das Hinzufügen einer Ganzzahlspalte 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);

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

Dies scheint jedoch ungeschickt:

  • Wenn ich Datensatz 0 an den Anfang verschieben möchte, muss ich jeden Datensatz neu anordnen
  • Wenn ich einen neuen Datensatz in der 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 wie die folgenden vorzustellen:

  • Zwei Datensätze haben das gleiche order
  • Es gibt Lücken orderzwischen den Datensätzen

Diese können aus einer Reihe von Gründen relativ leicht vorkommen.

Dies ist der Ansatz, den Anwendungen wie Joomla verfolgen:

Beispiel für den Bestellungsansatz von Joomla

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

Einige Leute haben vorgeschlagen, eine Dezimalzahl zum Speichern der Reihenfolge zu verwenden, sodass Sie "2.5" verwenden können, um einen Datensatz zwischen den Datensätzen in Reihenfolge 2 und 3 einzufügen seltsame Dezimalstellen (wo hören Sie auf? 2,75? 2,875? 2,8125?)

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

Tom Marthenal
quelle
5
Nur damit du es weißt. . . „Der Grund , solche Systeme werden als‚relational‘ist , dass der Begriff Beziehung ist im Grunde nur ein mathematischer Begriff für eine Tabelle ...“ - Eine Einführung in Datenbanksysteme , CJ Date, 7. Aufl. S. 25
Mike Sherrill 'Cat Recall'
@ MikeSherrill "CatRecall", den ich nicht mitbekommen habe, habe ich die Frage mit dem alten ordersund dem ddl behoben .
Evan Carroll

Antworten:

17

Wenn ich Datensatz 0 an den Anfang 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 der Mitte einfügen möchte, muss ich jeden Datensatz danach neu anordnen

Das ist wahr, es sei denn, Sie verwenden einen Datentyp, der "zwischen" Werten unterstützt. Mit Float- und numerischen Typen können Sie einen Wert beispielsweise auf 2,5 aktualisieren. Aber auch varchar (n) funktioniert. (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 wie die folgenden vorzustellen:

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 ein DBMS Werte in einer Spalte sortiert.

Einige Leute haben vorgeschlagen, eine Dezimalzahl zum Speichern der Reihenfolge zu verwenden, sodass Sie "2.5" verwenden können, um einen Datensatz zwischen den Datensätzen in Reihenfolge 2 und 3 einzufügen seltsame Dezimalstellen (wo hören Sie auf? 2,75? 2,875? 2,8125?)

Sie hören nicht auf, bis Sie müssen . Das DBMS hat kein Problem damit, Werte mit 2, 7 oder 15 Nachkommastellen zu sortieren.

Ich denke , Ihr wirkliches Problem ist , dass Sie mögen , dass sehen Werte in sortierter Reihenfolge als ganze Zahlen. Das kannst du 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
Mike Sherrill 'Cat Recall'
quelle
Der with cte as (select *,row_number() over (order by sort_order desc) as row from test) update cte set sort_order=row;
Ordentlichkeit
Hier ist ein zusätzlicher Hinweis: Wenn Sie möchten, dass es wirklich perfekt ist, sollten Sie prüfen, ob Sie mehr Zeilen verschieben, als Sie unberührt lassen möchten. Wenn ja, dann aktualisieren Sie die weniger zahlreichen - die "unberührten" - D
Ruben Boeck
7

Es ist sehr einfach. Sie müssen eine "Kardinalitätsloch" -Struktur haben:

Sie müssen 2 Spalten haben:

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

Einfügen / Aktualisieren

  1. Wenn Sie den ersten neuen Datensatz einfügen, legen Sie fest order = round(max_bigint / 2).
  2. Stellen Sie beim Einfügen am Tabellenanfang ein order = round("order of first record" / 2)
  3. Beim Einfügen am Ende der Tabelle setzen Sie order = round("max_bigint - order of last record" / 2) 4) Beim Einfügen in der Mitte setzen Sieorder = 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 denken, dass Sie eine kleine Kardinalität haben, können Sie die Ordnungsspalte neu erstellen (normalisieren).

In der maximalen 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 genau sein!

user2382679
quelle
4

Im Allgemeinen erfolgt die Bestellung anhand einiger Informationen in den Aufzeichnungen, dem Titel, der ID oder was auch immer für die jeweilige Situation geeignet 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 Schallplatte zu schaffen, die auf den 5. Platz geht, können Sie Folgendes tun:

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

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

igelkott
quelle
4

… Es ist wohl noch chaotischer, weil Sie am Ende 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 bestimmt, daher spielt es keine Rolle, wie viele Nachkommastellen 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 nur die Auftragswerte für J und K auswählen und dann den Durchschnitt bilden und anschließend F aktualisieren müssen Deadlocks).

Wenn Sie in der Ausgabe eher Ganzzahlen als Brüche sehen möchten, berechnen Sie die Ganzzahlen entweder in der Clientanwendung oder verwenden Sie die Funktionen ROW_NUMBER () oder RANK () (sofern diese in Ihrem RDBMS enthalten sind).

Greenstone Walker
quelle
1

In meinem eigenen Projekt habe ich vor, eine Lösung zu versuchen, die der Lösung mit der Dezimalzahl ä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, da Sie nur ein b"\x00"an die beteiligten Datensätze anhängen , wenn Sie mehr Werte benötigen. ( intist in Python 3 unbegrenzt, andernfalls müssten Sie am Ende einen Teil der zu vergleichenden Bytes auswählen. Dabei wird davon ausgegangen, dass zwischen zwei benachbarten Werten die Differenzen gegen Ende gepackt werden.)

Angenommen, Sie haben zwei Datensätze b"\x00"und b"\x01"und möchten, dass ein Datensatz zwischen ihnen liegt. Zwischen 0x00und stehen keine Werte zur Verfügung. 0x01Fügen Sie also b"\x00"an beide Werte an, und Sie haben jetzt eine Reihe von Werten dazwischen, mit denen Sie neue Werte einfügen können.

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

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

>>> 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']
tjb1982
quelle
0

Ich fand diese Antwort viel besser. Ich zitiere es ganz:

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

Erwägen:

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

Und wenn Sie zum Beat ItEnde gehen möchten, 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 sehr gut mit sehr großen Zahlen skalieren. Versuchen Sie, ein paar tausend Titel in eine hypothetische Wiedergabeliste in Ihrer Datenbank aufzunehmen, und sehen Sie, wie lange es dauert, einen Titel 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 bedeutende Vorteile - die Reihenfolge der Tabelle ist etwas, über das Sie nachdenken können. Das dritte Lied hat immer eine order3. Die einzige Möglichkeit, dies zu gewährleisten, besteht darin, aufeinanderfolgende ganze Zahlen als Reihenfolge zu verwenden. Durch die Verwendung von Pseudolisten, Dezimalzahlen oder Ganzzahlen mit Lücken können Sie diese Eigenschaft nicht garantieren. In diesen Fällen besteht die einzige Möglichkeit, den n-ten Titel zu erhalten, darin, die gesamte Tabelle zu sortieren und den n-ten Datensatz abzurufen.

Und das ist viel einfacher, als Sie denken. Es ist einfach herauszufinden, was Sie tun möchten, um die beiden Update-Anweisungen zu generieren, und damit andere Personen sich diese beiden Update-Anweisungen ansehen und erkennen, was gerade getan wird.

vedant
quelle