it-swarm.com.de

So speichern Sie bestellte Informationen in einer relationalen Datenbank

Ich versuche zu verstehen, wie bestellte Informationen in einer relationalen Datenbank richtig gespeichert werden.

Ein Beispiel:

Angenommen, ich habe eine Wiedergabeliste, die aus Songs besteht. In meiner relationalen Datenbank befindet sich eine Tabelle mit Playlists, die einige Metadaten (Name, Ersteller usw.) enthält. Ich habe auch eine Tabelle namens Songs, die einen playlist_id Sowie songspezifische Informationen (Name, Künstler, Dauer usw.) enthält.

Wenn ein neuer Titel zu einer Wiedergabeliste hinzugefügt wird, wird er standardmäßig an das Ende angehängt. Bei Bestellung mit Song-ID (aufsteigend) ist die Reihenfolge die Reihenfolge der Addition. Was aber, wenn ein Benutzer Songs in der Wiedergabeliste nachbestellen kann?

Ich hatte ein paar Ideen mit ihren Vor- und Nachteilen:

  1. Eine Spalte namens order, die eine Ganzzahl ist. Wenn ein Song verschoben wird, wird die Reihenfolge aller Songs zwischen seiner alten und neuen Position geändert, um die Änderung widerzuspiegeln. Der Nachteil dabei ist, dass jedes Mal, wenn ein Song verschoben wird, viele Abfragen durchgeführt werden müssen und der Verschiebungsalgorithmus nicht so trivial ist wie bei den anderen Optionen.
  2. Eine Spalte namens order, die eine Dezimalzahl (NUMERIC) ist. Wenn ein Lied verschoben wird, wird ihm der Gleitkommawert zwischen den beiden benachbarten Zahlen zugewiesen. Nachteil: Dezimalfelder benötigen mehr Platz und es kann sein, dass die Genauigkeit ausgeht, es sei denn, es wird darauf geachtet, den Bereich nach einigen Änderungen neu zu verteilen.
  3. Eine andere Möglichkeit wäre, ein previous und ein next Feld zu haben, die auf andere Songs verweisen. (oder sind NULL im Fall des ersten bzw. letzten Songs in der Wiedergabeliste; Grundsätzlich erstellen Sie eine verknüpfte Liste ). Nachteil: Abfragen wie "Finde den X. Song in der Liste" sind nicht mehr zeitkonstant, sondern linear.

Welches dieser Verfahren wird in der Praxis am häufigsten angewendet? Welches dieser Verfahren ist bei mittleren bis großen Datenbanken am schnellsten? Gibt es andere Möglichkeiten, dies zu erreichen?

BEARBEITEN: Der Einfachheit halber gehört ein Song im Beispiel nur zu einer Wiedergabeliste (eine Viele-zu-Eins-Beziehung). Natürlich könnte man auch eine Junction-Tabelle verwenden, sodass die Song-Playlist eine Viele-zu-Viele-Beziehung ist (und eine der oben genannten Strategien auf diese Tabelle anwenden).

24
Qqwy

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, das n-te Lied zu erhalten, darin, die gesamte Tabelle zu sortieren und die n-te Aufzeichnung 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.

32
user40980

Zunächst ist aus Ihrer Beschreibung nicht ersichtlich, was Sie getan haben, aber Sie benötigen eine PlaylistSongs -Tabelle, die eine PlaylistId und eine SongId enthält und beschreibt, welche Songs dazu gehören zu welchen Wiedergabelisten.

In dieser Tabelle müssen Sie Bestellinformationen hinzufügen.

Mein Lieblingsmechanismus ist mit reellen Zahlen. Ich habe es kürzlich implementiert und es hat wie ein Zauber funktioniert. Wenn Sie einen Song an eine bestimmte Position verschieben möchten, berechnen Sie seinen neuen Ordering -Wert als Durchschnitt der Ordering -Werte des vorherigen und des nächsten Songs. Wenn Sie eine 64-Bit-reelle Zahl verwenden, wird Ihnen ungefähr zur gleichen Zeit, zu der die Hölle zufriert, die Genauigkeit ausgehen. Wenn Sie jedoch wirklich Ihre Software für die Nachwelt schreiben, sollten Sie eine schöne gerundete Ganzzahl Ordering neu zuweisen Werte für alle Songs in jeder Wiedergabeliste von Zeit zu Zeit.

Als zusätzlichen Bonus ist hier der Code, den ich geschrieben habe, der dies implementiert. Natürlich können Sie es nicht so verwenden, wie es ist, und es wäre im Moment zu viel Arbeit für mich, es für Sie zu bereinigen. Deshalb veröffentliche ich es nur, damit Sie Ideen daraus erhalten.

Die Klasse ist ParameterTemplate (was auch immer, fragen Sie nicht!) Die Methode ruft die Liste der Parametervorlagen, zu denen diese Vorlage gehört, von ihrem übergeordneten ActivityTemplate ab. (Was auch immer, fragen Sie nicht!) Der Code enthält einen Schutz vor Ungenauigkeiten. Der Divisor wird zum Testen verwendet: Der Komponententest verwendet einen großen Divisor, um schnell die Genauigkeit zu verringern und so den Präzisionsschutzcode auszulösen. Die zweite Methode ist öffentlich und "nur für den internen Gebrauch; nicht aufrufen", damit der Testcode sie aufrufen kann. (Es konnte nicht paketprivat sein, da sich mein Testcode nicht im selben Paket befindet wie der Code, den es testet.) Das Feld, das die Reihenfolge steuert, heißt Ordering und wird über getOrdering() und aufgerufen setOrdering(). Sie sehen kein SQL, da ich die objektrelationale Zuordnung über den Ruhezustand verwende.

/**
 * Moves this {@link ParameterTemplate} to the given index in the list of {@link ParameterTemplate}s of the parent {@link ActivityTemplate}.
 *
 * The index must be greater than or equal to zero, and less than or equal to the number of entries in the list.  Specifying an index of zero will move this item to the top of
 * the list. Specifying an index which is equal to the number of entries will move this item to the end of the list.  Any other index will move this item to the position
 * specified, also moving other items in the list as necessary. The given index cannot be equal to the current index of the item, nor can it be equal to the current index plus
 * one.  If the given index is below the current index of the item, then the item will be moved so that its new index will be equal to the given index.  If the given index is
 * above the current index, then the new index of the item will be the given index minus one.
 *
 * NOTE: this method flushes the persistor and refreshes the parent node so as to guarantee that the changes will be immediately visible in the list of {@link
 * ParameterTemplate}s of the parent {@link ActivityTemplate}.
 *
 * @param toIndex the desired new index of this {@link ParameterTemplate} in the list of {@link ParameterTemplate}s of the parent {@link ActivityTemplate}.
 */
public void moveAt( int toIndex )
{
    moveAt( toIndex, 2.0 );
}

/**
 * For internal use only; do not invoke.
 */
public boolean moveAt( int toIndex, double divisor )
{
    MutableList<ParameterTemplate<?>> parameterTemplates = getLogicDomain().getMutableCollections().newArrayList();
    parameterTemplates.addAll( getParentActivityTemplate().getParameterTemplates() );
    assert parameterTemplates.getLength() >= 1; //guaranteed since at the very least, this parameter template must be in the list.
    int fromIndex = parameterTemplates.indexOf( this );
    assert 0 <= toIndex;
    assert toIndex <= parameterTemplates.getLength();
    assert 0 <= fromIndex;
    assert fromIndex < parameterTemplates.getLength();
    assert fromIndex != toIndex;
    assert fromIndex != toIndex - 1;

    double order;
    if( toIndex == 0 )
    {
        order = parameterTemplates.fetchFirstElement().getOrdering() - 1.0;
    }
    else if( toIndex == parameterTemplates.getLength() )
    {
        order = parameterTemplates.fetchLastElement().getOrdering() + 1.0;
    }
    else
    {
        double prevOrder = parameterTemplates.get( toIndex - 1 ).getOrdering();
        parameterTemplates.moveAt( fromIndex, toIndex );
        double nextOrder = parameterTemplates.get( toIndex + (toIndex > fromIndex ? 0 : 1) ).getOrdering();
        assert prevOrder <= nextOrder;
        order = (prevOrder + nextOrder) / divisor;
        if( order <= prevOrder || order >= nextOrder ) //if the accuracy of the double has been exceeded
        {
            parameterTemplates.clear();
            parameterTemplates.addAll( getParentActivityTemplate().getParameterTemplates() );
            for( int i = 0; i < parameterTemplates.getLength(); i++ )
                parameterTemplates.get( i ).setOrdering( i * 1.0 );
            rocs3dDomain.getPersistor().flush();
            rocs3dDomain.getPersistor().refresh( getParentActivityTemplate() );
            moveAt( toIndex );
            return true;
        }
    }
    setOrdering( order );
    rocs3dDomain.getPersistor().flush();
    rocs3dDomain.getPersistor().refresh( getParentActivityTemplate() );
    assert getParentActivityTemplate().getParameterTemplates().indexOf( this ) == (toIndex > fromIndex ? toIndex - 1 : toIndex);
    return false;
}
3
Mike Nakis

Was für mich bei einer kleinen Liste in der Größenordnung von 100 Artikeln funktioniert hat, war ein hybrider Ansatz:

  1. Decimal SortOrder-Spalte, jedoch mit nur ausreichender Genauigkeit, um eine Differenz von 0,5 zu speichern (d. H. Decimal (8,2) oder so).
  2. Nehmen Sie beim Sortieren die PKs der Zeile über und unter die Position, in die die aktuelle Zeile gerade verschoben wurde, sofern vorhanden. (Sie haben keine Zeile darüber, wenn Sie das Objekt beispielsweise an die erste Position verschieben.)
  3. Stellen Sie die PKs der aktuellen, vorherigen und nächsten Zeile auf den Server, um die Sortierung durchzuführen.
  4. Wenn Sie eine vorherige Zeile haben, setzen Sie die Position der aktuellen Zeile auf prev + 0.5. Wenn Sie nur eine nächste haben, setzen Sie die Position der aktuellen Zeile auf next - 0.5.
  5. Als nächstes habe ich einen gespeicherten Prozess, der alle Positionen mithilfe der SQL Server-Funktion Row_Number aktualisiert und nach der neuen Sortierreihenfolge sortiert. Dadurch wird die Reihenfolge von 1,1,5,2,3,4,6 auf 1,2,3,4,5,6 geändert, da die Funktion row_number ganzzahlige Ordnungszahlen liefert.

Sie erhalten also eine ganzzahlige Reihenfolge ohne Lücken, die in einer Dezimalspalte gespeichert ist. Es ist ziemlich sauber, fühle ich. Es kann jedoch sein, dass es nicht sehr gut skaliert werden kann, wenn Sie Hunderttausende von Zeilen auf einmal aktualisieren müssen. Aber wenn ja, warum verwenden Sie überhaupt eine benutzerdefinierte Sortierung? (Hinweis: Wenn Sie eine große Tabelle mit Millionen von Benutzern haben, aber jeder Benutzer nur einige hundert Elemente zum Sortieren hat, können Sie den obigen Ansatz problemlos verwenden, da Sie ohnehin eine where-Klausel verwenden, um die Änderungen auf nur einen Benutzer zu beschränken )

0
John