SQL Serverデータベースで単一行構成テーブルを使用する。悪いアイデア?


145

ショッピングカートアプリケーションを開発する際に、管理者の好みと要件に基づいて設定と構成を保存する必要があることがわかりました。この情報には、会社情報、配送アカウントID、PayPal APIキー、通知設定などが含まれます。

リレーショナルデータベースシステムで単一の行を格納するテーブルを作成することは非常に不適切と思われます。

この情報を保存する適切な方法は何ですか?

注:私のDBMSはSQL Server 2008で、プログラミングレイヤーはASP.NET(C#)で実装されています。

回答:


189

過去にこれを2つの方法で行いました-単一行テーブルとキー/値ペアテーブル-それぞれのアプローチには良い点と悪い点があります。

単列

  • 正:値は正しいタイプで保存されます
  • ポジティブ:(上記の理由により)コードで処理するのが簡単です
  • 正:デフォルト値を各設定に個別に与えることができます
  • 否定:新しい設定を追加するにはスキーマの変更が必要です
  • 負:設定が多い場合、テーブルが非常に広くなる可能性があります

キー/値ペア

  • ポジティブ:新しい設定を追加するためにスキーマを変更する必要がない
  • ポジティブ:テーブルスキーマは狭く、新しい設定に追加の行が使用されます
  • 負:各設定のデフォルト値は同じ(null / empty?)
  • 否定:すべてを文字列として保存する必要があります(つまり、nvarchar)
  • ネガティブ:コードで設定を処理するときは、設定のタイプを知ってキャストする必要があります

単一行オプションは、操作するのがはるかに簡単です。これは、各設定をデータベースの正しいタイプで保存でき、設定のタイプとルックアップキーをコードに保存する必要がないためです。

このアプローチを使用する際に心配したことの1つは、「特別な」単一行設定テーブルに複数の行があることでした。(SQL Serverで)私はこれを克服しました:

  • デフォルト値0の新しいビット列を追加する
  • この列の値が0であることを確認するためのチェック制約を作成する
  • ビット列に一意の制約を作成する

これは、ビット列の値が0でなければならないため、テーブルに存在できる行は1つだけですが、一意の制約のため、その値を持つ行は1つしか存在できないことを意味します。


5
LOBアプリケーションで単一行の処理を行います。値はすべて正しいタイプであり、アプリケーションでの使用がはるかに簡単になります。私たちのスキーマはアプリケーションと一緒にバージョン管理されているので、構成セットアップへの変更は、アプリケーションのリビジョンと同じように管理されます。
DaveE 2010

17
単一行が正:一部の列でFKを定義できます!
wqw

8
タイプ識別子を使用してキーと値のペアを実行し、どの列に値タイプの値があるかを判別できます。これにより、両方の長所が得られ、ストアドプロシージャを使用して、必要なときに値を取得できます。
ミドルトーン

19
単一行ソリューションを実装した後、1日が本当に台無しになる可能性があるのは、後で「各値が最後に変更された時刻と変更者を追跡してみましょう...」というタスクを課されたときです
Dave Mateer

6
1つのケースで発見した単一行ソリューションのもう1つの利点は、「設定」用の単一行テーブルを備えた1つのクライアント用にアプリケーションを構築したことです。同じアプリケーションを使用したいが別の設定が必要な他の2つのクライアントを後で取得しました。クライアントごとに個別の設定セットを維持するために、「client_id」PKをテーブルに追加するだけで済みました。(これは、これらの "設定"が実際にはまだモデル化していない上位レベルのエンティティの単なる属性であることを理解したときです。)
Jeffrey Kemp

10

(少なくとも)情報タイプと情報値の列を持つテーブルを作成する必要があります。これにより、新しい情報が追加されるたびに新しい列を作成する必要がなくなります。


1
シンプルで端正です。そこからキーと値のペアのリストを操作するだけです。デフォルト値について少し考えることをお勧めします。これは、使用のコンテキストによって異なります...
Paul Kohler

4
新しい列を作成することが問題になるのはなぜですか?SQLスキーマの更新に関する政治的な問題のために、開発者が回避しなければならない状況があることは知っていますが、質問にはそれについての言及はありません。
2010

6

1つの行で問題なく動作します。それは強い型さえ持っています:

show_borders    bit
admin_name      varchar(50)
max_users       int

1つの欠点はalter table、新しい設定を追加するためにスキーマの変更()が必要になることです。代替案の1つは、次のようなテーブルになる正規化です。

pref_name       varchar(50) primary key
pref_value      varchar(50) 

これには弱い型(すべてがvarchar)がありますが、新しい設定を追加することは、単に行を追加することであり、データベースの書き込みアクセスだけで実行できるものです。


4

個人的には、それが機能する場合は1行に格納します。それをSQLテーブルに格納するのはやり過ぎですか?おそらく、そうすることで実際に害はありません。


4

ご想像のとおり、最も単純な状況を除いて、すべての構成パラメーターを単一の行に配置することには多くの欠点があります。それは悪い考えです...

構成やユーザー設定タイプの情報を格納する便利な方法はXMLです。多くのDBMSがXMLデータ型をサポートしています。XML構文を使用すると、構成が進化するにつれて、構成を記述する「言語」と構造を拡張できます。XMLの利点の1つは、階層構造を暗黙的にサポートすることです。たとえば、番号付きのサフィックスを使用して名前を付けなくても、構成パラメーターの小さなリストを格納できます。XML形式の考えられる欠点は、このデータの検索と一般的な変更が他のアプローチほど単純ではないことです(複雑ではありませんが、単純/自然ではありません)。

リレーショナルモデル近いままにする場合は、Entity-Attribute-Valueモデルがおそらく必要です。これにより、個々の値は通常次のようなテーブルに格納されます。

EntityId     (foreign key to the "owner" of this attribute)
AttributeId  (foreign key to the "metadata" table where the attribute is defined)
StringValue  (it is often convenient to have different columns of different types
IntValue      allowing to store the various attributes in a format that befits 
              them)

これにより、AttributeIdは、可能な各属性(ここでは「構成パラメータ」)が定義されているテーブルへの外部キーです。

AttributeId  (Primary Key)
Name
AttributeType     (some code  S = string, I = Int etc.)
Required          (some boolean indicating that this is required)
Some_other_fields   (for example to define in which order these attributes get displayed etc...)

最後にEntityIdを使用すると、これらのさまざまな属性を「所有」するエンティティを特定できます。あなたの場合、管理する構成が1つしかない場合、それはUserIdである可能性があります。

EAVモデルは、アプリケーションの進化に伴って可能な構成パラメーターのリストを拡張できるようにするほかに、「メタデータ」、つまり属性自体に関連するデータをデータテーブルに配置するため、一般的に見られる列名のすべてのハードコーディングを回避できます構成パラメーターが単一行に格納されている場合。


3
構成テーブルのほとんどの用途にとってはやり過ぎのように思えます。
JerryOL

このアプローチの背後にある一般的な考え方は素晴らしいと思います。しかし、なぜXMLなのでしょうか。JSONやYAMLなどの単純なデータ交換形式を選択するだけで、他の両方のバリエーションの利点を活用できます。
シュラマー2013

1
EAVはリレーショナルですが、正規化されていません。確かにそれの使用例があります(たとえば、ORMシステムはそれらを気に入っているようです)が、メタデータがEAVのデータベースにあるという主張は、それを使用する説得力のある理由ではありません。すべてのRDBMSのシステムテーブルにはメタデータが含まれているため、発見できるため、単一行のテーブルにもデータベース内にメタデータがあります。ハードコードされた列名も問題ではありません。エンティティと属性にキーを使用する場合、ハードコーディングされたルックアップテーブルがどこかで定義されています(または、さらに悪いことに、プレゼンテーションレイヤーにあります)。
ダボス

3

正規化されたアプローチで新しい構成パラメーターを追加するときにスキーマを変更する必要はありませんが、おそらく新しい値を処理するようにコードを変更しています。

デプロイメントに「代替テーブル」を追加することは、単一行アプローチの単純さとタイプセーフティのトレードオフのように大きくは思えません。


2

キーと値のペアは、構成設定を保存できる.Net App.Configに似ています。

したがって、値を取得する場合は、次のようにします。

SELECT value FROM configurationTable
WHERE ApplicationGroup = 'myappgroup'
AND keyDescription = 'myKey';

1

これを行う一般的な方法は、プロパティファイルに似た「プロパティ」テーブルを用意することです。ここには、すべてのアプリの定数を格納するか、必要なだけの定数ではないものを格納できます。

その後、必要に応じてこのテーブルから情報を取得できます。同様に、他に保存する設定があることがわかった場合は、それを追加できます。次に例を示します。

property_entry_table

[id, scope, refId, propertyName, propertyValue, propertyType] 
1, 0, 1, "COMPANY_INFO", "Acme Tools", "ADMIN"  
2, 0, 1, "SHIPPING_ID", "12333484", "ADMIN"  
3, 0, 1, "PAYPAL_KEY", "2143123412341", "ADMIN"   
4, 0, 1, "PAYPAL_KEY", "123412341234123", "ADMIN"  
5, 0, 1, "NOTIF_PREF", "ON", "ADMIN"  
6, 0, 2, "NOTIF_PREF", "OFF", "ADMIN"   

このようにして、あなたが持っているデータと、来年持ってまだ知らないデータを保存することができます:)。

この例では、スコープとrefIdをバックエンドで必要なものに使用できます。したがって、propertyType "ADMIN"にスコープ0 refId 2がある場合、それがどの設定であるかがわかります。

プロパティタイプは、いつかここにも管理者以外の情報を格納する必要がある場合に役立ちます。

このようにカートデータを格納したり、そのためにルックアップを保存したりしないでください。ただし、データがシステム固有の場合は、この方法を使用できます。

例:DATABASE_VERSIONを保存する場合は、次のようなテーブルを使用します。そうすることで、アプリをアップグレードする必要があるときに、プロパティテーブルをチェックして、クライアントにあるソフトウェアのバージョンを確認できます。

ポイントは、カートに関連するものにこれを使用しないことです。明確に定義されたリレーショナルテーブルでビジネスロジックを維持します。プロパティテーブルはシステム情報専用です。


@finnwこの方法は、特にさまざまな種類のルックアップが多数ある場合に、ルックアップに使用しないでください。おそらく私はその質問を誤解しました。定数とシステムプロパティのテーブルが必要なようです。その場合、なぜ10個の異なるテーブルがあるのですか?
ステファノ

注:「リレーショナルカートのデータを保存する必要がある」ではなく、「設定と構成を保存する」と述べた
Stephano

これに対する私の反対は、新しい属性を追加するときにSQLスキーマの更新を回避するために、SQLの型指定やその他の制約メカニズムをバイパスしていることです。あなたが言うように「来年あなたが持っているであろうそしてまだ知らないデータ」。はい、来年は新しいデータがありますが、追加されたときに、新しい(型付き)SQL列、CHECK、そして場合によってはFOREIGN KEY制約を作成するのを止めるにはどうすればよいでしょうか。
finnw

私の最初の本能は、このデータを単純にフラットファイルに追加することです。正解です。代わりにテーブルを使用するこのプロセスは、DBMSの制約メカニズムを実際に回避します。ただし、適切なデータベース手法を厳守しようとする場合、要点を見逃していると思います。最初の答えを確認してください。最高はSOに投票: stackoverflow.com/questions/406760/...
Stephano

2
私はキーと値のペアに行き、起動時にすべてを辞書にダンプし、ソートします。
Paul Creasey

0

単一の行が構成に最適な実装であるかどうかはわかりません。2つの列(configName、configValue)を持つ構成アイテムごとに1行を持つことをお勧めしますが、これにはすべての値を文字列にキャストして戻す必要があります。

いずれにしても、グローバル設定に単一の行を使用しても害はありません。それをDBに格納するためのその他のオプション(グローバル変数)のほうが適しています。最初の構成行を挿入し、テーブルの挿入を無効にして複数の行を防ぐことで、それを制御できます。


0

主要なタイプごとに1つの列と、データがどの列にあるかを示す1つの列を追加することにより、変換なしでキーと値のペアを実行できます。

したがって、テーブルは次のようになります。

id, column_num, property_name, intValue, floatValue, charValue, dateValue
1, 1, weeks, 51, , ,
2, 2, pi, , 3.14159, , 
3, 4, FiscYearEnd, , , , 1/31/2015
4, 3, CompanyName, , , ACME, 

それはもう少し多くの部屋を使用しますが、多くても数十の属性を使用しています。column_num値のcaseステートメントを使用して、正しいフィールドをプル/結合できます。


0

すみません、そうです、後で。とにかく、私がすることはシンプルで効果的です。3つの()列を持つテーブルを作成するだけです。

ID-整数(11)

名前-varchar(64)

値-テキスト

新しい構成列を作成、更新、または読み取る前に行うことは、「値」をシリアル化することです。このように私はタイプを確信しています(まあ、phpは:)です)

例えば:

b:0; B OOLEAN 用です(false

b:1; B OOLEAN 用です(true

i:1988; 私は NT 用です

s:5: "Kader"; 5文字の長さのS TRING 用です

これが役に立てば幸いです:)


1
タイプの新しい列を作成しないのはなぜですか?i:19882つの情報を1つの列にまとめようとしているようです。
maksymiuk 2016

@maksymiuk単純に、シリアル化されていない場合、(ifまたはswitch)の後にループを使用する代わりに正確な型を取得するため... etc
Kader Bouyakoub

ループやスイッチなどは必要ありません。実際には、各行から情報を解析する必要のある手順が削除されますが、型に追加の列がある場合、型情報はコンポーネントが情報を取得してすでに利用できます。最初のクエリ以外の手順を実行する必要はありません
maksymiuk

echo (int) $var整数や他の型のために何かをすることによってあなたの意味?
Kader Bouyakoub 2016年

0

キー列をvarcharとし、値列をJSONとします。1は数値"1"ですが、文字列です。truefalseブール値です。オブジェクトを持つこともできます。

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