The question is about my database design. Is it OK?
I feel uncomfortable about having 2 separate WordTraining and SyllableTraining tables and about too simple design (not much normalized). Maybe there are any other issues.
My web-app teaches kids to read syllables and words.
First the user (kid) chooses - whether to train to read syllables, or to train to read (whole) words.
Then the user is presented with words/syllables from a fixed list (WordBank and SyllableBank Tables below), if the user fails a read exercise - the app asks the same question again next time and then periodically asks again at increasing time intervals. If the user sees the word/syllable for the first time and passes the read exercise at the first attempt - this word/syllable would never be shown to the user again (for the exception of the special Cram Mode when the user can practise all words from the database regardless of his previous test results).
I expect not more than 60 000 words / 300 syllables / ~10 mln users.
My database design is as follows:
WordBank +----+-------+------------------+ | id | Word | SyllabilizedWord | +----+-------+------------------+ | 1 | hello | hel/lo | | 2 | papa | pa/pa | +----+-------+------------------+ SyllableBank +----+----------+ | id | Syllable | +----+----------+ | 1 | hel | | 2 | lo | +----+----------+ WordTraining +----+--------+------------+---------------+--------+--------+--------+ | id | Failed | NextRep | FirstSeenDate | Ignore | WordId | UserId | +----+--------+------------+---------------+--------+--------+--------+ | 1 | True | NULL | 2020-02-26 | False | 1 | 1 | | 2 | True | 2020-02-30 | 2020-02-26 | False | 4 | 2 | | 3 | False | NULL | 2020-02-26 | False | 7 | 3 | +----+--------+------------+---------------+--------+--------+--------+ SyllableTraining +----+--------+------------+---------------+--------+------------+--------+ | id | Failed | NextRep | FirstSeenDate | Ignore | SyllableId | UserId | +----+--------+------------+---------------+--------+------------+--------+ | 1 | True | NULL | 2020-02-26 | False | 1 | 1 | | 2 | True | 2020-02-30 | 2020-02-26 | False | 4 | 2 | | 3 | False | NULL | 2020-02-26 | False | 7 | 3 | +----+--------+------------+---------------+--------+------------+--------+ User +----+---------------+------------+--------------+---------+---------------+ | id | LoginName | FullName | Email | PswHash | LastLoginDate | +----+---------------+------------+--------------+---------+---------------+ | 1 | johnLoginName | John Black | [email protected] | acb3456 | 2020-02-22 | +----+---------------+------------+--------------+---------+---------------+ WordTraining Table - Once the user (who clicked button (chose) Word-trainig Mode) is presented a word for the first time (sees it first) - a record is created. WordId is a ForeignKey matching with (primary key) of the table Word (all words available for practise), UserId is a foreign key for respective User. Ignore atribute means that user wants to see respective word (exercise with it) no more.
WordBank.SyllabilizedWord - is just to show a prompt how to syllabilize the word correctly.
More detailed app logic:
- I have multiple users
- For each user there are a few training modes possible (train read whole words, train read syllables, 3rd mode is complicated to describe)
- For each user I store statistics - ids of word or syllable the user was shown (at least once) + whether the user failed or passed the exercise + date when user was shown that kind of Problem.
Algorithm
- User logs in
- User selects mode (train syllables or words). In the future different modes might be added or initial modes can be often modified.
- At first start of the app when user clicks "Next Problem" button, a new (unseen yet) Problem is presented to the user. He answers and the Problem (respective word or syllable) is marked passed or failed. Plus
FirstSeenDatetable-field (WordTrainingorSyllableTrainingtable - depending on what user chose to practice) is set (ISO-date when the user saw this Problem). - At the second start of the app when user clicks "Next Problem" button, problem selection is like this: If there are any Failed Problems - show them all first. If there are no Failed Problems - show unseen-yet Problems. If Failed (at previous game) Problem is passed during this time, Problem is marked "Passed" (
Failis false), and the fieldNextRep(next repetition (review)) is set (ISO-date when to show that problem to the user again). FieldNextRepis set to 2 * (todayDate - FirstSeenDate) - but this algorithm is subject to change and might be very complicated. - At third start of the app when user clicks "Next Problem" button - If there are any
NextRepdue today (or before today = missed) - show them all first. Then logic is like in item 4 above (show all failed, then unseen). IfNextRepProblem is passed, FieldNextRepis set to new value = 2 * (todayDate - (current)NextRep) - - but this algorithm is subject to change and might be very complicated.