0

I've have a structural database design question. I'm curios how I should deal with NULL values and table creation in my database. I've come up with a few solutions myself but I want others to look at it to confirm a good solution or come up with another solution.

[Problem]: A question can have zero or multiple answer's and a answer should have a value or a question_option_id column filled in depending on the type column of the question.

enter image description here

[Solution 1]: Split the tables up into 2, this does take care of the NULL values but now there are duplicate relations (user_id) this isn't a big deal but when the answer_* tables grow with other generic columns these need to be duplicated on all answer_* tables.

enter image description here

[Solution 2]: Add one more table to the previous solution to store all the generic columns and keep the split tables to only contain type specific columns. The only but there is and the main reason why I even ask this question is that we now went from 1 answer table to 3 and all answer -> answer_* relations are optional because the answer type is variable.

enter image description here

I would love to hear your input on this!

Mees van Wel
  • 1,393
  • 1
  • 7
  • 13
  • 2
    I would use the first design and would just add an integrity constraint on the table: `constraint chk1 check (values is not null and question_option_id is null or values is null and question_option_id is not null)`. The other designs look overkill [to me]. – The Impaler Jul 27 '22 at 17:19
  • 2
    It's unclear what this is trying to achieve and how answers and question options relate. Describing this from a user's perspective might help. It feels like you have either multiple choice questions (which might allow multiple answers from the same user) or you have fill-in-the-blank questions. Is that the purpose? – Schwern Jul 27 '22 at 17:31
  • Yes, the purpose is to distinguish open question answers from closed question answers. the answer options could be "male", "female" if the question type is closed for instance gender – Mees van Wel Jul 27 '22 at 17:56
  • @TheImpaler So a shit load of NULL columns is no big deal? – Mees van Wel Jul 27 '22 at 17:58
  • 1
    Do questions have names and descriptions? Or just the text of the question? – Schwern Jul 27 '22 at 18:30
  • 1
    @MeesvanWel That is correct. Many database engines actually use bitmaps to store nulls. So no storage is used for a null. – The Impaler Jul 27 '22 at 18:44
  • @Schwern questions have a name and description the neme referring to the text of the question – Mees van Wel Jul 27 '22 at 18:52

1 Answers1

1

Consider the following.

  • My Favorite Dog Breed Is
    • Beagle
    • Basset Hound
    • Boxer
    • Other, please specify _____________

You can have an answer to a question option which also has a value.

Now consider that both your answer and your question option have their own references to the question. It is possible to have the answer and the answer's question options refer to different questions! This could be constrained with triggers, but it's better to eliminate the redundancy.

That redundancy is there because, as you've designed option 1, a "closed" answer has to refer to the question and an "open" answer will refer to the question via its options.


What I would do is make a "closed" answer to be a question option with no name. An I'd mark whether or not question options can have a value.

In addition, I would remove the enum from question. It is possible to have a question marked as closed and yet have multiple options. The question options are the single source of truth, look at them. If necessary later for optimization, you can add an update/delete/insert trigger on question_option to update the enum.

-- You didn't specify a database, so I'll use PostgreSQL.
create table users (
  id bigserial primary key,
  name text not null
);

create table questions (
  id bigserial primary key,

  -- Which is the text of the question? Consider question_text.
  -- It's unclear what a description of a question is. Extra
  -- explanatory text?
  name text not null,
  description text
);

create table question_options (
  id bigserial primary key,
  question_id bigint not null references questions,

  -- name is an odd way to describe the text of
  -- the option. Label?
  -- I went with not null here because this forces
  -- you to choose a label or an empty string.
  label text not null,

  -- Does the option accept a value?
  type enum('with value', 'without value') not null
);

create table answers (
  id bigserial primary key,
  user_id bigint not null references users,
  value text,
  question_option_id bigint references question_options,

  -- It's useful to know when an answer was given.
  answered_at timestamp not null default current_timestamp(),

  -- Presumably a user can't choose the same to the same question multiple times.
  -- If they can, lift the constraint later.
  unique(user_id, question_option_id),

  constraint answers_filled_in_check check(
    values is not null or question_option_id is not null
  )
);

An "open question" has one option with a blank label which accepts a value. A closed question can have any number of options with any settings.

A design consideration I did not address: what if a user can answer a question multiple times and you want to store the history of their answers?

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • First of all thanks for your extensive answer. Next to fill you in I use Postgres. This seems like a really good solution. One question though, what if I want to have a boolean question and answers as well, how would this look in the current schema? My frontend needs to know what input to show thats why I created the question enum. – Mees van Wel Jul 27 '22 at 19:31
  • @MeesvanWel A boolean question has two options: true and false. If your front end has to know what input to show, it will look at the question's options and decide what to do with them. Ideally your front-end has no SQL in it at all and asks the question [model](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (or [repository](https://medium.com/@pererikbergman/repository-design-pattern-e28c0f3e4a30)) what its options are and what type it is and the queries are hidden in the model/repository. Avoid that question type column because if the options change it will be wrong. – Schwern Jul 27 '22 at 19:43
  • That's for sure my back-end is served over a private GraphQL api that my React front-end app queries. One more question haha, If the question should be a date input for instance to enter your birth day where would I store the information that states that a date input should popup instead of a text input? – Mees van Wel Jul 27 '22 at 20:12
  • @MeesvanWel Yes, that information would have to be stored as part of the question option. – Schwern Jul 27 '22 at 20:30