列挙型をDBに保存するのはなぜですか?


69

このように、DBに列挙型を保存する方法についてアドバイスを求める質問を数多く目にしました。しかし、なぜそうするのだろう。したがってPersongenderフィールドとGender列挙型を持つエンティティがあるとしましょう。次に、個人テーブルには列の性別があります。

正当性を強制する明白な理由に加えてgender、アプリケーションに既にあるものをマッピングするために余分なテーブルを作成する理由がわかりません。そして、私はその複製を持つことが本当に好きではありません。



1
定期的に変更される可能性のあるデータを他にどこに保存しますか?誰かが一緒に来て新しいオプションを追加したい場合は、すべてのオプションについて考えているかもしれません。そのハードコードされたリストを調整する準備はできていますか?誰かが性別を男性または女性以外のもの、例えばインターセックスなどにしたいと思うかもしれません。
JBキング

4
@JBKing ... Facebookの性別リストをご覧ください。


3
顧客が「だまされたTumblrite」である場合、少なくともビジネスに留まるつもりなら、顧客のニーズを満たす何かを作成できるデータベーススキーマを作成するのは間違いありません。
ロボットを取得

回答:


74

構想や期待に満ちた別の例を見てみましょう。ここに列挙型がありますが、これはバグの優先順位のセットです。

データベースにはどのような値を保存していますか?

だから、私は保存することができ'C''H''M'、および'L'データベースインチ または'HIGH'など。これには、文字列型データの問題があります。有効な値の既知のセットがあり、そのセットをデータベースに保存していない場合、作業が困難になる可能性があります。

なぜコードにデータを保存するのですか?

あなたは持っているList<String> priorities = {'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'};コードまたはその効果に何か。これは、このデータの適切な形式へのさまざまなマッピングがあることを意味します(すべてのキャップをデータベースに挿入していますが、それをとして表示していますCritical)。コードのローカライズも困難になりました。アイデアのデータベース表現を、コードに保存されている文字列にバインドしました。

このリストにアクセスする必要がある場所ならどこでも、コードの複製または定数の束を持つクラスが必要です。どちらも良い選択肢ではありません。また、このデータを使用する可能性のある他のアプリケーションがあることも忘れてはなりません(他の言語で記述されている可能性があります-Java Webアプリケーションには、Crystal Reportsレポートシステムとデータを供給するPerlバッチジョブがあります)。レポートエンジンは有効なデータのリストを知る必要があり('LOW'優先順位にマークが付けられていない場合、それがレポートの有効な優先順位であることを知る必要がある場合はどうなりますか)、バッチジョブには有効なデータに関する情報が含まれます値は。

仮に、「私たちは単一言語のショップ-すべてがJavaで書かれている」と言って、この情報を含む単一の.jarを持っているかもしれませんが、アプリケーションは互いに密接に結合され、データ。変更があるたびに、レポート部分とバッチ更新部分をWebアプリケーションと一緒にリリースする必要があります。すべての部分でそのリリースがスムーズに進むことを願っています。

上司が別の優先事項を望んでいる場合はどうなりますか?

あなたの上司が今日来ました。新しい優先度があります- CEO。次に、すべてのコードを変更して、再コンパイルと再デプロイを行う必要があります。

「enum-in-the-table」アプローチでは、列挙リストを更新して新しい優先順位を設定します。リストを取得するすべてのコードは、データベースからリストを取得します。

データは単独ではめったにありません

優先度を使用すると、データは、ワークフローに関する情報を含む可能性のある他のテーブルにキーを設定したり、この優先度やその他の設定を行うことができるユーザーを決定します。

:性別は、使用中の代名詞へのリンクがあります:ビットのための質問で述べたように、性別に戻ってhe/his/himshe/hers/her...あなたはコード自体にそのコーディングハード避けたいです。そしてあなたの上司がやって来て、あなたが'OTHER'性別を持っていることを追加する必要があります(シンプルに保つために)、あなたはこの性別を関連付ける必要がthey/their/themあります...そしてあなたの上司はFacebookが持っているものを見て...ええ、そうです。

列挙テーブルではなく、文字列型のデータに制限することで、データと他のビットとのこの関係を維持するために、他のテーブルにその文字列を複製する必要がありました。

他のデータストアはどうですか?

これをどこに保管しても、同じ原則が存在します。

  • priorities.prop優先順位のリストを持つファイルを作成できます。このリストは、プロパティファイルから読み取ります。
  • 次のエントリを持つドキュメントストアデータベース(CouchDBなど)を持つことができますenums(そしてJavaScriptで検証関数を記述します)。

    {
       "_id": "c18b0756c3c08d8fceb5bcddd60006f4",
       "_rev": "1-c89f76e36b740e9b899a4bffab44e1c2",
       "priorities": [ "critical", "high", "medium", "low" ],
       "severities": [ "blocker", "bad", "annoying", "cosmetic" ]
    }
    
  • 少しのスキーマを持つXMLファイルを作成できます。

    <xs:element name="priority" type="priorityType"/>
    
    <xs:simpleType name="priorityType">
      <xs:restriction base="xs:string">
        <xs:enumeration value="critical"/>
        <xs:enumeration value="high"/>
        <xs:enumeration value="medium"/>
        <xs:enumeration value="low"/>
      </xs:restriction>
    </xs:simpleType>
    

基本的な考え方は同じです。データストア自体は、有効な値のリストを保存して実施する必要がある場所です。ここに配置することで、コードとデータについて簡単に推論できます。chriticalデータストアから何が返されるかを知っているので、毎回持っているものを防御的にチェックすることを心配する必要はありません(大文字ですか?それとも小文字ですか?この列にタイプがあるのはなぜですか?など)データストアが送信することを期待しているものとまったく同じです。有効な値のリストをデータストアに照会できます。

持ち帰り

有効な値のセットは、コードではなくデータです。あなたはないために努力する必要がDRYコード-しかし、重複の問題は、あなたが複製されていることであるデータをコードではなく、データとしての地位を尊重し、データベースに格納します。

データストアに対して複数のアプリケーションを簡単に記述でき、コードをデータに結合していないため、データ自体に密接に結合されているすべてをデプロイする必要があるインスタンスを回避できます。

CEO優先度が追加されたときにアプリケーション全体を再テストする必要がないため、アプリケーションのテストが簡単になります。優先度の実際の値を考慮するコードがないためです。

互いに独立してコードとデータについて推論できるので、メンテナンスを行うときにバグを見つけて修正するのが簡単になります。


6
あなたが変更することなく、あなたのコードに列挙値を追加することができた場合は任意の(それはそれのローカライズされたディスプレイであることがないようにと)ロジックを、私は最初の場所で追加の列挙値の必要性を疑います。そして、私は問題を分析するために簡単なSQLクエリでデータベースバックアップを簡単にクエリする能力を評価するのに十分な年齢ですが、最近のORMでは、基礎となるデータベースをまったく見る必要なく非常にうまくいくことができます。ただし、ここではローカリゼーション(代名詞)についてのポイントは理解していません-そのようなものは確かにデータベースにあるべきではありませんが、何らかの種類のリソースファイルです。
Voo

1
@Voo the pronounsは、この列挙値に関連する他のデータの例です。データがテーブルにない場合、適切なFK制約なしに文字列型の値が存在する必要があります。リソースファイルに代名詞(このようなもの)がある場合は、データベースとファイルの間にカップリングがあります(データベースを更新し、ファイルを再デプロイします)。再デプロイを行うことなく、オンザフライで管理インターフェースを介して変更可能なredmineの列挙を検討してください。

1
...データベースは多言語データストアであることも忘れないでください。1つの言語でORMの一部として検証を行う必要がある場合、使用する他の言語でその検証を複製する必要があります(最近、PythonがデータベースにデータをプッシュするJavaフロントエンドで作業しました) -Java ORMとPythonシステムは物事に同意する必要があります-そして、その同意(有効なタイプ)は、データベースに「enum」テーブルを強制させることで最も簡単に実装されました。)

2
@Vooの列挙型のRedmineの使用法はbugzillaと同じです。「最も重要なテーブルにはシステムのすべてのバグが含まれます。重大度や優先度などの列挙型の値を含むさまざまなバグプロパティで構成されます。」-自由形式のテキストフィールドではなく、この既知の列挙可能なセットの1つである値です。コンパイル時の列挙型ではありませんが、列挙型のままです。Mantisも参照してください。

1
確認するために-あなたのポイントは、人々は決してEnumを使用すべきではないということですか?はっきりしませんでした。
-niico

18

これらのうち、クエリを読むときに間違いを起こす可能性が高いと思うものはどれですか?

select * 
from Person 
where Gender = 1

または

select * 
from Person join Gender on Person.Gender = Gender.GenderId
where Gender.Label = "Female" 

SQLで列挙テーブルを作成するのは、後者の方が読みやすいため、SQLの作成と保守のエラーが少なくなるためです。

で性別を直接文字列にすることもできますが、Person大文字と小文字を区別して強制する必要があります。また、文字列と整数の違いにより、データベースの最適化の程度に応じて、テーブルのストレージヒットとクエリ時間を増やすことができます。


5
しかし、その後、テーブルを結合しています。エンティティに2つの列挙型がある場合、単純なクエリのために3つのテーブルを結合します。
user3748908

11
@ user3748908-そうですか?結合はDBが得意とするものであり、少なくともこのルートを選択した人々の目には、選択肢はさらに悪いものです。
Telastyn

8
@ user3748908:データベースは結合の実行が本当に優れているだけでなく、一貫性を強化することも非常に優れています。一貫性の強制は、あるテーブルの列を別のテーブルの識別行に向け、「この列の値はそのテーブルの識別子の1つでなければならない」と言えば、本当にうまくいきます。
Blrfl

2
これはすべて真実ですが、パフォーマンス上の理由で結合を犠牲にする必要がある多くの場合があります。誤解しないでください。私はこのタイプの設計と参加についてすべてを説明していますが、パフォーマンスのために参加が不要な場合があるとしたら、世界は終わらないと思います。
JonH

3
パフォーマンス上の理由で@JonHのために参照テーブルへの結合を削除する必要がある場合は、より大きなサーバーを購入するか、多数のサブクエリを介して述語をプッシュしようとするのをやめる必要があります(何をしているのか知っていると思います)。参照テーブルは、DBを起動してから数秒以内にキャッシュ内に存在する必要があるものです。
ベン

10

人々がまだこれについて言及していなかったとは信じられません。

外部キー

データベースに列挙を保持し、列挙値を含むテーブルに外部キーを追加することにより、コードがその列に誤った値を入力しないようにします。これはデータの整合性に役立ち、列挙型のテーブルが必要な最も明白な理由です。


質問はわずか5行の長さであり、「正確さを強制する明白な理由に加えて」と明確に述べています。だから、OPは明白だと述べており、彼は他の正当化を探しているので、誰もそれを言及していません-PS:あなたに同意します、それは十分な理由です。
user1007074

6

私はあなたに同意するキャンプにいます。コードにGender列挙を、データベースにtblGenderを保持すると、メンテナンス時に問題が発生する可能性があります。これらの2つのエンティティは同じ値を持つ必要があるため、一方に加えた変更はもう一方にも加えなければならないことを文書化する必要があります。

次に、次のように列挙値をストアドプロシージャに渡す必要があります。

create stored procedure InsertPerson @name varchar, @gender int
    insert into tblPeople (name, gender)
    values (@name, @gender)

ただし、これらの値をデータベーステーブルに保持した場合、どのように行うかを考えてください。

create stored procedure InsertPerson @name varchar, @genderName varchar
    insert into tblPeople (name, gender)
    select @name, fkGender
    from tblGender
    where genderName = @genderName --I hope these are the same

確かにリレーショナルデータベースは結合を念頭に置いて構築されていますが、どのクエリが読みやすいでしょうか?


別のクエリの例を次に示します。

create stored procedure SpGetGenderCounts
    select count(*) as count, gender
    from tblPeople
    group by gender

これと比較してください:

create stored procedure SpGetGenderCounts
    select count(*) as count, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender
    group by genderName --assuming no two genders have the same name

さらに別のクエリの例を示します。

create stored procedure GetAllPeople
    select name, gender
    from tblPeople

この例では、結果の性別セルをintからenumに変換する必要があることに注意してください。ただし、これらの変換は簡単です。これと比較してください:

create stored procedure GetAllPeople
    select name, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender

これらのクエリはすべて、データベースから列挙定義を保持するという考えに沿った場合、より小さく、より保守しやすくなります。


1
性別ではない場合はどうでしょう。私たちは性別がフィールドであることに夢中になりすぎていると思います。OPが「だから、優先度フィールドを持つエンティティバグがあるとしましょう」と言った場合-あなたの答えは変わりますか?

4
@MichaelT「優先度」の可能な値のリストは、少なくともデータの一部である限り、コードの一部です。さまざまな優先順位のグラフィカルアイコンが表示されますか?データベースから引き出されるとは思わないでしょうか?そして、そのようなものはテーマとスタイルを設定でき、DBに保存されている値の同じ範囲を表します。とにかくデータベースで変更することはできません。同期するプレゼンテーションコードがあります。
ユージンリャブツェフ

1

データ分析で使用できるという理由で、Gendersテーブルを作成します。データベース内のすべての男性または女性を検索して、レポートを生成できます。データを表示する方法が多ければ多いほど、トレンド情報を見つけやすくなります。明らかに、これは非常に単純な列挙ですが、複雑な列挙(世界の国や州など)の場合、特殊なレポートを簡単に生成できます。


1

最初に、データベースが1つのアプリケーションでのみ使用されるのか、または複数のアプリケーションがデータベースを使用する可能性があるのか​​を判断する必要があります。場合によっては、データベースはアプリケーションのファイル形式にすぎません(この点でSQLiteデータベースを使用できることがよくあります)。この場合、enum定義をテーブルとしてビット複製することは、多くの場合うまくいく可能性があり、より意味があります。

ただし、複数のアプリケーションがデータベースにアクセスする可能性を検討するとすぐに、enumのテーブルは非常に理にかなっています(他の答えはなぜより詳細になります)。考慮すべきもう1つのことは、あなたや他の開発者が生のデータベースデータを見ることです。その場合、これは別のアプリケーションの使用と見なすことができます(ラボゲージが生のSQLである場合のみ)。

コードで定義された列挙型(よりクリーンなコードとコンパイル時のチェック用)およびデータベース内のテーブルがある場合、ユニットテストを追加して、2つが同期していることを確認することをお勧めします。


1

コード内のビジネスロジックを駆動するために使用されるコード列挙がある場合でも、上/下で詳述する多くの理由で、DB内のデータを表すテーブルを作成する必要があります。DB値とコード値の同期を維持するためのヒントを次に示します。

  1. テーブルのIDフィールドをID列にしないでください。IDと説明をフィールドとして含めます。

  2. テーブルで何か別のことを行うと、開発者が値が半静的/コード列挙に結び付けられていることを知るのに役立ちます。他のすべてのルックアップテーブル(通常はユーザーが値を追加できる場所)には通常、LastChangedDateTimeとLastChangedByがありますが、enum関連のテーブルにこれらを持たないことは、開発者のみが変更できることを思い出すのに役立ちます。これを文書化します。

  3. 列挙内の各値が対応するテーブルにあり、それらの値のみが対応するテーブルにあることを確認する確認コードを作成します。ビルド後に実行する自動化されたアプリケーション「ヘルステスト」がある場合は、そこで実行します。そうでない場合は、アプリケーションがIDEで実行されるたびに、アプリケーションの起動時にコードが自動的に実行されるようにします。

  4. 本番環境を作成し、同じことを行いますが、DB内からSQLスクリプトを配信します。正しく作成されていれば、環境の移行にも役立ちます。


0

データにアクセスするユーザーにも依存します。アプリケーションが1つだけの場合は問題ないかもしれません。データウェアハウスまたはレポートシステムに追加する場合。彼らは、そのコードが何を意味するのか、人間がコードを変更できるバージョンは何であるのかを知る必要があります。

通常、型テーブルは、コード内で列挙型として複製されません。キャッシュされたリストにタイプテーブルをロードできます。

Class GenderList

   Public Shared Property UnfilteredList
   Public Shared Property Male = GetItem("M")
   Public Shared Property Female = GetItem("F")

End Class

多くの場合、タイプは行き来します。新しいタイプが追加された日付が必要になります。特定のタイプがいつ削除されたかを知る。必要な場合にのみ表示します。クライアントが性別として「トランスジェンダー」を望んでいるが、他のクライアントは望んでいない場合はどうなりますか?これらの情報はすべてデータベースに最適に保存されます。

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