web-dev-qa-db-ja.com

複数の異なる型である可能性のある値を格納する適切な方法

AnswersテーブルとQuestionsテーブルがあります。

Answersテーブルには値がありますが、質問によっては、この値はbitnvarchar、またはnumber(これまでのところ)の場合があります。 Questionには、その意図された回答値タイプがどうあるべきかという概念があります。

少なくとも数値を比較する必要があるため、これらのAnswer値を解析することが重要になります。

もう少しコンテキストについては、質問と潜在的な回答(通常、テキストボックスタイプの入力に許可されているデータタイプ)は、ソートの調査で一部のユーザーによって提供されます。次に、指定された他のユーザーが回答を提供します。

私が検討したいくつかのオプションは次のとおりです。

A.目的のタイプに応じて異なる方法で解析されるXMLまたは文字列(質問で追跡されます)

B. Answerテーブルを参照する(またはAnswerテーブルによって参照される)3つの個別のテーブル。意図されたタイプに基づいて結合されます。この場合、各質問の回答が1つだけになるように制約を設定する最善の方法、またはそれをアプリケーションに任せるかどうかはわかりません。

C.意図したタイプに基づいて取得できるAnswerテーブルの3つの個別の列。

私は、これらのアプローチの長所と短所、または私が考慮していなかった代替のアプローチについていくつかの情報を得ていただければ幸いです。

11
David Garrison

フロントエンドがデータにアクセスする方法に本当に依存します。

O/R-mapperを使用している場合は、データベースの設計ではなく、クラスのオブジェクト指向の設計に焦点を当ててください。その後、データベースはクラス設計をミラーリングします。正確なdb設計は、使用しているO/R-mapperおよび継承マッピングモデルによって異なります。

レコードセット、データテーブル、データリーダーなどを介してテーブルに直接アクセスする場合、簡単なことは、インバリアントカルチャを使用して値を文字列に変換し、単純なテキスト列に格納することです。 。そしてもちろん、値を読み取るときにテキストを特殊な値の型に戻すために、同じカルチャを再度使用します。

または、値タイプごとに1つの列を使用できます。今日、テラバイトのドライブがあります!

XML列は可能ですが、おそらく単純なテキスト列に比べて複雑さが増し、ほとんど同じこと、つまりシリアライズ/デシリアライズを行います。

分離された結合テーブルは、正しい正規化された方法です。ただし、かなり複雑になります。

単純にする。

my answer to Questionnaire database design-which way better? も参照してください。

あなたが言ったことに基づいて、私は次の一般的なスキーマを使用します:

CREATE TABLE [dbo].[PollQuestion]
(
    [PollQuestionId] INT NOT NULL PRIMARY KEY IDENTITY,
    [QuestionText] NVARCHAR(150) NOT NULL, -- Some reasonable character limit
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL,  -- Remove this if you don't need to hide questions
)
CREATE TABLE [dbo].[PollOption]
(
    [PollOptionId] INT NOT NULL PRIMARY KEY IDENTITY,
    [PollQuestionId] INT NOT NULL,  -- Link to the question here because options aren't shared across questions
    [OptionText] NVARCHAR(50) NOT NULL, -- Some reasonable character limit
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL  -- Remove this if you don't need to hide options

    CONSTRAINT [FK_PollOption_PollQuestionId_to_PollQuestion_PollQuestionId] FOREIGN KEY ([PollQuestionId]) REFERENCES [dbo].[PollQuestion]([PollQuestionId])
)
CREATE TABLE [dbo].[PollResponse]
(
    [PollResponseId] INT NOT NULL PRIMARY KEY IDENTITY,
    [PollOptionId] INT NOT NULL,
    [UserId] INT NOT NULL,
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL,  -- Remove this if you don't need to hide answers

    CONSTRAINT [FK_PollResponse_PollOptionId_to_PollOption_PollOptionId] FOREIGN KEY ([PollOptionId]) REFERENCES [dbo].[PollOption]([PollOptionId]),
    CONSTRAINT [FK_PollResponse_UserId_to_User_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User]([UserId])
)

データは直接操作する必要のある質問ではなく質問への回答であるため、回答が数字、日付、Wordなどであるかどうかは問題ではありません。さらに、データは質問との関連でのみ意味があります。そのため、nvarcharは、データを格納するための最も用途の広い人間が読み取り可能なメカニズムです。

質問と潜在的な回答は最初のユーザーから収集され、PollQuestionテーブルとPollOptionテーブルに挿入されます。質問に答える2番目のユーザーは、回答のリストから選択します(true/false = 2つのリスト)。 PollQuestionテーブルを展開して、作成した質問を追跡するために、必要に応じて作成者のユーザーIDを含めることもできます。

UIでは、ユーザーが選択する回答をPollOptionId値に関連付けることができます。 PollQuestionIdと一緒に使用すると、回答が質問に対して有効であることをすばやく確認できます。有効な場合の応答は、PollResponseテーブルに入力されます。

ユースケースの詳細に応じて、いくつかの潜在的な問題があります。最初のユーザーが数学の質問を使用したいが、複数の可能な答えを提供したくない場合。別の状況は、最初のユーザーが提供するオプションが2番目のユーザーが選択できる唯一のオプションではない場合です。これらの追加のユースケースをサポートするために、次のようにこのスキーマを作り直すことができます。

CREATE TABLE [dbo].[PollResponse]
(
    [PollResponseId] INT NOT NULL PRIMARY KEY IDENTITY,
    [PollOptionId] INT NULL,
    [PollQuestionId] INT NOT NULL,
    [UserId] INT NOT NULL,
    [AlternateResponse] NVARCHAR(50) NULL, -- Some reasonable character limit
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL,  -- Remove this if you don't need to hide answers

    CONSTRAINT [FK_PollResponse_PollOptionId_to_PollOption_PollOptionId] FOREIGN KEY ([PollOptionId]) REFERENCES [dbo].[PollOption]([PollOptionId]),
    CONSTRAINT [FK_PollResponse_UserId_to_User_UserId] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User]([UserId])
)

また、必要に応じて、オプションが提供されるか、または代替応答のいずれかではなく、両方(オプションと代替応答)ではないことを確認するために、おそらくチェック制約を追加します。

編集:AlternateResponseの通信データ型

完全な世界では、 generics の概念を使用して、AlternateReponseのさまざまなデータ型を処理できます。残念ながら、私たちは完璧な世界に住んでいません。私が考えることができる最良の妥協案は、PollQuestionテーブルでAlternateResponseデータ型を指定し、AlternateReponseをnvarcharとしてデータベースに格納することです。以下は、更新された質問スキーマと新しいデータ型テーブルです。

CREATE TABLE [dbo].[PollQuestion]
(
    [PollQuestionId] INT NOT NULL PRIMARY KEY IDENTITY,
    [QuestionText] NVARCHAR(150) NOT NULL, -- Some reasonable character limit
    [QuestionDataTypeId] INT NOT NULL,
    [Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
    [Archived] DATETIME2(2) NULL,  -- Remove this if you don't need to hide questions
    -- Insert FK here for QuestionDataTypeId
)
CREATE TABLE [dbo].[QuestionDataType]
(
    [QuestionDataTypeId] INT NOT NULL PRIMARY KEY IDENTITY,
    [Description] NVARCHAR(50) NOT NULL, -- Some reasonable character limit
)

このQuestionDataTypeテーブルから選択することで、質問作成者が使用できるすべてのデータタイプをリストできます。 UIはQuestionDataTypeIdを参照して、代替応答フィールドの適切な形式を選択できます。 TSQLデータ型に限定されないため、「電話番号」をデータ型にすることができ、UIで適切なフォーマット/マスキングを取得できます。また、必要に応じて、単純なcaseステートメントを介してデータを適切なタイプにキャストし、代替の回答に対してあらゆる種類の処理(選択、検証など)を実行できます。

4
Erik

EAVモデルに関する情報については、Aaron Bertrandによる とにかくEAVの何が悪いのか? を参照してください。

XMLや複数のテーブルを使用するよりも、複数の方法で各データ型の列を使用する方が良いでしょう。

制約部分は簡単です:

CHECK 
(
    CASE WHEN col1 IS NOT NULL THEN 1 ELSE 0 END + 
    CASE WHEN col2 IS NOT NULL THEN 1 ELSE 0 END + 
    CASE WHEN col3 IS NOT NULL THEN 1 ELSE 0 END = 1
)

eav とタグ付けされたこのサイトには多くの既存の質問と回答があり、おそらく質問者が質問でその用語を使用することを知らなかった他の人もいます。

それらはすべての長所と短所をカバーする可能性が高いため、これらを一読することを強くお勧めします(これにより、実際に変更されていない場合でも、ここでそれらを再度ハッシュすることはできません)。

Aaron Bertrand が残した質問のコメントに基づく回答

0
user126897