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
FirstSeenDate
table-field (WordTraining
orSyllableTraining
table - 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" (
Fail
is false), and the fieldNextRep
(next repetition (review)) is set (ISO-date when to show that problem to the user again). FieldNextRep
is 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
NextRep
due today (or before today = missed) - show them all first. Then logic is like in item 4 above (show all failed, then unseen). IfNextRep
Problem is passed, FieldNextRep
is set to new value = 2 * (todayDate - (current)NextRep) - - but this algorithm is subject to change and might be very complicated.