Forma adecuada de almacenar un valor que podría ser de varios tipos diferentes

11

Tengo una tabla de respuestas y una tabla de preguntas .

La tabla de respuestas tiene un valor, pero dependiendo de la cuestión, este valor podría ser una bit, nvarcharo number(hasta ahora). La pregunta tiene una noción de cuál debería ser el tipo de valor de respuesta deseado.

Será importante analizar estos valores de respuesta en un punto u otro, ya que los números, al menos, deberán compararse.

Para un poco más de contexto, las preguntas y posibles respuestas (generalmente un tipo de datos permitido para una entrada de tipo cuadro de texto) son suministradas por algunos usuarios en una encuesta de algún tipo. Luego, las respuestas son proporcionadas por otros usuarios específicos.

Un par de opciones que he considerado son:

A. XML o cadena que se analiza de manera diferente según el tipo deseado (que se realiza un seguimiento en la pregunta)

B. Tres tablas separadas que hacen referencia (o son referenciadas por) la tabla de respuestas y se unen según el tipo deseado. En este caso, no estoy seguro de la mejor manera de configurar las restricciones para garantizar que cada pregunta tenga solo una respuesta, o si eso debería dejarse a la aplicación.

C. Tres columnas separadas en la tabla de respuestas que se pueden recuperar según el tipo deseado.

Me alegraría recibir información sobre los pros y los contras de estos enfoques, o enfoques alternativos que no había considerado.

David Garrison
fuente

Respuestas:

2

Realmente depende de cómo su front-end acceda a los datos.

Si está utilizando un mapeador O / R, concéntrese en el diseño orientado a objetos de sus clases, no en el diseño de la base de datos. La base de datos simplemente refleja el diseño de la clase. El diseño exacto de la base de datos depende del mapeador O / R y del modelo de mapeo de herencia que esté utilizando.

Si está accediendo a las tablas directamente a través de conjuntos de registros, tablas de datos, lectores de datos o similares, una cosa simple es convertir los valores en una cadena utilizando una cultura invariable y almacenarla en una columna de texto simple. . Y, por supuesto, use la misma cultura nuevamente para convertir el texto nuevamente a los tipos de valores especializados al leer los valores.

Alternativamente, puede usar una columna por tipo de valor. ¡Tenemos unidades de terabytes hoy!

Es posible una columna XML, pero probablemente agrega más complejidad en comparación con la columna de texto simple y hace más o menos lo mismo, es decir, serializar / deserializar.

Las tablas unidas separadas son la forma normalizada correcta de hacer las cosas; sin embargo, también agregan bastante complejidad.

Mantenlo simple.

Vea también mi respuesta al diseño de la base de datos del Cuestionario: ¿qué forma es mejor? .

Olivier Jacot-Descombes
fuente
4

Según lo que ha dicho, usaría el siguiente esquema general:

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

Realmente no le importa si la respuesta es un número, una fecha, una palabra, etc., porque los datos son una respuesta a una pregunta, no algo en lo que necesita operar directamente. Además, los datos solo tienen significado en contexto para la pregunta. Como tal, nvarchar es el mecanismo legible por humanos más versátil para almacenar los datos.

La pregunta y las posibles respuestas se recopilarían del primer usuario y se insertarían en las tablas PollQuestion y PollOption. El segundo usuario que responde las preguntas seleccionaría de una lista de respuestas (verdadero / falso = lista de 2). También puede expandir la tabla PollQuestion para incluir la identificación de usuario del creador, si corresponde, para rastrear las preguntas que crean.

En su IU, la respuesta que selecciona el usuario puede estar vinculada al valor de PollOptionId. Junto con el PollQuestionId puede verificar que la respuesta sea válida para la pregunta rápidamente. Su respuesta, si es válida, se ingresaría en la tabla PollResponse.

Hay un par de posibles problemas dependiendo de los detalles de su caso de uso. Si el primer usuario quiere usar una pregunta matemática y no desea ofrecer múltiples respuestas posibles. Otra situación es si las opciones que proporciona el usuario inicial no son las únicas opciones que el segundo usuario puede elegir. Puede volver a trabajar este esquema de la siguiente manera para admitir estos casos de uso adicionales.

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

Probablemente también agregaría una restricción de verificación para asegurarme de que se proporcione una opción o una respuesta alternativa, pero no ambas (opción y respuesta alternativa), según sus necesidades.

Editar: Comunicando el tipo de datos para AlternateResponse.

En un mundo perfecto, podríamos utilizar el concepto de genéricos para manejar varios tipos de datos para la Respuesta Alternativa. Por desgracia, no vivimos en un mundo perfecto. El mejor compromiso que se me ocurre es especificar cuál debería ser el tipo de datos AlternateResponse en la tabla PollQuestion, y almacenar AlternateReponse en la base de datos como un nvarchar. A continuación se muestra el esquema de preguntas actualizado y la nueva tabla de tipos de datos:

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
)

Puede enumerar todos los tipos de datos disponibles para los creadores de preguntas seleccionando en esta tabla QuestionDataType. Su interfaz de usuario puede hacer referencia al QuestionDataTypeId para seleccionar el formato adecuado para el campo de respuesta alternativo. No está limitado a los tipos de datos TSQL, por lo que "Número de teléfono" puede ser un tipo de datos y obtendrá el formato / enmascaramiento adecuado en la interfaz de usuario. Además, si es necesario, puede enviar sus datos a los tipos apropiados a través de una declaración de caso simple para realizar cualquier tipo de procesamiento (selección, validación, etc.) en las respuestas alternativas.

Erik
fuente
0

Echa un vistazo a ¿Qué tiene de malo EAV? por Aaron Bertrand para obtener información sobre el modelo EAV.

Es probable que sea mejor en múltiples formas tener una columna para cada tipo de datos en lugar de tener XML o varias tablas.

La parte de restricción es fácil:

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
)

Hay muchas preguntas y respuestas existentes en este sitio etiquetadas , y probablemente otras en las que el autor de la pregunta no sabía usar ese término en su pregunta.

Recomiendo encarecidamente leerlos, ya que probablemente cubrirán todos los pros y los contras (esto evita que las personas los vuelvan a mezclar aquí, cuando en realidad no han cambiado).

Respuesta basada en los comentarios de las preguntas dejadas por Aaron Bertrand


fuente
-1

Creo que el problema se piensa demasiado o hay algunas restricciones adicionales sobre por qué ciertas respuestas podrían ser más aceptables que otras. Actualmente, parece que no hay evidencia de que la respuesta tenga que ser procesada de alguna manera por la base de datos, sino solo como un campo de registro.

Iría con un NVARCHAR (MAX) y luego dejaría que la interfaz se ocupara de almacenar / recuperar el contenido. Posiblemente un campo de bit IS_CORRECTO donde la interfaz podría almacenar si la respuesta es correcta.

HansLindgren
fuente