web-dev-qa-db-ja.com

DBでレコードを注文する機能を作成するにはどうすればよいですか?

レコードを並べ替えてDBに保存できるようにする必要があります(MS SQLを使用していますが、問題ではないようです)。私は可能な2つの解決策を見ます:

  1. 注文列を追加します。次に、2つのレコードを並べ替える場合は、これら2つのレコードのOrder列の値を変更する必要があります。最初のレコードセットの値を2番目に、またはその逆に設定します。問題-列の順序付けに一意の制約を追加する必要がありますが、最初のステップで次のようなエラーを検出するため、簡単に行うことはできません。

{「一意キー制約 'IX_EscortItems'の違反。オブジェクト 'dbo.EscortItems'に重複キーを挿入できません。重複キーの値は(2、20)です。\ r\nステートメントが終了しました。 "}

  1. 2番目のアプローチは、単一リンクリストです。 Parent列を追加し、最初の要素または前の要素のIDにnullを格納します。しかし、一意制約に関しても同じ問題があります。

この問題を解決するための正しいアプローチは何ですか?

3
user285336

使用できるアプローチは複数あります。

  1. 最も簡単な方法は、(リンクリストまたはシーケンス番号として)順序を指定する列のUNIQUE制約を無視することです。その後、最終的にデータベースが再び整合性を保つようにするのはソフトウェア次第です。
  2. ギャップのあるシーケンス番号を使用します。
    最初に10、20、30などのシーケンス番号から始める場合は、すでに指定されているシーケンス番号の間にシーケンス番号を割り当てることで、それらを並べ替えることができます。

    たとえば、4番目の要素を最初と2番目の要素の間に配置すると、シーケンス番号は10、15、20、30、50などになります。

    要素を配置するためにギャップがなくなるのを回避するには、シーケンス番号を定期的に正規化するか、浮動小数点シーケンス番号を使用できます。

あなたのための4つのオプション。私はオプション4が好きですが、最初のものが最も簡単です。

オプション1. 1つ上のレベルから開始する

Order列の値は重要ではなく、それらの相対値のみです。 1から10、11から20、または57から66の10行に番号を付けるかどうかはすべて同じです。

したがって、非常に簡単な解決策は、最も高いOrder列よりも1つ上から開始することです。行にOrderの値が1〜10あり、それらを逆にする必要がある場合は、 20から11。

それらは正しくソートされ、個別の値の範囲にあるため、一意性違反はありません。

これの欠点は、リスト全体をやり直したくなく、2つの値を交換したいだけの場合は少しトリッキーです。

オプション2.一意性制約にソートバージョン番号を含める

1つの列Orderを追加する代わりに、2つの列SortOrderSortVersionを追加します。これら2つの列の組み合わせに一意性制約を適用します。

リスト内のアイテムを並べ替える場合は、SortVersionの新しい値を保存するときに、SortVersion列を1つ増やします。

オプション3.行を削除して再挿入する

使用しているフレームワークによっては、レコードをドメインオブジェクトとしてアプリケーション層に格納するため、これが最も簡単なオプションとなる場合があります。拭き取り、必要な順序で挿入し直してください。

これは、EFを使用していない場合、またはカスケード削除を引き起こすFK関係がある場合は、実行できない可能性があります。

オプション4.「order by」列を別のテーブルに保存する

技術的に言えば、これは最も正規化されたオプションです。ソート順はおそらくエンティティの属性ではなく、エンティティと他のエンティティ間の関係の属性だからです。したがって、Orderは実際には3NFに違反しています。信じられないかもしれませんが、列の値はキー(1NF)、キー全体(2NF)、および何もない主キー(3NF)に依存する必要があります)、この場合、Order列は、より大きなコンテキスト内のエンティティの関係に依存します。これを正規化するには、結合テーブルが必要です。

したがって、請求書テーブルとline_itemテーブルがあるとします。 3番目のテーブル、InvoiceIDLineItemID、およびSortOrderを含むinvoice_line_itemテーブルを追加します。行の順序を変更する場合は、行をすべて削除してから、再度挿入してください。これにより、FKまたは関連オブジェクトの問題が回避されます。これらのFKはこの3番目のテーブルではなく、他のテーブルの1つにあるためです。これは、明細と請求書の関係を変更することになるので、それらを削除して置き換えることは理にかなっています。当然、すべてをトランザクションでラップする必要があります。

6
John Wu

一意の制約は、Order列の組み合わせである必要がありますand親テーブルへの外部キー。例として「ショッピングカート」を使用します。

Table: ShoppingCartItems
- ShoppingCartId (FK to ShoppingCarts table)
- DisplayNumber (int)

この場合、一意の制約により、ShoppingCartItems.ShoppingCartIdShoppingCartItems.DisplayNumberの組み合わせがテーブル内で一意になるため、ショッピングカートに同じ表示順序で複数のアイテムを含めることはできません。

3
Greg Burghardt

一時的に負の値を使用します

declare int @rowA = 7 
declare int @rowB = 22

update table set order = -@rowB where order =  @rowA; 
update table set order =  @rowA where order =  @rowB; 
update table set order =  @rowB where order = -@rowB;

あなたはそれをトランザクションにラップすることができますが、ほとんどの場合あなたは衝突を起こすことはないでしょう

3
paparazzo

3つの提案:

  1. 常に、重複が発生しない順序で更新を行ってください。たとえば、2と3を交換するには、最初に2をnullで更新し、次に2を挿入して3を置き換え、最後に3を挿入します。
  2. ソートされたリストの真ん中にレコードを挿入するには、クエリが必要になるでしょう。レコードに順番に番号を付ける場合、1つを挿入するには多くのレコードの番号を付け直す必要があります。リンクリストを使用する場合は、2つを更新するだけで済みます。そのため、リンクリストは実行時に安価になり、おそらくコーディングが容易になると思います。
  3. 私はあなたの列に名前を付けるのが慣習だと思いますsort_keyordinalまたはおそらくposition

RubyでのActive Recordのオープンソース実装があなたに刺激を与えるかもしれません。 acts_as_list gem のソースを読んで、これらのアイデアと比較してください。

2
dcorking

リレーショナルデータベース全般の基本的な考え方の1つは、データベース自体のレコードには「固有の」順序がないということです。1

順序付けは通常、クエリの結果に課せられます。この場合、項目を表示する順序を指定する列を決定し、それに応じて結果を順序付けることができます。

たとえば、各注文の日付/時刻のいずれかで注文のレポートを作成できるようにしたい場合があります(つまり、注文された順に表示します)。

select ID, value, date /* , ... */ from Orders
order by ship_date

...または注文が発送された日時

select ID, value, date /* , ... */ from Orders
order by ship_date

...またはおそらくサイズの降順(最大の注文がいつ発生したかに関係なくすばやく確認できるようにするため)。

select ID, value, date /* , ... */ from Orders
order by value desc

もちろん、order_by列(必要に応じて別の名前で)完全に任意の順序で(または、少なくともデータベースに格納されていない基準に基づいて)順序で表示する場合。この場合、並べ替えを行うときに、いくつかの選択肢があります。 1つは、この列に一意の値が含まれていることをデータベースに通知しないことです。もう1つは、単に重複を避けることです。たとえば、1と2の番号が付いたレコードから始めたとします。それらを逆にするには、番号をそれぞれ4と3に変更します。

これをプログラムで実行するには、現在列にある最大値を見つけます2、次に並べ替えるときに、それより大きい値をベース値として使用します。また、現在使用されている最小値を確認することもできます。レコード数よりも大きい場合は、0から番号付けをやり直すことができます。


1. MS SQLには「クラスター化インデックス」の概念がありますが、これはレコードが格納される順序に直接関連しています-これは基本的には最適化です。 2.通常はこの列にインデックスを付けるので、この操作は非常に高速であると予想できます。

2
Jerry Coffin

多くの場合、注文列を使用する場合、重要なのは特定の値ではなく注文自体です。したがって、順序列を使用する場合、それらが互いに相対的に正しくソートされている限り、その列の番号が何であるかを気にする必要はありません。

したがって、レコードを順番に上に移動したい場合は、次のことができます。

  • 挿入ポイント以降のすべての注文値をインクリメントして、開いたスロットを作成します
  • レコードの注文値をそのスロットに更新します。

競合なし

さらに図解。注文値がある場合:

1,2,3,4,5,6,7,8

#7のアイテムを#3に移動するには、まず次のような更新を実行します

update foo set order = order +1 where container = X and order >=3

これはあなたに与えます

1,2,4,5,6,7,8,9

次に、移動する特定のアイテムの注文値を設定します

update foo set order = 3 where id = 102334

そして、あなたは終わるかもしれません

1,2,3,4,5,6,7,9

他のタイプの再配置についても、同様のパターンに従います。

これは明らかに、小さめのサイズまたは中程度のサイズのセットを扱う場合に最適です。特定の順序で15,000,000のレコードがある場合、おそらく他のアプローチの1つを使用する必要があります。

2
GrandmasterB

ここで、要件をより深く掘り下げ、実装についてあまり気にしないでください。それはそれ自体を処理します。それを行うためのより良い方法はありません。

並べ替える一意の番号があるかどうか、本当に気になる人はいますか?ユーザーとして(これを手動で実行していると仮定して)、それらがすべてゼロであるかどうか気にしない場合はどうなりますか?不注意で重複が含まれていると思われ、その順序がインターフェースに表示されているものと一致すると想定している場合は、いつでも警告を投稿できます。繰り返しになりますが、請求システムのように、この一意でソートされた番号の要件はありますか(これらの番号のギャップを許容しないドイツの会社のシステムに取り組んだことを思い出します)。

ソートを実行するための非常に複雑で時間のかかるアルゴリズムがあり、パフォーマンスの目的でこの値を保存したい場合は、データベースの制約を気にする必要がありますか?コードで処理します。

ユーザーがドロップダウンリストで優先する並べ替えを設定できるシステムを使用しています。管理者が番号を割り当て、それを機能させるだけです。重複のトラッピングはそれだけの価値はありません。

2
JeffO