How would you design this DB? - mysql

We are launching a website (paid subscription) and the sign up process includes entering an activation code. Activation codes are printed on scratch cards and sold via offline channels. Some of these cards are for 1 month access. Others are for 3 months and 1 year. Activation codes are unique 10-digit random numbers.
When the access expires, users can buy another activation card and extend the subscription by entering the new activation code. Additionally, we should also be able to extend their subscription if they request for it. For example, until a certain date (e.g. 1 additional week).
Considering the above information, how would you design the DB for the user-activation_code relationship? Do you think this design is good?
tbl_user
----------------
id
name
status_id
tbl_user_status
----------------
id
description
tbl_activation_code
----------------
activation_code
activation_code_type_id
activation_code_status_id
user_id
activated_date
expiry_date
tbl_activation_code_type
----------------
id
description
tbl_activation_code_status
----------------
id
description
Update: Activation codes will be required only:
1) Upon initial sign up
2) Closer to the access expiry date (say, 7 days) when the system displays a notification with a link to page to enter the activation code
3) After expiry, when a user tries to login, she will be asked for the activation code
Therefore, a user is not expected to key in the activation code as and when wanted.

It's not bad. However, I would suggest that you add two fields to tbl_user:
tbl_user
----------------
id
name
status_id
activated_date
expiry_date
Of course, activated_date holds the date they were first activated, while expiry_date holds the date when they will expire. You also need a procedure to update this expiry_date, whenever they buy a new card. This procedure should handle two cards with overlapping dates, so the user doesn't double-up payment for a particular period. For example:
Card 1 - Sep 1 to Sep 30
Card 2 - Sep 16 to Oct 15
There are fifteen days of overlap there, so the user's activated_date should be Sep 1, while their expiry_date should be Oct 30 (Oct 15 + 15 days).
Considering this, I would change tbl_activation_code, as expiry_date becomes a bit misleading. Instead, create a column called access_days that will be used to calculate the user's expiry_date.
Also, if you want to remember cards that were issued, even if not activated, then I would split tbl_activation_code into two tables:
tbl_activation_code
----------------
activation_code
activation_code_type_id
activation_code_status_id
access_days
tbl_activation
----------------
activation_code_id
user_id
activated_date

I would consider a bit of denormalisation - at the moment, to determine whether a user currently has access or not you have to look through potentially multiple records for that user in tbl_activation_code to see if there is an active record for that user.
So it might be worth adding a surrogate IDENTITY/autonumber field in tbl_activation_code, and adding a foreign key to that in tbl_user - this would point to the user's current activation code record, simplifying the scenarios where you need to find the current state of a user's access. This way, a user record will always reference directly their current activation code, plus you still have the full history of their previous codes.

Related

Grouping with associated variables

i have a table as below:
Account no. Login Name Numbering
1234 rty234 1
1234 bhoin1 1
3456 rty234 2
3456 0hudp 2
9876 cfrdk 3
From the table above, you can see that rty234 and bhoin1 registered a same account no of 1234, thus i know that rty234 and bhoin1 are related and i numbered them as 1. The numbering field was based on the account no.
Then I found that rty234 also registered another account no of 3456 and the same account no was registered by 0hudp as well. Thus, i concluded that rty234, bhoin1 and 0hudp are related. Therefore, i wanted to renumber the third and forth row to 1. If they are not further related, then just remain the numbering. How can i achieve that using mysql.
The expected output will be as follow:
Account no. Login Name Numbering New_Numbering
1234 rty234 1 1
1234 bhoin1 1 1
3456 rty234 2 1
3456 0hudp 2 1
9876 cfrdk 3 3
You need to understand how to design a relational database.
These groupings that you want to make with the New_Numbering field should be done at the time the accounts are registered. I see two pieces of arbitrary information that needs to be tracked: account number and login name. Seems like the people registering the account can type whatever they want here, effectively, perhaps account numbers must be numerical. That detail doesn't matter.
What you want here is one account which can have multiple account numbers associated with it, and multiple logins. I would also assume that future development may add more to this, for example - why do people need multiple logins? Maybe different people are using them, or different applications. Presumably, we could collect additional information about the login names that stores additional details about each login. The same could be said about account numbers - certainly they contain more detail than just an account number.
First, you need one main login table.
You describe rty234 and bhoin1 as if they are unique people. So make this is a login_name column which is a unique index in a login table. This table should have an auto-increment login_id as the primary key. Probably this table also has a password field and additional information about that person.
Second, create an account table.
After creating their login, make them register an account with that login. Make this a two-step process. When they offer a new account number, create a record for it in the account table with additional identifying information that only the account-holder would know. Somehow you have to validate that this is actually their account in order to create this record, I would think. This table would also contain an auto-incremented primary key called account_id in addition to account_no and probably other details about the account.
Third, create a login_account table.
Once you validate that a login actually should have access to an account, create a record here. This should contain a login_id and an account_id which connects these two tables. Additionally, it might be good to include the information provided which shows that this login should have access to this account.
Now, when you want to query this data, you can find groups of data that have the same login_id or account_id, or even that share either a login or an account with a specific registration. Beyond that, it gets hairy to do in an SQL query. So if you really want to be able to go through the data and see who is in the same organization or something, because they share either a login or an account with the same group, you have to have some sort of script.
Create an organization table.
This table should contain an organization_id so you can track it, but probably once you identify the group you'll want to add a name or additional notes, or link it to additional functionality. You can then also add this organization_id field to the login or account tables, so you can fill them once you know the organization. You have to think about if it's possible for two organizations to share accounts, and maybe there's a more complicated design necessary. But I'm going to keep it simple here.
Your script should load up all of the login_id and account_id values and cache them somewhere. Then go through them all and if they have an organization_id, put their login_id or account_id in a hashmap with the value as the organization_id. Then load up all of the login_account records. If either the login_id or account_id has an organization_id in its hashmap, then add the other to its hashmap with the same organization_id. (if there's already one there, it would violate the simple organization uniqueness assumption I made, but this is where you would handle complexity - so I would just throw an exception and see if it happens when I run the script)
Hopefully this is enough example to get you started. When you properly design a database like this, you allow the information to connect naturally. This makes column additions and future updates much easier. Good luck!

Table structure of active members for 4 years in sql

I am working on an achievement system and the users can unlock a badge when they have been active on the site for 4 years.
I tried to store each time a user in logged in but it's not really a good idea.
So my question is, how the table structure should be if I want to know if the user was active for 4 years?
It depends on what you mean by active, but if you mean that four years has elapsed since they first logged on, registered etc. then you just need a date field to store the account setup / logon etc. Then when they logon you can test to see if four years has past since that date and insert a badge accordingly.
You can save the last consecutive log in and the first log in. That's just two columns. Every time a user logs in, you can check if he logged in the day before. If yes, then update that column. If not then update the first log in because that's when the 4 year period will begin again. Let me know if that makes sense or if you need examples to help with understanding the thought process.
That is too generic a question to provide an full solution. By active for 4 years it sounds like you will need to track their actions. So perhaps some sort of transaction or history table that links their userid with a datetime plus actions performed.
Then you just have to define what "active" means such as having performed specific actions at least once per week, etc.
EDIT
First off I am a SQL Server developer, but have attempted to convert this to MySQL syntax for you.
~ = primay keys to ensure uniqueness & good performance
User table
~ UserID unique user id - could be an Identity, GUID or similar field
UserName unique user name
anything else you want to track such as First Name, Last Name, etc
UserAction table
~ UserID link to User table
~ ActionType number indicating what the user performed
1 = login
add in other types in the future if you ever want to track anything else
~ ActionDate datetime of the action
anything else that you may want to track such as the duration or end date (in this case that would be them logging out)
Database systems are designed to hold lots of data so you shouldn't have to worry too much about that unless space is a factor. You can always delete any data that is older than 4 or 5 years if you like.
Normally I would just use a CTE (common table expression) but apparently they won't be available until MySQL 8.0 [https://www.mysql.com/why-mysql/presentations/mysql-80-common-table-expressions/]
So instead here is another method. It assumes that you want a login within each given year. i.e. at least one login from 2017, one from 2016, 2015 & 2014. If instead you wanted at least one login going back 1 year from today and so on then we will need to modify this query.
-- this will return the number of years with at least one login, going as far back as 4 years
SELECT COUNT(1) AS NumberOfYearsWithLogin
FROM (
SELECT YEAR(CURDATE()) - YEAR(ActionDate) AS NumberOfYearsAgo
FROM UserAction
WHERE UserID = 123 -- put user of interest here
AND ActionType = 1 -- login
AND YEAR(CURDATE()) - YEAR(ActionDate) < 4 -- check the last 4 years
GROUP BY YEAR(CURDATE()) - YEAR(ActionDate) -- group by year number with today's year as number 0
) A
;

Three tables -- Use cascade relationships or add extras?

I am a PHP developer and have basic knowledge of MySQL yet I am working on a personal project so have to dig into it.
Billing details will be stored mimicking stripe objects. However, I am wondering if I should use indexes in cascade or use them on each table.
Let me explain through (a simplified) example. A user has a subscription. Each subscription raises an invoice a month. Each invoice raises one charge (or several if some failed.)
table_user
- user_id
- username
- password
table_subscription
- sub_id
- start_date
- end_date
- amount
- user_id
table_invoice
- invoice_id
- period_start
- period_end
- amount
- paid
- subscription_id
table_charge
- charge_id
- amount
- status
- failure_code
- failure_reason
- invoice_id
My point is that I want to quickly list each user's charges and invoices. One charge/invoice belongs to one user. Should I use uder_id key on just subscription (as charges and invoices are linked) or, should I still add user_id to both invoice and charge tables?
It's easier to SELECT but also if in the future I make charges not related to an invoice (buying a one-off extra) but linked to that user.
Looking forward to receiving some suggestions.
The simple answer is to use 'user_id' as a key for each table. If you have MySQL Workbench you can also link them via keys. If 'user_id' is a unique identifier for a user than it needs to be on every table where it is used. This way you do not have a table that is 'lost' and needs code or triggers to find the user. It is only one extra column per table and worth the effort. PHP will use it as an absolute point of reference back to the user, and so can your background routines. I would also archive the users id and IP address for a few years, just in case there is a payment issue and they close their account.

better schema for a database

i have an application in which a user can lock a certain commodity and once he locks it,he has to pick it up in 2 hours. If he doesnt pick it up in 2 hours then that item is unlocked and user loses 1 locking chance. the user has 3 locking chances initially and if he loses all 3 in 2 months time then he is banned for 2 months. now i have prepared a schema for this but i feel its not optimum. the schema looks like this
user table
--locking_chances_left //initially 3
--first_chance_miss_date //date on which the user loses its first locking chance
--second_chance_miss_date //date on which the user loses its second locking chance
--banned // boolean field to indicate whether the user is banned
locked_items table
--item_no
--user_id
--locking_time
banned_users table
--user_id
--ban_date //date on which the user is banned i.e lost the last chance
now i have a event which is scheduled to run every minute to see if any item in locked_items table has been locked for more than 2 hours and if it finds any then it removes it from this table which unlocks the item and then decreases locking_chances_left by 1 from the users table. now i have to keep track of whether a user loses all his chances in a period of 2 months to ban him. so i have kept first_chance_miss_date to keep the date when his chances decrease from 3 to 2 and second_chance_miss_date to keep the date when his chances decrease from 2 to 1. i have an after update trigger on users table that checks when the value of locking_chances_left is changed and it updates the first_chance_miss_date and second_chance_miss_date accordingly. is there some better way without using these 2 fields for miss dates and just using one field.
thanks for bearing this
I'd probably do this with a "user_missed_date" table with user_id and missed_date as fields you can then
select user_id, count(*) as misses from user_missed_date where date>[last two months] group by user_id
Or use that as the basis for a subquery.
You would probably want indexes on both user_id, missed_date and missed_date,user_id
I don't think this is a better solution, but I'll throw it out there:
You could have a table of lock_events, instead of locked_items. Every time an item gets locked, it goes in the event table. If an item gets picked up, you could either delete it, or you could add an additional event saying it was picked up. If you select items that are older than 2 hours, you get a list of expired locked items.
This way you have a history of all the events in the system. It's simple to calculate chances_left and also simple to see if the user burnt all his chances in a 2 month period. You end up doing more CPU cycles here, but you also get a nice record of all the transactions on your site!

Problem with an agenda/availability query

I have a mysql table with users and their weekly calendar.
Every user can set his own availability for the week (morning, afternoon, night / MON thru SAT), and that is not going to change often, almost never.
Imagine those users are personal trainers in a gym, or tennis courts you can book...
My problem here is to find the right query (or maybe even rethinking the way i'm storing that data in mysql) in order for an external web user to check availability of them based on 3 check buttons [o]morning, [o]afternoon, and [o]night
So I want my web user to go to my website and check/uncheck those buttons in order to see which one (personal trainer, or whatever) is available
So if I check Morning i can see only the people available, also (but not only), in the morning,(because a personal trainer can be available during the morning but also in the afternoon etc..)..
it may sounds an easy problem but i'm having hard time...
any help is appreciated
Thanks!
This isn't really an algorithm question, this is more of a DBA question. You'd most likely have a user table and an availability table.
user:
userid
...
availability:
userid
day
timeofday
When given a query such as Monday Wednesday Morning Afternoon (assuming the relationship is (Monday OR Wednesday) AND (Morning OR Afternoon)) you can do a query such as.
SELECT userid FROM availability WHERE day='wednesday' OR day is 'monday' AND timeofday='morning' OR timeofday='afternoon'
The answer to this question will be dependant on your DB structure. If your are storing the availabile times as 1, 2, 3 or any combo of such 12, 13, 123, 23 then you can simply use a
MYSQL Regular expression to limit your results based on the input checkbox criteria.
I would suggest somthing like:
SELECT trainer FROM trainer_table WHERE availaility regexp '[Limiting Criteria]'
In the above code, simply replace trainer with the name of your fields you wish to return. Then replace trainer_table with the name of your table and finally replace Limiting Criteria with your limiting text, be it 1 or 2 or 3 or any combination.
If you want more specific help, an example of your own table structure would be helpful.