Правильный способ хранения значения, которое может быть нескольких разных типов

11

У меня есть таблица ответов и таблица вопросов .

Таблица Ответы имеет значение, но в зависимости от вопроса, это значение может быть bit, nvarcharили number( до сих пор). Вопрос имеет представление о том , что его предполагаемом тип значения ответа должен быть.

Будет важно проанализировать эти значения Ответа в той или иной точке, поскольку числа, по крайней мере, нужно будет сравнивать.

Для некоторого большего контекста вопросы и потенциальные ответы (обычно тип данных, разрешенный для ввода типа текстового поля) предоставляются некоторыми пользователями в опросе. Ответы затем предоставляются другими указанными пользователями.

Я рассмотрел несколько вариантов:

A. XML или строка, которая анализируется по-разному в зависимости от предполагаемого типа (который отслеживается в вопросе)

Б. Три отдельные таблицы, которые ссылаются (или на которые ссылается) таблица Ответов и объединяются в зависимости от предполагаемого типа. В этом случае я не уверен в наилучшем способе установки ограничений, чтобы гарантировать, что на каждый вопрос есть только один ответ, или его следует оставить приложению.

C. Три отдельных столбца в таблице ответов, которые можно получить в зависимости от предполагаемого типа.

Я был бы рад получить некоторую информацию о плюсах и минусах этих подходов или альтернативных подходах, которые я не рассматривал.

Дэвид Гаррисон
источник

Ответы:

2

Это действительно зависит от того, как ваш интерфейс получает доступ к данным.

Если вы используете O / R-mapper, сосредоточьтесь на объектно-ориентированном дизайне ваших классов, а не на дизайне базы данных. База данных тогда просто отражает дизайн класса. Точный дизайн БД зависит от используемой вами модели O / R-mapper и модели наследования.

Если вы обращаетесь к таблицам напрямую через наборы записей, таблицы данных, устройства чтения данных или тому подобное, вам нужно просто преобразовать значения в строку с использованием инвариантной культуры и сохранить ее в простом текстовом столбце. , И, конечно же, снова используйте ту же культуру для преобразования текста обратно в специализированные типы значений при чтении значений.

В качестве альтернативы вы можете использовать один столбец для каждого типа значения. У нас сегодня терабайтные диски!

Столбец XML возможен, но, вероятно, добавляет больше сложности по сравнению с простым текстовым столбцом и выполняет почти то же самое, а именно сериализацию / десериализацию.

Разделенные соединенные таблицы - это правильный нормализованный способ работы; однако, они также добавляют некоторую сложность.

Будь проще.

Читайте также мой ответ на вопросник «Разработка базы данных» - какой путь лучше? ,

Оливье Жако-Дескомб
источник
4

Исходя из того, что вы сказали, я бы использовал следующую общую схему:

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

На самом деле вам все равно, является ли ответ числом, датой, словом и т. Д., Потому что данные - это ответ на вопрос, а не то, над чем вам нужно работать напрямую. Кроме того, данные имеют значение только в контексте вопроса. Таким образом, nvarchar - самый универсальный читабельный механизм хранения данных.

Вопрос и возможные ответы будут получены от первого пользователя и вставлены в таблицы PollQuestion и PollOption. Второй пользователь, который отвечает на вопросы, будет выбирать из списка ответов (true / false = список из 2). Вы также можете расширить таблицу PollQuestion, включив, при необходимости, идентификатор пользователя создателя, чтобы отслеживать вопросы, которые они создают.

В вашем пользовательском интерфейсе ответ, выбранный пользователем, может быть привязан к значению PollOptionId. Вместе с PollQuestionId вы можете быстро проверить, что ответ действителен для вопроса. Их ответ, если он действителен, будет занесен в таблицу PollResponse.

Есть несколько потенциальных проблем в зависимости от деталей вашего варианта использования. Если первый пользователь хочет использовать математический вопрос, и вы не хотите предлагать несколько возможных ответов. Другая ситуация, если параметры, которые предоставляет первоначальный пользователь, не являются единственными параметрами, которые может выбрать второй пользователь. Вы можете переработать эту схему следующим образом, чтобы поддержать эти дополнительные варианты использования.

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.

В идеальном мире мы могли бы использовать концепцию обобщений для обработки различных типов данных для AlternateReponse. Увы, мы не живем в идеальном мире. Лучший компромисс, о котором я могу подумать, - это указать, какой тип данных AlternateResponse должен быть в таблице PollQuestion, и сохранить 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. Ваш пользовательский интерфейс может ссылаться на QuestionDataTypeId, чтобы выбрать правильный формат для альтернативного поля ответа. Вы не ограничены типами данных TSQL, поэтому «Номер телефона» может быть типом данных, и вы получите соответствующее форматирование / маскирование в пользовательском интерфейсе. Также, если необходимо, вы можете привести ваши данные к соответствующим типам с помощью простого оператора case для выполнения любого вида обработки (выбор, проверка и т. Д.) Альтернативных ответов.

Erik
источник
0

Взгляните, что же такого плохого в EAV? Аароном Бертраном за некоторую информацию о модели 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
)

На этом сайте есть много вопросов и ответов, помеченных как , и, возможно, другие, где не знал, использовать этот термин в своем вопросе.

Я настоятельно рекомендую ознакомиться с ними, так как они, скорее всего, покроют все плюсы и минусы (это не позволяет людям перефразировать их здесь, когда на самом деле они не изменились).

Ответ основан на комментариях к вопросу, оставленных Аароном Бертраном


источник
-1

Я думаю, что проблема слишком продумана или есть некоторые дополнительные ограничения в том, почему некоторые ответы могут быть более приемлемыми, чем другие. В настоящее время, похоже, нет никаких доказательств того, что Ответ должен был бы каким-либо образом обрабатываться БД, а просто как поле журнала.

Я хотел бы использовать NVARCHAR (MAX), а затем просто позволить внешнему интерфейсу заниматься хранением / извлечением контента. Возможно, битовое поле IS_CORRECT, где интерфейс может хранить, если ответ правильный.

HansLindgren
источник