web-dev-qa-db-ja.com

質問銀行シナリオで複数のテーブルを参照する外部キー

QuestionsBanksのセットにQuestionsが含まれるビジネスドメインに取り組んでいます。複数のQuizzesは特定のQuestionBankBankId)を参照でき、各QuizQuestionStudentにエントリがあります- 質問クイズについて学生

データベース構造を形成するために、次のテーブルを作成しました。

CREATE TABLE QuestionBanks (
    BankId UniqueIdentifier --PK
)

CREATE TABLE Questions (
    BankId UniqueIdentifier, --PK
    QuestionNumber Int, --PK
    QuestionText VarChar(1000)
)

CREATE TABLE Quizzes (
    QuizId UniqueIdentifier, --PK
    BankId UniqueIdentifier
)

CREATE TABLE QuizQuestionStudent (
    QuizId UniqueIdentifier, --PK
    QuestionNumber Int, --PK
    StudentId UniqueIdentifier, --PK
    IsCorrect Bit
)

QuizQuestionStudentにもQuestionsへの外部(FK)が含まれるようにスキーマを設計する最良の方法は何ですか(有効な質問のみが追加されるようにします)。 QuizQuestionStudentQuestionNumber)の列とQuizzesテーブル(BankIdの列で構成されるFKが本質的に必要です=)。

1つの解決策はQuizQuestionStudentの各行に対してBankIdを繰り返すことですが、それはかなり無駄に見えます。

これを達成する他の方法はありますか?

4
Lambo Jayapalan

QuestionNumberはどういうわけかテーブルの質問の一意の識別子ですか?それとも、QuestionBankは、たとえば1から25までの5つの異なる質問セットを持つように設計されていますか?

Questions.QuestionNumberが特定のBankIdの単なる非一意の順序付け値である場合、QuizQuestionStudentレコードを保存するときに、回答が適切な銀行の質問であると想定せずに、特定の質問が属するQuestionBankを知る方法はありません。 。 QuestionNumber = 5の場合、複数の銀行に5つ目の質問があった場合にBankIdを確認する方法がわかりません。クイズと同じBankIdに属していると想定することもできますが、データベースがチェックするものは何ですか?

ここで、QuestionNumberが一意である場合、別の問題があります。質問のBankIdをクイズのBankIdと同じにする必要があります。 QuizQuestionStudentテーブルでBankIdを繰り返しても問題はありません。その値はアプリから取得されたものであり、確認しようとしているものだからです。他の2つのテーブルのデータが、QuizQuestionStudentの値だけでなく、互いの値にも準拠していることを確認します。外部キーを使用してそれを行うことはできません。 (すべてのテーブルのデザインが大幅に異なることで問題が解決する可能性がありますが、思いつきませんでした。)

ただし、それでもデータベース層で強力に実施できます。 「インデックス付きビュー」または「マテリアライズドビュー」を作成して、その値を制約できます。 ビューは複数のテーブルを参照できます。 BankIdを2つの異なるパスから取得し、それらが同じであることを確認します。

CREATE VIEW QuizValidityCheck
WITH SCHEMABINDING
AS
(
SELECT
1 AS DupeCheckValue
FROM QuizQuestionStudent qqs
INNER JOIN Quizzes qz ON qqs.QuizId = qz.QuizId
INNER JOIN Questions qu ON qqs.QuestionNumber = qu.QuestionNumber
CROSS JOIN (VALUES (0),(1)) AS RowExploder(ExploderRow) -- this forces there to be 2 rows any time there's a BankId mismatch, quick causes the unique constraint below to fail
WHERE qu.BankId <> qz.BankId
)
GO

CREATE UNIQUE CLUSTERED INDEX IX_CHECK_BankIdMismatch ON QuizValidityCheck(DupeCheckValue)
GO

DFを使用して列にカスタムCHECK制約を適用する を使用して、もう一方を確認することもできます。しかし、 それがバラバラになるいくつかのシナリオ があります。

1
Riley Major

これが私の2セントです。

最初に、すべての主キーにuniqueidentifiersを使用していることに気付きました。多くの場合、これはクラスター化インデックスにそれを使用することも意味します。その場合、uniqueidentifiersを使用する理由と、本当に必要かどうかを検討する必要があります。あなたはそれらを増分するアイデンティティを持つ整数に変更する方が良いかもしれません。 詳細はこちらをご覧ください

特定の質問については、スキーマをそのままにする場合は2つのオプションが表示されます。

  1. BankIDをQuizQuestionStudentテーブルに追加すると、必要に応じて外部キーをテーブルに配置できます。
  2. QuestionIDという新しい列をQuestionsテーブルに追加します。これにより、ID列を作成して主キーにするか、一意のインデックスにしてから、QuizQuestionStudentテーブルから外部キーを追加できます。

編集:

以下の説明の後、私が理解したことから、QuizQuestionStudentテーブルのQuestionNumberが正しいBankIdおよびQuizIdに関連付けられていることを確認したいと思います。これを取り除くには、BankIdをQuizQuestionStudentに配置します。次に、Quizzesテーブルを変更して、BankIdを主キーに含めるか、QuizIdとBankIdに一意のインデックスを作成します。次に、2つの外部キーをQuizQuestionStudentテーブルに配置できます。 BankIdとQuestionNumberの質問テーブルを参照するもの、次に、QuizIdとBankIdのクイズテーブルを参照するもの。 2つの間に、間違った質問が挿入されるのを防ぎます。

キーのあるテーブル:

CREATE TABLE dbo.QuestionBanks(
  BankId   INT NOT NULL
 ,CONSTRAINT pk_QuestionBanks PRIMARY KEY CLUSTERED(BankId))

CREATE TABLE dbo.Questions(
  BankId           INT
 ,QuestionNumber   INT
 ,CONSTRAINT pk_questions PRIMARY KEY CLUSTERED(BankId, QuestionNumber)
 ,CONSTRAINT fk_questions_BankId FOREIGN KEY(BankId) REFERENCES dbo.QuestionBanks(BankId))

CREATE TABLE dbo.Quizzes(
  QuizId   INT NOT NULL
 ,BankId   INT NOT NULL
 ,CONSTRAINT pk_quizzes PRIMARY KEY CLUSTERED(QuizId, BankId)
 ,CONSTRAINT fk_quizzes_bankid FOREIGN KEY(BankId) REFERENCES dbo.QuestionBanks(BankId))

CREATE TABLE dbo.QuizQuestionStudent(
  QuizId           INT NOT NULL
 ,QuestionNumber   INT NOT NULL
 ,StudentId        INT NOT NULL
 ,BankId           INT NOT NULL
 ,IsCorrect        BIT
 ,CONSTRAINT fk_QuizQuestionStudent_QuizID_BankID FOREIGN KEY(QuizId, BankId)
  REFERENCES dbo.Quizzes(QuizId, BankID)
 ,CONSTRAINT fk_QuizQuestionStudent_BankID_QuestionNumber FOREIGN KEY(BankId, QuestionNumber)
  REFERENCES dbo.Questions(BankId, QuestionNumber))

主キー、またはクラスター化インデックスキーを小さくしたい場合は、主キー制約を一意のインデックスに置き換えることができます。

0
Shawn Beddes