web-dev-qa-db-ja.com

データベース内の順序付きリストの最良の表現?

この種のことはリレーショナルデータベースの原則に反することを私は知っていますが、状況を説明させてください。

ユーザーがいくつかのアイテムを配置するページがあります。

 ________________
| -Item1         |
| -Item2         |
| -Item3         |
| -Item4         |
|________________|

これらのアイテムは、ユーザーが指定した順序のままである必要があります。ただし、この順序はユーザーが任意の回数変更できます。

 ________________
| -Item1         |
| -Item4         |
| -Item2         |
| -Item3         |
|________________|

アプローチ1

私の当初の考えは、リスト内の自分の場所を表すインデックスをアイテムに与えることでした。

Page           Item
-----------    ---------------
FK | pid       FK | pid 
   | name      PK | iid 
                  | index
                  | content 

このソリューションを使用すると、アイテムを選択できますwhere pid = Page.pidおよびorder by indexこれは便利です。ただし、順序を変更するたびに、他の1つのアイテム(最良の場合)と他のすべてのアイテム(最悪の場合)の間のどこかを変更する必要があります。

アプローチ2

また、各項目がリスト内の次の項目を指すデータ構造のような「リンクリスト」を作成することも検討しました。

Page           Item
-----------    ---------------
FK | pid       FK | pid 
   | name      PK | iid 
                  | next
                  | content 

これにより、注文の変更にかかる費用が少なくなる可能性がありますが、注文を抽出するにはフロントエンドプログラミングに依存する必要があります。

思いもよらなかったアプローチはありますか?私にお知らせください。

35
Greg Guida

@ a1ex07はここで正しい方向に進んでいると思います(+1)。 itemOrderのギャップが3NFに違反しているとは思いませんが、3NFの別の違反について心配しています(これについては以下で詳しく説明します)。また、itemOrderフィールドの不正なデータにも注意する必要があります。これが私が始める方法です:

create table pages (
  pid int,
  primary key (pid)
);

create table users (
  uid int,
  primary key (uid)
);

create table items (
  iid int,
  primary key (iid)
);

create table details (
  pid int not null references pages(pid),
  uid int not null references users(uid),
  iid int not null references items(iid), 
  itemOrder int,
  primary key (pid, uid, iid),
  unique (pid, uid, itemOrder)
);

主キーは、ページごと、ユーザーごとに、一意のアイテムがあることを保証します。一意の制約により、ページごと、ユーザーごとに、一意のitemOrdersが確実に存在します。 3NFについての私の心配は次のとおりです。このシナリオでは、itemOrderは主キーに完全には依存していません。 (pid, uid)パーツのみに依存します。それは2NFでもありません。それが問題です。主キーにitemOrderを含めることもできますが、PKが必要なため、最小ではない可能性があるのではないかと心配しています。これをより多くのテーブルに分解する必要があるかもしれません。まだ考えています 。 。 。


[編集-トピックについてもっと考えます。 。 。 ]

仮定

  1. ユーザーがいます。

  2. ページがあります。

  3. アイテムがあります。

  4. (ページ、ユーザー)はアイテムのセットを識別します。

  5. (ページ、ユーザー)は、必要に応じてアイテムを保存できるスロットの順序付きリストを識別します。

  6. (ページ、ユーザー)のリストに重複するアイテムを含めたくありません。

プランA

上記のdetailsテーブルを強制終了します。

(ページ、ユーザー)で識別されるアイテムのセットを表すテーブルItemsByPageAndUserを追加します。

create table ItemsByPageAndUser (
   pid int not null references pages(pid),
   uid int not null references users(uid),
   iid int not null references items(iid),
  primary key (pid, uid, iid)   
)

テーブルSlotsByPageAndUserを追加して、アイテムを含む可能性のあるスロットの順序付きリストを表します。

create table SlotsByPageAndUser (
   pid       int not null references pages(pid),
   uid       int not null references users(uid),
   slotNum   int not null,
   iidInSlot int          references items(iid),
 primary key (pid, uid, slotNum),   
 foreign key (pid, uid, iid) references ItemsByPageAndUser(pid, uid, iid),
 unique (pid, uid, iid)
)

注1iidInSlotはnull許容であるため、必要に応じて空のスロットを使用できます。ただし、アイテムが存在する場合は、アイテムテーブルと照合する必要があります。

注2:これに使用できるアイテムのセットに含まれていないアイテムを追加しないようにするには、最後のFKが必要です(ユーザー、ページ)。

注3(pid, uid, iid)の一意性制約は、リストに一意性アイテムを含めるという設計目標を強制します(仮定6)。これがなければ、(page、user)で識別されるセットから、異なるスロットにある限り、好きなだけアイテムを追加できます。

これで、(ページ、ユーザー)への一般的な依存関係を維持しながら、アイテムをスロットから適切に分離しました。

この設計は確かに3NFであり、BCNFである可能性がありますが、その点でSlotsByPageAndUserについて心配しています。

問題は、テーブルSlotsByPageAndUserに一意の制約があるため、SlotsByPageAndUserItemsByPageAndUserの間の関係のカーディナリティが1対1であるということです。一般に、エンティティのサブタイプではない1-1の関係は間違っています。もちろん例外もありますが、おそらくこれも例外です。しかし、おそらくもっと良い方法があります。 。 。

プランB

  1. SlotsByPageAndUserテーブルを強制終了します。

  2. slotNum列をItemsByPageAndUserに追加します。

  3. (pid, uid, iid)に一意の制約をItemsByPageAndUserに追加します。

今では:

create table ItemsByPageAndUser (
   pid     int not null references pages(pid),
   uid     int not null references users(uid),
   iid     int not null references items(iid),
   slotNum int,
 primary key (pid, uid, iid),   
 unique (pid, uid, slotNum)
)

注4slotNumをnull許容のままにすると、リストにないセット内のアイテムを指定する機能が保持されます。だが 。 。 。

注5:null許容列を含む式に一意性制約を設定すると、一部のデータベースで「興味深い」結果が生じる可能性があります。 Postgresで意図したとおりに機能すると思います。 ( このディスカッション ここSOを参照してください。)他のデータベースの場合、マイレージは異なる場合があります。

今では厄介な1-1の関係がぶら下がっていないので、それは良いことです。唯一の非キー属性(slotNum)はキー、キー全体、およびキーのみに依存するため、これは3NFのままです。 (あなたが話しているページ、ユーザー、アイテムを教えてくれないと、slotNumについて尋ねることはできません。)

[(pid, uid, iid)-> slotNum]および[(pid,uid,slotNum)-> iid]であるため、BCNFではありません。ただし、(pid、uid、slotNum)に一意の制約があり、データが不整合な状態になるのを防ぐのはそのためです。

これは実行可能な解決策だと思います。

7
Alan

解決策:indexを文字列にします(文字列は、本質的に、無限の「任意精度」を持っているため)。または、intを使用する場合は、indexを1ではなく100ずつインクリメントします。

パフォーマンスの問題は次のとおりです。2つのソートされたアイテム間に「中間」の値がありません。

item      index
-----------------
gizmo     1
              <<------ Oh no! no room between 1 and 2.
                       This requires incrementing _every_ item after it
gadget    2
gear      3
toolkit   4
box       5

代わりに、次のようにしてください(以下のより良い解決策):

item      index
-----------------
gizmo     100
              <<------ Sweet :). I can re-order 99 (!) items here
                       without having to change anything else
gadget    200
gear      300
toolkit   400
box       500

さらに良いこと:Jiraがこの問題を解決する方法は次のとおりです。それらの「ランク」(いわゆるインデックス)は、ランク付けされたアイテムの間に1トンの呼吸の余地を与える文字列値です。

これが私が使用しているjiraデータベースの実際の例です

   id    | jira_rank
---------+------------
 AP-2405 | 0|hzztxk:
 ES-213  | 0|hzztxs:
 AP-2660 | 0|hzztzc:
 AP-2688 | 0|hzztzk:
 AP-2643 | 0|hzztzs:
 AP-2208 | 0|hzztzw:
 AP-2700 | 0|hzztzy:
 AP-2702 | 0|hzztzz:
 AP-2411 | 0|hzztzz:i
 AP-2440 | 0|hzztzz:r

この例hzztzz:iに注意してください。文字列ランクの利点は、2つのアイテム間のスペースが不足することです。つまり、まだ他にランクを付け直す必要はありません。文字列にさらに文字を追加して、フォーカスを絞り込むだけです。

編集:コメントで述べたように、0|hzztzz:0|hzztzz:aの間に何も挿入することはできません。そのため、jiraのデータベースでは、そのシナリオを回避するために、:iではなく:aが定期的に最後に自動的に追加されます。本当に問題を防ぎたい場合は、I thinkアルゴリズムを変更して、(たとえば)最後に:aを挿入するたびに、代わりに:aiを挿入することができます。このようにして、ランキングが文字aで終わらないことを論理的に保証します。これは、何も並べ替えなくても、アイテムをさらに挿入するための「余地」があることを意味します。

34
Alexander Bird

Pageという名前のorderテーブルに新しい文字(nvarchar)列を追加できます。このテーブルには、iidの区切りリストが好きな順序で含まれています。つまり、1,4,3,2。利点は、維持する1つのテーブルの1つのフィールドだけです。明らかな欠点は、文字型と数値型の間で変換するユーティリティ関数を作成する必要があることです。これは、実際にはそれほど長くはかからないでしょう。

4
Barry Kaye

アイテムの数がそれほど多くないと予想される場合は、最初のアプローチの少し変更されたバージョンを使用できます。連続するインデックス間にギャップを作るだけです。たとえば、最初のアイテムのインデックスは100、2番目のアイテムは200などです。この方法では、ギャップが見つからない場合にのみ、毎回すべてのインデックスを更新する必要はありません。

3
a1ex07

アプローチ1を使用して、インデックス更新のパフォーマンスへの影響を確認してください。 1ページあたり百万のアイテムを処理しない限り、パフォーマンスが不足していることに気付く可能性は低く、を処理する際にSQLのすべての能力を保持します。 == --- ==)データのセット

純粋な非手続き型SQLからの操作がはるかに難しいことに加えて、アプローチ2では、アイテムを並べ替えるときに「リンク」を再接続する適切な場所を見つけるためにリストをトラバースする必要があります。 。

2