Maneira correta de armazenar um valor que pode ter vários tipos diferentes

11

Eu tenho uma tabela de respostas e uma tabela de perguntas .

A tabela Respostas tem um valor, mas, dependendo da questão, este valor poderia ser um bit, nvarcharou number(até agora). A pergunta tem uma noção de qual deve ser o tipo de valor da resposta pretendida.

Será importante analisar esses valores de resposta em um ponto ou outro, pois os números, pelo menos, precisarão ser comparados.

Para um pouco mais de contexto, as perguntas e respostas em potencial (normalmente um tipo de dados permitido para uma entrada do tipo caixa de texto) são fornecidas por alguns usuários em uma pesquisa de tipos. As respostas são fornecidas por outros usuários especificados.

Algumas opções que considerei são:

A. XML ou string que é analisada de maneira diferente, dependendo do tipo pretendido (que é monitorado na pergunta)

B. Três tabelas separadas que referenciam (ou são referenciadas por) a tabela Resposta e são unidas com base no tipo pretendido. Nesse caso, não tenho certeza da melhor maneira de configurar as restrições para garantir que cada pergunta tenha apenas uma resposta ou se isso deve ser deixado para o aplicativo.

C. Três colunas separadas na tabela de respostas que podem ser recuperadas com base no tipo pretendido.

Eu ficaria feliz em receber algumas informações sobre os prós e contras dessas abordagens, ou abordagens alternativas que eu não tinha considerado.

David Garrison
fonte

Respostas:

2

Realmente depende de como seu front-end acessa os dados.

Se você estiver usando um mapeador de O / R, concentre-se no design orientado a objetos de suas classes, não no design do banco de dados. O banco de dados espelha apenas o design da classe. O design exato do banco de dados depende do modelo de mapeamento de O / R e de mapeamento de herança que você está usando.

Se você estiver acessando as tabelas diretamente através de conjuntos de registros, tabelas de dados, leitores de dados ou similares, uma coisa simples a se fazer é converter os valores em uma cadeia de caracteres usando uma cultura invariável e armazená-los em uma coluna de texto simples . E, é claro, use a mesma cultura novamente para converter o texto novamente nos tipos de valores especializados ao ler os valores.

Como alternativa, você pode usar uma coluna por tipo de valor. Temos unidades de terabyte hoje!

Uma coluna XML é possível, mas provavelmente adiciona mais complexidade em comparação à coluna de texto simples e faz praticamente a mesma coisa, ou seja, serialização / desserialização.

Tabelas unidas separadas são a maneira normalizada correta de fazer as coisas; no entanto, eles adicionam bastante complexidade também.

Mantenha simples.

Veja também minha resposta ao design do banco de dados do questionário - de que maneira é melhor? .

Olivier Jacot-Descombes
fonte
4

Com base no que você disse, eu usaria o seguinte esquema geral:

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

Você realmente não se importa se a resposta é um número, data, palavra etc. etc. porque os dados são uma resposta a uma pergunta e não algo em que você precisa operar diretamente. Além disso, os dados só têm significado no contexto da questão. Como tal, o nvarchar é o mecanismo legível por humanos mais versátil para armazenar os dados.

A pergunta e as respostas em potencial seriam reunidas do primeiro usuário e inseridas nas tabelas PollQuestion e PollOption. O segundo usuário que responde às perguntas selecionaria em uma lista de respostas (verdadeiro / falso = lista de 2). Você também pode expandir a tabela PollQuestion para incluir o ID do usuário do criador, se apropriado, para rastrear as perguntas que ele cria.

Na sua interface do usuário, a resposta que o usuário seleciona pode ser vinculada ao valor PollOptionId. Juntamente com o PollQuestionId, você pode verificar se a resposta é válida para a pergunta rapidamente. Sua resposta, se válida, seria inserida na tabela PollResponse.

Existem alguns problemas em potencial, dependendo dos detalhes do seu caso de uso. Se o primeiro usuário quiser usar uma pergunta matemática, e você não quiser oferecer várias respostas possíveis. Outra situação é se as opções que o usuário inicial fornece não são as únicas opções que o segundo usuário pode escolher. Você pode refazer esse esquema da seguinte maneira para suportar esses casos de uso adicionais.

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

Eu provavelmente adicionaria uma restrição de verificação para garantir que uma opção seja fornecida ou uma resposta alternativa, mas não ambas (opção e resposta alternativa), dependendo de suas necessidades.

Editar: tipo de dados de comunicação para AlternateResponse.

Em um mundo perfeito, poderíamos usar o conceito de genéricos para lidar com vários tipos de dados para o AlternateReponse. Infelizmente, não vivemos em um mundo perfeito. O melhor compromisso que posso pensar é especificar qual deve ser o tipo de dados AlternateResponse na tabela PollQuestion e armazenar o AlternateReponse no banco de dados como um nvarchar. Abaixo está o esquema de perguntas atualizado e a nova tabela de tipos de dados:

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
)

Você pode listar todos os tipos de dados disponíveis para criadores de perguntas, selecionando nesta tabela QuestionDataType. Sua interface do usuário pode fazer referência ao QuestionDataTypeId para selecionar o formato apropriado para o campo de resposta alternativo. Você não está limitado aos tipos de dados TSQL; portanto, "Número de telefone" pode ser um tipo de dados e você receberá uma formatação / máscara apropriada na interface do usuário. Além disso, se necessário, você pode converter seus dados nos tipos apropriados por meio de uma simples declaração de caso, a fim de executar qualquer tipo de processamento (seleção, validação etc.) nas respostas alternativas.

Erik
fonte
0

Veja o que há de tão ruim no EAV, afinal? por Aaron Bertrand para obter algumas informações sobre o modelo EAV.

Provavelmente, será melhor, de várias maneiras, ter uma coluna para cada tipo de dados, em vez de ter XML ou várias tabelas.

A parte da restrição é 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
)

Existem muitas perguntas e respostas existentes neste site com a tag , e provavelmente outras em que o solicitante não sabia usar esse termo em sua pergunta.

Eu recomendo a leitura dessas, pois elas provavelmente abrangerão todos os prós e contras (isso evita que as pessoas as repaginem aqui, quando na realidade elas não mudaram).

Resposta baseada nos comentários das perguntas de Aaron Bertrand


fonte
-1

Penso que o problema é levado em consideração demais ou há algumas restrições adicionais sobre por que certas respostas podem ser mais acessíveis do que outras. Atualmente, parece não haver evidência de que a resposta precise ser processada de alguma forma pelo banco de dados, mas apenas como um campo de log.

Eu iria com um NVARCHAR (MAX) e depois deixaria o frontend lidar com o armazenamento / recuperação do conteúdo. Possivelmente, um campo de bits IS_CORRECT onde o frontend pode armazenar se a resposta estiver correta.

HansLindgren
fonte