web-dev-qa-db-ja.com

コンマで区切られた複数の外部キーを使用するのは間違っていますか?

DealDealCategoriesの2つのテーブルがあります。 1つの取引に多くの取引カテゴリを設定できます。

したがって、適切な方法は、次の構造を持つDealCategoriesというテーブルを作成することです。

DealCategoryId (PK)
DealId (FK)
DealCategoryId (FK)

ただし、アウトソーシングチームは、次のように複数のカテゴリをDealテーブルに保存しました。

DealId (PK)
DealCategory -- In here they store multiple deal ids separated by commas like this: 18,25,32.

彼らのしたことは間違っているように感じますが、これが正しくない理由を明確に説明する方法がわかりません。

これが間違っていることをどのように説明すればよいですか?または多分I'm間違っている人で、これは許容できるものですか?

31

はい、それはひどい考えです。

行く代わりに:

SELECT Deal.Name, DealCategory.Name
FROM Deal
  INNER JOIN
     DealCategories ON Deal.DealID = DealCategories.DealID
  INNER JOIN
     DealCategory ON DealCategories.DealCategoryID = DealCategory.DealCategoryID
WHERE Deal.DealID = 1234

あなたは今行く必要があります:

SELECT Deal.ID, Deal.Name, DealCategories
FROM Deal
WHERE Deal.DealID = 1234

次に、アプリケーションコードで、コンマリストを個々の数値に分割する必要があります。次に、データベースを個別にクエリします。

SELECT DealCategory.Name
FROM DealCategory
WHERE DealCategory.DealCategoryID IN (<<that list from before>>)

このデザインアンチパターンは、リレーショナルモデリングの完全な誤解(テーブルを怖がる必要はありません。テーブルはあなたの友達です。テーブルを使用してください)、またはカンマ区切りのリストを取得してそれを分割するほうが速いという誤って誤解されているという信念が原因ですアプリケーションコードでは、リンクテーブルを追加するよりも(neverです)。 3番目のオプションは、外部キーを設定できるほどSQLに自信がない/能力がないということですが、そうであれば、リレーショナルモデルの設計とは何の関係もありません。

SQL Antipatterns (Karwin、2010)は、このアンチパターン(彼は「Jaywalking」と呼んでいます)の章全体を15〜23ページで取り上げています。また、著者は SOでの類似の質問 に投稿しています。 (この例に適用される)彼が指摘する重要な点は次のとおりです。

  • 特定のカテゴリのすべての取引のクエリはかなり複雑です(その問題を解決する最も簡単な方法は正規表現ですが、正規表現自体が問題です)。
  • 外部キーの関係なしに参照整合性を強制することはできません。 DealCategory nrを削除した場合。 #26、アプリケーションコードで、カテゴリ#26への参照を探して各取引を実行し、それらを削除する必要があります。これはデータ層で処理する必要があるものであり、アプリケーションで処理する必要があることは非常に悪いことです
  • 集約クエリ(COUNTSUMなど)も、「複雑」から「ほとんど不可能」までさまざまです。開発者に、すべてのカテゴリのリストと、そのカテゴリの取引数を取得する方法を尋ねます。適切な設計では、これはSQLの4行です。
  • 更新ははるかに難しくなります(つまり、5つのカテゴリに分類される取引がありますが、2つを削除し、他の3つを追加する必要があります)。これは、適切に設計された3行のSQLです。
  • 最終的には、VARCHARリストの長さの制限に遭遇します。カンマで区切られたリストが4000文字を超える場合は、とにかく、そのモンスターが地獄のように遅くなる可能性があります。
  • リストをデータベースから引き出して分割し、別のクエリでデータベースに戻るのは、本質的に1つのクエリよりも時間がかかります。

TLDR:これは根本的に欠陥のある設計であり、適切にスケーリングされず、最も単純なクエリでさえも複雑さが増し、すぐに使用できるとアプリケーションの速度が低下します。

49
Simon Righarts

ただし、私たちのアウトソーシングチームは、次のように複数のカテゴリをDealテーブルに格納しました。

DealId(PK)DealCategory-ここでは、18,25,32のように、コンマで区切られた複数の取引IDを格納します。

onlyが特定の取引のカテゴリを照会する必要がある場合、これは実際に良い設計です。

しかし、特定のカテゴリのすべての取引を知りたい場合、それは恐ろしいことです。

また、更新、カウント、結合など、他のことを実行することは非常に困難でエラーが発生しやすくなります。

非正規化はその場所にありますが、同じデータに対して他のすべてのクエリを犠牲にして、あるタイプのクエリに対して最適化することを覚えておく必要があります。常に1つのパターンでクエリを実行することがわかっている場合は、非正規化された設計を使用すると有利な場合があります。ただし、クエリの種類にもっと柔軟性が必要になる可能性がある場合は、正規化された設計を使用してください。

他の形式の最適化と同様に、非正規化が正当化されるかどうかを決定する前に、実行するクエリを知る必要があります。

4
Bill Karwin

列の複数の値は、第1正規形に反しています。

また、テーブルはデータベース内でリンクされるため、速度はまったく向上しません。最初に文字列を読み取って解析し、次に「取引」のすべてのカテゴリを選択する必要があります。

正しい実装は、DealIdとDealCategoryIdを持つ「DealDealCategories」のような接合テーブルです。

悪い階層の実装?

また、DealCategoriesから別のDealCategoryへのFKは、DealCategoriesの階層/ツリーの不適切な実装のように見えます。親ID(いわゆる隣接リスト)の関係を使用してツリーを操作するのは面倒です。

階層を実装するときは、ネストされたセット(読みやすいが変更は難しい)とクロージャテーブル(全体的なパフォーマンスは最高ですが、メモリ使用量が多すぎる可能性があります-DealCategoriesには多すぎません)を確認してください!

1
Erik Hart