web-dev-qa-db-ja.com

ユーザー定義フィールド用のデータベースを設計する方法は?

私の要件は次のとおりです。

  • 任意のデータ型のユーザー定義フィールドを動的に追加できる必要がある
  • UDFをすばやく照会できる必要がある
  • データ型に基づいてUDFで計算を行える必要がある
  • データ型に基づいてUDFをソートできる必要がある

その他の情報:

  • 私は主にパフォーマンスを探しています
  • UDFデータを添付できる数百万のマスターレコードがあります
  • 私が最後にチェックしたとき、現在のデータベースには50mil以上のUDFレコードがありました
  • ほとんどの場合、UDFはすべてのレコードではなく、数千のマスターレコードにのみ添付されます。
  • UDFは結合されず、キーとして使用されません。クエリまたはレポートに使用される単なるデータです

オプション:

  1. StringValue1、StringValue2 ... IntValue1、IntValue2、...などで大きなテーブルを作成します。このアイデアは嫌いですが、誰かが他のアイデアよりも優れている理由を教えてくれればそれを考慮します。

  2. 必要に応じて新しい列をオンデマンドで追加する動的テーブルを作成します。また、すべての列のインデックスを作成しないとパフォーマンスが低下すると感じているため、このアイデアも好きではありません。

  3. UDFName、UDFDataType、およびValueを含む単一のテーブルを作成します。新しいUDFが追加されたら、そのデータだけをプルし、指定されたタイプに解析するビューを生成します。解析基準を満たさないアイテムはNULLを返します。

  4. データ型ごとに1つずつ、複数のUDFテーブルを作成します。したがって、UDFStrings、UDFDatesなどのテーブルがあります。おそらく、#2と同じことを行い、新しいフィールドが追加されるたびにビューを自動生成します。

  5. XMLデータ型?私はこれらを使ったことがありませんが、言及されているのを見ました。特にパフォーマンスに関して、私が望む結果が得られるかどうかはわかりません。

  6. 他に何か?

136
Rachel

パフォーマンスが主な関心事である場合は、#6 ... UDFごとのテーブル(実際、これは#2のバリアントです)に進みます。この回答は、この状況とデータの分布とアクセスパターンの説明に合わせて特別に調整されています。

長所:

  1. 一部のUDFにはデータセット全体の小さな部分の値があることを示すため、個別のテーブルはUDFをサポートするために必要なサイズだけになるため、最高のパフォーマンスが得られます。同じことが関連するインデックスにも当てはまります。

  2. また、集計またはその他の変換のために処理する必要のあるデータの量を制限することにより、速度が向上します。データを複数のテーブルに分割することにより、UDFデータの集計およびその他の統計分析の一部を実行し、その結果を外部キーを介してマスターテーブルに結合して、非集計属性を取得できます。

  3. データが実際に何であるかを反映するテーブル/列名を使用できます。

  4. データ型、チェック制約、デフォルト値などを使用して、データドメインを定義する完全な制御があります。オンザフライのデータ型変換によるパフォーマンスの低下を過小評価しないでください。このような制約は、RDBMSクエリオプティマイザーがより効果的な計画を作成するのにも役立ちます。

  5. 外部キーを使用する必要がある場合、組み込みの宣言参照整合性は、トリガーベースまたはアプリケーションレベルの制約の適用によってほとんど実行されません。

短所:

  1. これにより、多くのテーブルが作成される可能性があります。スキーマの分離や命名規則を強制すると、これが軽減されます。

  2. UDFの定義と管理を操作するには、さらに多くのアプリケーションコードが必要です。これは、元のオプション1、3、4よりも必要なコードがまだ少ないと思います。

その他の考慮事項:

  1. UDFをグループ化するのに意味のあるデータの性質について何かがあれば、それを奨励する必要があります。そうすれば、これらのデータ要素を単一のテーブルに結合できます。たとえば、色、サイズ、およびコストのUDFがあるとします。データの傾向は、このデータのほとんどのインスタンスが次のように見えることです

     'red', 'large', 45.03 
    

    のではなく

     NULL, 'medium', NULL
    

    このような場合、NULLになる値はほとんどないため、3つの列を1つのテーブルに結合することで顕著な速度のペナルティは発生しません。 。

  2. 使用率が高く頻繁に使用されるUDFからパフォーマンスの壁にぶつかった場合は、マスターテーブルに含めることを検討する必要があります。

  3. 論理テーブルの設計により、特定のポイントに到達できますが、レコード数が非常に大きくなった場合は、選択したRDBMSによって提供されるテーブルパーティションオプションを確認する必要もあります。

47
Phil Helmer

私は 書かれた この問題について たくさん を持っています。最も一般的な解決策は、Entity-Attribute-Valueアンチパターンです。これは、オプション#3で説明したものに似ています。 ペストのようなこのデザインは避けてください

本当に動的なカスタムフィールドが必要なときにこのソリューションに使用するのは、それらをXMLのblobに格納することです。そのため、いつでも新しいフィールドを追加できます。ただし、高速化するために、検索または並べ替える必要がある各フィールドに追加のテーブルも作成します(フィールドごとにテーブルを作成するのではなく、searchableフィールド)。これは、逆索引設計とも呼ばれます。

このソリューションに関する2009年の興味深い記事をここで読むことができます。 http://backchannel.org/blog/friendfeed-schemaless-mysql

または、ドキュメントごとにカスタムフィールドがあることが予想されるドキュメント指向データベースを使用できます。 Solr を選択します。

22
Bill Karwin

ほとんどの場合、次の構造のテーブルを作成します。

  • varchar名
  • varcharタイプ
  • 10進数
  • varchar StringValue
  • 日付DateValue

もちろん、正確なタイプはニーズに応じて異なります(もちろん、使用しているdbmsにも依存します)。また、intおよびbooleanにNumberValue(10進数)フィールドを使用することもできます。他のタイプも必要になる場合があります。

値を所有するマスターレコードへのリンクが必要です。マスターテーブルごとにユーザーフィールドテーブルを作成し、単純な外部キーを追加するのがおそらく最も簡単で高速です。これにより、ユーザーフィールドごとにマスターレコードを簡単かつ迅速にフィルタリングできます。

何らかのメタデータ情報が必要な場合があります。したがって、次のようになります。

テーブルUdfMetaData

  • int id
  • varchar名
  • varcharタイプ

テーブルMasterUdfValues

  • int Master_FK
  • int MetaData_FK
  • 10進数
  • varchar StringValue
  • 日付DateValue

あなたが何をするにしても、私はnotテーブル構造を動的に変更します。メンテナンスの悪夢です。私もnot XML構造を使用します。非常に遅いです。

9

これは、MongoDBやCouchDBのような非リレーショナルソリューションによってよりよく解決される問題のように聞こえます。

どちらも動的なスキーマの拡張を可能にすると同時に、求めるタプルの整合性を維持できます。

私はビル・カーウィンに同意します、EAVモデルはあなたにとって高性能なアプローチではありません。リレーショナルシステムで名前と値のペアを使用することは本質的に悪いことではありませんが、名前と値のペアが完全なタプルの情報を作成する場合にのみ有効です。それを使用すると、実行時にテーブルを動的に再構築する必要があり、あらゆる種類のものが困難になり始めます。クエリは、ピボットメンテナンスの練習になります。または、タプルの再構築をオブジェクトレイヤーにプッシュすることを強制します。

オブジェクトレイヤーにスキーマルールを埋め込むことなく、null値または欠損値が有効なエントリであるか、エントリがないかを判断することはできません。

スキーマを効率的に管理する機能が失われます。 100文字のvarcharは「値」フィールドの正しいタイプですか? 200文字?代わりにnvarcharにする必要がありますか?それは難しいトレードオフになる可能性があり、セットの動的な性質に人為的な制限を課す必要があるということで終わります。 「ユーザー定義フィールドはx個しか持てず、各フィールドの長さはy文字のみです。

MongoDBやCouchDBなどのドキュメント指向ソリューションを使用すると、単一のTuple内でユーザーに関連付けられたすべての属性を維持できます。結合は問題ではないため、誇大宣伝にもかかわらず、これら2つのどちらも結合ではうまくいかないため、人生は幸せです。ユーザーは、約4MBに達するまで管理が難しくならない長さで、必要な数の属性を定義できます(または許可します)。

ACIDレベルの整合性を必要とするデータがある場合は、ソリューションを分割し、高信頼性データをリレーショナルデータベースに格納し、動的データを非リレーショナルストアに格納することを検討できます。

8
Data Monk

カスタム列を追加するユーザーを提供した場合でも、それらの列でのクエリが適切に実行されるとは限りません。クエリの設計には、パフォーマンスを向上させる多くの側面がありますが、その中で最も重要なのは、まず何を保存するかを適切に指定することです。したがって、基本的に、ユーザーが仕様を考慮せずにスキーマを作成し、そのスキーマから情報を迅速に導出できるようにすることを望んでいますか?その場合、特にユーザーがデータの数値分析を行えるようにしたい場合、そのようなソリューションがうまくスケールすることは非常に困難です。

オプション1

IMOこのアプローチでは、スキーマの意味がわからないスキーマが得られます。これは、災害のレシピであり、レポート設計者にとって悪夢です。つまり、どの列にどのデータが格納されているかを知るにはメタデータが必要です。そのメタデータがめちゃくちゃになると、データを使い果たす可能性があります。さらに、間違ったデータを間違った列に入力しやすくなります。 (「何?String1には修道院の名前が含まれていますか?シャリーシーンのお気に入りのドラッグだと思いました。」)

オプション3、4、5

IMO、要件2、3、および4は、EAVのあらゆるバリエーションを排除します。このデータを照会、ソート、または計算する必要がある場合、EAVはクトゥルフの夢であり、開発チームとDBAの悪夢です。 EAVはパフォーマンスの点でボトルネックを作成し、必要な情報にすばやく到達するために必要なデータの整合性を提供しません。クエリはすぐにクロス集計のゴーディアンノットに変わります。

オプション2,6

これには本当に1つの選択肢があります。仕様を収集してから、スキーマを構築します。

クライアントが保存したいデータで最高のパフォーマンスが必要な場合は、開発者と協力してニーズを理解し、可能な限り効率的に保存されるようにする必要があります。テーブルのスキーマに基づいてフォームを動的に構築するコードを使用して、他のテーブルとは別のテーブルに格納することもできます。列の拡張プロパティを許可するデータベースがある場合、フォームビルダーがニースラベルやツールチップなどを使用できるようにするために、スキーマを追加するだけで済むようにすることもできます。いずれにしても、レポートを効率的に作成して実行するには、データを適切に保存する必要があります。問題のデータに多数のヌルがある場合、一部のデータベースにはそのタイプの情報を保存する機能があります。たとえば、SQL Server 2008には、多数のNULLを持つデータ専用のスパース列と呼ばれる機能があります。

これが分析、フィルタリング、またはソートが行われないデータのバッグにすぎない場合、EAVのいくつかのバリエーションがそのトリックを行うかもしれないと思います。ただし、要件を考えると、これらの新しい列を別々のテーブルに格納し、それらのテーブルからフォームを動的に構築する場合でも、最も効率的なソリューションは適切な仕様を取得することです。

スパース列

6
Thomas

これは問題のある状況であり、どのソリューションも「正しい」ようには見えません。ただし、オプション1は、おそらく単純さとパフォーマンスの両方の点で最適です。

これは、一部の商用エンタープライズアプリケーションで使用されるソリューションでもあります。

[〜#〜] edit [〜#〜]

現在利用できるが、質問が最初に尋ねられたときに存在しなかった(または少なくとも成熟していない)別のオプションは、DBのjsonフィールドを使用することです。

多くのリレーショナルDBは、jsonベースのフィールド(動的なサブフィールドのリストを含むことができる)をサポートし、それらのクエリを許可します。

postgress

mysql

4
Ophir Yoktan
  1. データ型ごとに1つずつ、複数のUDFテーブルを作成します。したがって、UDFStrings、UDFDatesなどのテーブルがあります。おそらく、#2と同じことを行い、新しいフィールドが追加されるたびにビューを自動生成します。

私の調査によると、データ型に基づいた複数のテーブルはパフォーマンスに役立ちません。特に、50 + UDFを含む20Kまたは25Kレコードのようなバルクデータがある場合。パフォーマンスは最悪でした。

次のような複数の列を持つ単一のテーブルを使用する必要があります。

varchar Name
varchar Type
decimal NumberValue
varchar StringValue
date DateValue
4
Amit Contractor

私は経験がありますか、1、3、および4で、それらはすべて面倒なものになりますが、データが何であるかが明確ではないか、データを動的なタイプのレコードに分割するための何らかのソフト分類で本当に複雑です。

XMLを試してみたいと思いますが、XMLの内容に対してスキーマを適用して、データの入力などを確認できます。これにより、UDFデータの異なるセットを保持できます。 SQL Serverの新しいバージョンでは、XMLフィールドでインデックスを作成できます。これにより、パフォーマンスが向上します。 ( http://blogs.technet.com/b/josebda/archive/2009/03/23/sql-server-2008-xml-indexing.aspx を参照)

2
Jon Egerton

SQL Serverを使用している場合、sqlvariant型を見落とさないでください。それは非常に高速であり、あなたの仕事をする必要があります。他のデータベースにも同様のものがあります。

XMLデータ型は、パフォーマンス上の理由からあまり良くありません。サーバー上で計算を行う場合、これらを常にデシリアライズする必要があります。

オプション1は音が悪く、汚いように見えますが、パフォーマンスの面では最善の方法です。パフォーマンスに勝るものがないため、Field00-Field99という名前の列を持つテーブルを作成しました。 INSERTのパフォーマンスも考慮する必要がある場合があります。この場合、これも目的です。きれいに見せたい場合は、いつでもこのテーブルにビューを作成できます!

2
Tim Rogers

SharePointはオプション1を使用し、適切なパフォーマンスを備えています。

1
Nathan DeWitt

私はこれまでこれらのオプションを使用せずにこれを非常にうまく管理しました(オプション6?:))。

ユーザーが使用するモデルを作成し(xmlとして保存し、カスタムモデリングツールを介して公開)、モデルから生成されたテーブルとビューから、ベーステーブルをユーザー定義のデータテーブルに結合します。したがって、各タイプには、コアデータを含むベーステーブルと、ユーザー定義フィールドを含むユーザーテーブルがあります。

例としてドキュメントを取り上げます。典型的なフィールドは、名前、タイプ、日付、著者などです。これはコアテーブルに含まれます。次に、ユーザーは、contract_end_date、renewal_clause、blah blah blahなど、独自のフィールドを使用して独自の特別な文書タイプを定義します。そのユーザー定義ドキュメントには、共通のプライマリキーで結合されたコアドキュメントテーブルxcontractテーブルがあります(したがって、xcontractsプライマリキーはコアテーブルのプライマリキーでも外部にあります)。次に、これら2つのテーブルをラップするビューを生成します。クエリが高速だったときのパフォーマンス。追加のビジネスルールをビューに埋め込むこともできます。これは私にとって本当にうまくいきました。

1
Kell

#4をお勧めします。このタイプのシステムはMagentoで使用されているためです。これは、高度に認定されたeコマースCMSプラットフォームです。 fieldIdlabel列を使用して、単一のテーブルを使用してカスタムフィールドを定義します。次に、各データ型に個別のテーブルを作成し、それらの各テーブル内にfieldIdおよびデータ型value列でインデックスを作成するインデックスを作成します。次に、クエリで次のようなものを使用します。

SELECT *
FROM FieldValues_Text
WHERE fieldId IN (
    SELECT fieldId FROM Fields WHERE userId=@userId
)
AND value LIKE '%' + @search + '%'

私の意見では、これにより、ユーザー定義型に対して可能な限り最高のパフォーマンスが保証されます。

私の経験では、月に数百万人のユーザーにサービスを提供し、カスタム製品属性を持つ数千の製品をホストし、データベースがレポート用であってもワークロードを簡単に処理する複数のMagento Webサイトに取り組んできました。

レポートの場合、PIVOTを使用して、フィールドテーブルラベル値を列名に変換し、クエリ結果を各データ型テーブルからピボットされたものに変換できます。列。

0
Mark Entingh

コメントで、UDFフィールドは、ユーザーによって適切にマッピングされていないインポートされたデータをダンプすることだと言っていました。

おそらく別のオプションは、各ユーザーが作成したUDFの数を追跡し、6(または他の同様にランダムな制限)のカスタムフィールドトップを使用できると言ってフィールドを再利用することです。

このようなデータベース構造の問題に直面した場合、多くの場合、アプリケーションの基本設計(ケースではインポートシステム)に戻って、さらに制限を加えるのが最善です。

今私がすることは、ユーザーへのリンクを追加したオプション4(編集)です:

general_data_table
id
...


udfs_linked_table
id
general_data_id
udf_id


udfs_table
id
name
type
owner_id --> Use this to filter for the current user and limit their UDFs
string_link_id --> link table for string fields
int_link_id
type_link_id

次に、パフォーマンスを最適化し、インデックスを正しくするためのビューを作成してください。このレベルの正規化により、DBフットプリントは小さくなりますが、アプリケーションはより複雑になります。

0
Wouter Simons

私たちのデータベースは、ユーザーが7kを超える「カスタムフィールド」を持つアプリ(ヘルプデスクソフトウェア)SaaS)を強化しています。

  1. (EntityID, FieldID, Value)テーブルのsearchingデータの
  2. entitiesテーブルのJSONフィールド。すべてのエンティティ値を保持し、データの表示に使用されます。 (この方法では、値を取得するために100万回のJOINを必要としません)。

さらに#1を分割して、 this answer が示唆するような「データ型ごとのテーブル」を持つようにすることもできます。これにより、UDFにインデックスを付けることもできます。

追伸「Entity-Attribute-Value」アプローチを擁護するいくつかの言葉は、誰もがバッシングを続けています。何十年もの間、#2なしで#1を使用してきましたが、うまく機能しました。時にはそれはビジネス上の決定です。アプリを書き直してデータベースを再設計する時間はありますか、それともクラウドサーバーで数ドルを費やすことができますか?ちなみに、#1アプローチを使用していたとき、私たちのDBは何十万人ものユーザーがアクセスする何百万ものエンティティを保持しており、16 GBのデュアルコアdbサーバーはうまく機能していました(AWSの「r3」vm) 。

0
Alex