順序付けされた情報をリレーショナルデータベースに保存する方法


20

注文した情報をリレーショナルデータベースに適切に保存する方法を理解しようとしています。

例:

曲で構成されるプレイリストがあるとします。リレーショナルデータベース内には、Playlistsいくつかのメタデータ(名前、作成者など)を含むの。また、私はと呼ばれるテーブルを持っSongs含む、playlist_id曲固有の情報(名前、アーティスト、期間など)だけでなく、。

デフォルトでは、新しい曲がプレイリストに追加されると、最後に追加されます。Song-ID(昇順)で注文する場合、注文は追加の順序になります。しかし、ユーザーがプレイリストの曲を並べ替えることができるとしたらどうでしょうか?

いくつかのアイデアを思いつきました。それぞれに長所と短所があります。

  1. と呼ばれる列orderは、整数です。曲を移動すると、その変更を反映するために、古い位置と新しい位置の間のすべての曲の順序が変更されます。これの欠点は、曲を移動するたびに多くのクエリを実行する必要があり、移動アルゴリズムが他のオプションほど簡単ではないことです。
  2. orderという10進数の列(NUMERIC)。曲を移動すると、隣接する2つの数字の間に浮動小数点値が割り当てられます。欠点:10進数フィールドはより多くのスペースを必要とし、数回変更するたびに範囲を再分散するように注意しない限り、精度が不足する可能性があります。
  3. 別の方法はpreviousnext他の曲を参照するフィールドとフィールドを持つことです。(または、現在、プレイリストの最初の曲、最後の曲の場合はNULLです。基本的には、リンクリストを作成します)。欠点:「リストでX番目の曲を見つける」などのクエリは、一定時間ではなく、線形時間になります。

これらの手順のうち、実際に最もよく使用されるのはどれですか?これらの手順のうち、中規模から大規模のデータベースで最も速いのはどれですか?これを実現する他の方法はありますか?

編集:簡単にするため、この例では、ソングは1つのプレイリストにのみ属します(多対1の関係)。もちろん、ジャンクションテーブルを使用して、song⟷playlistを多対多の関係にすることもできます(そして、そのテーブルに上記の戦略の1つを適用します)。


1
オプション1(整数としての順序)を100ステップで使用できます。次に、1つの曲を移動する場合、並べ替える必要はありません。100の間の値をとるだけです。時々、曲間のギャップを再取得するために、新しい番号を付け直す必要があります。
knut

4
「これの欠点は、曲を移動するたびに多くのクエリを実行する必要があることです」?!-- update songorder set order = order - 1 where order >= 12 & order <= 42; update songorder set order = 42 where id = 123;それは2つの更新です-30ではありません。注文に一意の制約を適用する場合は3つ。

2
他の何かが必要であるという事実を知らない限り、オプション1を使用してください。データベースの初心者が遭遇する問題の1つは、データベースがこの種のことを非常に優れていることを理解していないことです。データベースを機能させることを恐れないでください。
GrandmasterB

1
Queries like 'find the Xth Song in the list' are no longer constant-timeまた2.オプションの真実である
ドク・ブラウンは、

2
@MikeNakis:高価に思えますが、すべての作業はサーバーで行われています。サーバーは(通常)この種の作業用に最適化されています。数百万行のテーブルではこの手法を使用しませんが、数千行のテーブルではこの手法を割引しません。
TMN

回答:


29

データベースは特定のものに対して最適化されています。多くの行をすばやく更新することもその1つです。これは、データベースに作業を任せるときに特に当てはまります。

考慮してください:

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

Beat It最後に移動するには、2つのクエリがあります。

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

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

以上です。これは、非常に大きな数値で非常にうまくスケールアップします。データベース内の仮想的なプレイリストに数千曲を入れてみて、ある場所から別の場所に曲を移動するのにかかる時間を確認してください。これらは非常に標準化された形式を持っているため:

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

update table
  set order = ?
  where song = ?

非常に効率的に再利用できる2つの準備済みステートメントがあります。

これはいくつかの重要な利点を提供します-テーブルの順序はあなたが推論できるものです。3曲目はorder常に3曲です。これを保証する唯一の方法は、連続した整数を順序として使用することです。疑似リンクリスト、10進数、またはギャップのある整数を使用しても、このプロパティは保証されません。これらの場合、n番目の曲を取得する唯一の方法は、テーブル全体をソートしてn番目のレコードを取得することです。

そして本当に、これはあなたが思っているよりもずっと簡単です。やりたいことを理解し、2つの更新ステートメントを生成し、他の人がそれら2つの更新ステートメントを見て、何が行われているかを理解するのは簡単です。


2
私はこのアプローチが好きになり始めています。
マイクナキス

2
@MikeNakisそれはうまく機能します。同様のアイデアに基づいた二分木もあります- 変更された事前順序木です。頭を動かすには少し時間がかかりますが、階層データに対して非常に優れたクエリを実行できます。大きな木であっても、パフォーマンスの問題は一度もありません。コードについて推論できることは、単純なコードが必要なパフォーマンスを欠いていることが示されるまで(そしてそれは極端な状況でのみ行われていた)、私が非常に強調したことです。

キーワードであるorderため、使用に問題order byはありますか?
kojow7

@ kojow7、フィールドにキーワードと競合する名前がある場合、それらを目盛り「 `」で囲む必要があります。
アンドリ

このアプローチは理にかなっていorderますが、新しい曲をプレイリストに追加するときに価値を得る最良の方法は何ですか。9曲目だとしたら、レコードを追加orderするCOUNT前に9曲を挿入するよりも良い方法はありますか?
delashum

3

まず第一に、あなたがしたことの説明からは明確ではありませんPlaylistSongsが、PlaylistIdとを含むテーブルが必要ですSongIdどの曲がどのプレイリストに属しているかを説明しています。

この表では、注文情報を追加する必要があります。

私のお気に入りのメカニズムは実数です。私は最近それを実装しました、そしてそれは魅力のように働きました。曲を特定の位置に移動する場合、前の曲と次の曲の値のOrdering平均として新しい値を計算しますOrdering。64ビットの実数を使用すると、地獄が凍結するのとほぼ同時に精度が不足しますが、実際に後世のためにソフトウェアを作成している場合はOrdering、それぞれの曲すべてに素敵な丸め整数値を再割り当てすることを検討してください時々プレイリスト。

追加のボーナスとして、これを実装したコードを次に示します。もちろん、それをそのまま使用することはできませんし、あなたのためにサニタイズするのは今のところ私には大変な仕事ですので、私はあなたからそれをアイデアを得るために投稿するだけです。

クラスはParameterTemplate(なんでも聞いてはいけません!)メソッドは、このテンプレートが属するパラメーターテンプレートのリストをその親から取得しますActivityTemplate。(何でも聞いてはいけません!)このコードには、精度の不足に対する保護が含まれています。除数はテストに使用されます。ユニットテストでは、大きな除数を使用して、精度がすぐになくなるため、精度保護コードをトリガーします。2番目のメソッドはpublicであり、「内部使用のみ。呼び出してはいけない」ので、テストコードで呼び出すことができます。(テストコードは、テストするコードと同じパッケージにないため、パッケージプライベートにすることはできません。)順序を制御するフィールドが呼び出されOrderinggetOrdering()およびを介してアクセスされますsetOrdering()。Hibernateを介してオブジェクトリレーショナルマッピングを使用しているため、SQLは表示されません。

/**
 * 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;
}

整数の順序付けを使用しますが、並べ替えが高すぎると感じた場合は、Xごとにジャンプすることにより、並べ替えの数を減らします。Xは、たとえば20スターターとしては問題ないはずです。
ウォーレンP

1
@WarrenPはい、私は知っています、それもこの方法で行うことができます、それが私が「最高の」または「1つ」のアプローチの代わりにこの「私のお気に入り」のアプローチと呼んだ理由です。
マイクナキス

0

私のために働いたのは、100個のオーダーの小さなリストのために、ハイブリッドアプローチを取ることでした:

  1. 10進数のSortOrder列ですが、0.5の差を格納するのに十分な精度しかありません(つまり、Decimal(8,2)など)。
  2. 並べ替えるとき、現在の行が移動した場所の上下に行のPKが存在する場合は、それらを取得します。(たとえば、アイテムを最初の位置に移動する場合、上の行はありません)
  3. 現在、前、および次の行のPKをサーバーに送信して、ソートを実行します。
  4. 前の行がある場合は、現在の行の位置をprev + 0.5に設定します。次だけがある場合は、現在の行の位置をnext-0.5に設定します。
  5. 次に、SQL ServerのRow_Number関数を使用してすべての位置を更新し、新しいソート順で並べ替えるストアドプロシージャがあります。これは、row_number関数が整数の序数を与えるため、順序を1,1.5,2,3,4,6から1,2,3,4,5,6に変換します。

そのため、10進列に格納された、ギャップのない整数の順序になります。かなりきれいだと感じます。しかし、一度に更新する必要のある数十万行があると、スケールアップが非常にうまくいかない場合があります。しかし、もしそうなら、そもそもユーザー定義のソートを使用しているのはなぜですか?(注:数百万のユーザーを含む大きなテーブルがあり、各ユーザーが数百のアイテムのみを並べ替える場合、where句を使用して変更を1人のユーザーのみに制限するため、上記のアプローチをうまく使用できます)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.