web-dev-qa-db-fra.com

Bonne façon de stocker une valeur qui pourrait être plusieurs types différents

J'ai une table réponses table et a questions Table.

Le tableau des réponses a une valeur, mais en fonction de la question, cette valeur pourrait être un bit, nvarchar ou number (jusqu'à présent). Le question a une notion de quel type de valeur de réponse prévu devrait être.

Il sera important d'analyser ces valeurs Réponse sur un point ou un autre puisque les chiffres, au moins, devront être comparés.

Pour un peu plus de contexte, les questions et les réponses potentielles (typiquement un type de données autorisé pour une entrée de type Textbox) sont fournis par certains utilisateurs d'une enquête dans une enquête. Les réponses sont ensuite fournies par d'autres utilisateurs spécifiés.

Quelques options que j'ai envisagées sont:

A. XML ou chaîne qui est analysée différemment en fonction du type souhaité (qui est gardé sur la trace de la question)

B. Trois tables distinctes qui se rapportent (ou sont référencées par) la table de réponses et sont reliées sur la base du type souhaité. Dans ce cas, je ne suis pas sûr de la meilleure façon de mettre en place les contraintes pour que chaque question n'a qu'une seule réponse ou si cela devrait être laissé à la demande.

C. Trois colonnes séparées sur la table de réponses pouvant être récupérées en fonction du type souhaité.

Je serais heureux d'avoir une contribution sur les avantages et les inconvénients de ces approches ou d'autres approches que je n'avais pas envisagées.

11
David Garrison

Cela dépend vraiment de la manière dont votre front-end accède aux données.

Si vous utilisez un O/R-mapper, concentrez-vous sur la conception orientée objet de vos classes, pas sur la conception de la base de données. La base de données reflète alors la conception de la classe. La conception exacte de la DB dépend du modèle de mappage O/R-mapper et de mappage de héritage que vous utilisez.

Si vous accédez directement aux tableaux via des ensembles d'enregistrements, des tables de données, des lecteurs de données ou similaires, une chose simple à faire, est de convertir les valeurs à une chaîne à l'aide d'une culture invariante et de la stocker dans une colonne de texte simple. . Et, bien sûr, utilisez la même culture à nouveau afin de convertir le texte en arrière sur les types de valeur spécialisés lors de la lecture des valeurs.

Sinon, vous pouvez utiliser une colonne par type de valeur. Nous avons des lecteurs de téraoctet aujourd'hui!

Une colonne XML est possible, mais ajoute probablement plus de complexité par rapport à la colonne de texte simple et fait à peu près la même chose, à savoir sérialiser/désérialiser.

Les tables jointes séparées sont la bonne manière normalisée de faire des choses; Cependant, ils ajoutent une certaine complexité aussi.

Rester simple.

Voir aussi ma réponse à Conception de la base de données de questionnaires - de quelle manière est le meilleur? .

Sur la base de ce que vous avez dit, j'utiliserais le schéma général suivant:

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])
)

Vous ne vous souciez pas vraiment si la réponse est un nombre, une date, un mot, etc. Étant donné que les données sont une réponse à une question non quelque chose dont vous avez besoin pour fonctionner directement. En outre, les données n'ont que la signification dans le contexte de la question. En tant que tel, NvarcharN est le mécanisme lisible le plus polyvalent pour stocker les données.

La question et les réponses potentielles seraient recueillies à partir du premier utilisateur et insérées dans les tables de pollostion et de poloption. Le deuxième utilisateur qui répond aux questions sélectionnerait dans une liste de réponses (true/false = la liste de 2). Vous pouvez également développer la table de la polluerest pour inclure l'ID utilisateur du créateur, le cas échéant afin de suivre les questions qu'ils créent.

Sur votre UI, la réponse que les sélectionneurs utilisent peuvent être liées à la valeur poloptionid. En collaboration avec le polluestidid, vous pouvez vérifier que la réponse est valide pour la question rapidement. Leur réponse si valide serait entrée dans la table Pollresponse.

Il y a quelques problèmes potentiels en fonction des détails de votre cas d'utilisation. Si le premier utilisateur souhaite utiliser une question mathématique et que vous ne voulez pas proposer plusieurs réponses possibles. Une autre situation est que si les options fournies par l'utilisateur initial ne sont pas les seules options que le second utilisateur peut choisir. Vous pouvez retravailler ce schéma comme suit pour soutenir ces cas d'utilisation supplémentaires.

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])
)

J'ajouterais probablement probablement une contrainte de vérification pour vous assurer que soit une option est fournie ou une réponse alternative, mais pas à la fois (option et réponse alternative), en fonction de vos besoins.

Modifier: DataType de communication pour alternateresponse.

Dans un monde parfait, nous pourrions utiliser le concept de génériques pour gérer divers types de données pour l'alternateeponse. Hélas nous ne vivons pas dans un monde parfait. Le meilleur compromis que je puisse penser est de spécifier ce que le type de données Alternateresponse doit être dans la table de polluestion et stocker la alternateReponse de la base de données en tant que NvarchaRar. Vous trouverez ci-dessous le schéma de questions actualisées et le nouveau tableau de données:

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
)

Vous pouvez répertorier tous les types de données disponibles pour les créateurs de questions en sélectionnant dans cette table QuestionDaTaType. Votre UI peut faire référence au questionnaire pour sélectionner le format approprié pour le champ de réponse alternatif. Vous n'êtes pas limité aux types de données TSQL, un "numéro de téléphone" peut donc être un type de données et vous obtiendrez le formatage/masquage approprié sur l'interface utilisateur. De plus, si nécessaire, vous pouvez lancer vos données sur les types appropriés via une instruction de cas simple afin de faire tout type de traitement (sélectionner, validation, etc.) sur les réponses alternatives.

4
Erik

Regardez ce qui est si mauvais à propos de EAV, de toute façon? par Aaron Bertrand pour des informations sur le modèle EAV.

Cela sera probablement meilleur de plusieurs façons d'avoir une colonne pour chaque type de données au lieu d'avoir XML ou plusieurs tables.

La partie contrainte est facile:

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
)

Il existe de nombreuses questions et réponses existantes sur ce site étiquetées EAV , et probablement d'autres où l'Asker ne savait pas utiliser ce terme dans leur question.

Je recommande vivement de lire à travers celles-ci, car ils couvriront probablement tous les avantages et les inconvénients (cela empêche les gens de les reproduire ici, quand en réalité ils n'ont pas changé).

Réponse basée sur les commentaires de la question laissés par Aaron Bertrand

0
user126897