It is easy to store user credit with an extra col in the user table, e.g. user_credits, now an extra requirement
for each credit added to the user_credits col, it will auto expire one year after if not being consumed.
Definitely I need an extra table for storing expiring information, e.g.
[table credits_history]
user_id
credits
used
created_at
So, when consuming credits, I need to
check if the user_credits is enough to consume
loop all credits_history table for the user's credit which used = 0 AND now - created_at < 1yr, and set used to 1
update the user_credits
Finally I need to set a daily cron job to update user_credits by looking at the created_at
Are the above approach reasonable? Or any standard way to handle the above requirements?
Don't store things that you can compute unless it's too expensive to calculate. Assuming that the number of rows in credits_history per user is not too high, you can structure your queries in such a way as to check the sum of available credits:
SELECT SUM(credits-consumed) -- see more about "consumed" below
FROM credits_history
WHERE user_id=?
AND created_at >= DATE_SUB(NOW(),INTERVAL 1 YEAR)
A nice side benefit of this approach is that you no longer need a cron job to maintain user_credits.
Consuming credits becomes a little tricky: you need to add a column showing the portion of the credit that has been used. Here is an example to illustrate this point. Let's say a user added these credits in his history:
created_on credits consumed
---------- ------- --------
05/06/2014 50 0
07/12/2014 70 0
12/01/2014 40 0
If you run the query above, you would get a sum of 160.
Now he wants to consume 90 credits. Your code should query all eligible credit records, order them from oldest to newest, and use as much credits as needed to cover the desired amount (i.e. 90)
created_on credits consumed
---------- ------- --------
05/06/2014 50 50
07/12/2014 70 40
12/01/2014 40 0
Now the query above would get a sum of 70.
No I don't think this is a normal approach. Normally you would have a table like user_credits which can have multiple rows for each user, each such row has the REMAINING credit, and expiry date (I think I prefer expiry date rather than created_at, since you calculate it only once). But then you would have a Transactions table which lists credits/debits (positive/negative credits). Buying credits would create a transaction of that amount of credits being added. Using credits would create a transaction of that amount of credits being deducted. Expiring credits would just be another transaction of credits being deducted. Each transaction also updates the remaining credit in user_credits.
The only annoying thing is that to calculate a user's current credits you have to go to user_credits and add up all of that user's rows. Some people might track another value in the main user table to bypass it.
Expiring credits can be done through a cron job to tidy up that table. Although if you want to be stingy you could check the expiry date at the time of the transaction as well to make sure nobody sneaks through just before the cron job.
I would do it as follows
TABLE: USER (id,name,address,phone,etc)
TABLE: CREDIT_TYPE(id,description, expiration_policy, etc)
TABLE: USER_CREDIT(user_id, credit_type_id, activation_date, expiration_date)
A user has zero or many credits
Each credit has some sort of expiration policy, rules, etx
If the CURRENT_DATE > expiration_date, the credit is expired
The expiration date is set immediately based on the policy set forth on the credit_type
Would that work?
Related
Lets say I have a Table tbl_Room with a column taken (boolean) and a Customer wants to rent this room for a short period.
Now, can I tell mysql to change the value of taken automatically depending on the timestamp, e. g. if the rent time/period is over, the value of taken should set automatically to false.
Or do I need to update my database with CRON or some other script that runs on the server periodically?
Please use mysql event to manage it.
CREATE EVENT [IF NOT EXIST] event_name
ON SCHEDULE schedule
DO
event_body
Reference
Under event_body you can write select statement to check period and then update table if period is over.
The best way to handle this sort of time-based request is counterintuitive.
Don't try to update the table at a specific time. Instead, include a timestamp column called something like lease_expires_at.
When you rent a room, update the row to set the value of lease_expires_at to the time at which the rental period expires. For example, if you rent a room for 30 minutes, starting now, do this.
UPDATE room
SET lease_expires_at = NOW() + INTERVAL 30 MINUTE
WHERE room_number = whatever
If you want to know whether a room is presently (NOW()) taken, do this:
SELECT room_number,
CASE WHEN lease_expires_at IS NULL THEN 0
WHEN lease_expires_at <= NOW() THEN 0
ELSE 1 END taken
FROM room
WHERE room = whatever
If you want to know whether a room will be available one hour from now (NOW() + INTERVAL 60 MINUTE), do this:
SELECT room_number,
CASE WHEN lease_expires_at IS NULL THEN 0
WHEN lease_expires_at <= NOW() + INTERVAL 60 MINUTE THEN 0
ELSE 1 END taken
FROM room
WHERE room = whatever
Then, once in a while, but not in any time-critical way, you can clean things up using a query like this
UPDATE room SET lease_expires = NULL WHERE lease_expires <= NOW()
You can use an event, or an overnight cronjob, or whatever you wish, to do this cleanup. The integrity of your application doesn't depend on exactly when this job runs.
The advantage of this should be clear: If you rely on some regularly running process to set an taken column value, and that process doesn't run or runs late, you get bad results. When you rely on the time, you get accurate results.
There's a small boundary-condition detail in this design. By using <= in my queries, I'm choosing to have the lease_expires_at timestamp represent the very first moment at which the room is available for another lease, not the last moment of the present lease. That's a handy choice, because if you put something like 2017-11-2017 11:00:00 into lease_expires_at, and somebody says "is the room available at 11:00?" you want to be able easily to say "yes." The guy who rented it at 10:30 gets it until the moment before 11:00.
you can use jquery time picker....after u can create a if loop in which JavaScript time function will check current time...to its original time...if condition is satisfied...we can change the mysql taken function
I have designed a database schema for a subscription product. user can select subscription starting from a date for certain amount of days. User can cancel a subscription for some days while still keeping the subscription active afterwards. meaning if user subscribes for a month she can cancel it on days say 10,15 and 20 thus paying for only 27 days (30 minus 3).
So far I have come up with this schema.
each user has one profile.
user can select a plan.
once user selects a plan it is noted as transaction which also stores information about start date and duration of that plan.
each transaction has payment (focusing on that part later)
Now, since user can cancel subscription any day how should I keep track of different users and days on which they had subscription?
The solution that I have in mind is crate a new table Plan_Transaction_user which will keep track of each date and transaction ID for that date. This way If user cancels her subscription on particular date there will be no record of that date for that transaction ID.
table will look like this:
Date Transaction ID
1-1-2017 1
1-1-2017 2
1-1-2017 3
1-2-2017 1
1-2-2017 3
Since user associated with transaction_id 2 cancelled for day 2 her transaction record is not present in this table.
Now if I have customer base of say 5000 then in best case within one year I will have 5000 * 365 ~ 1.8m rows. I am sure this is not best approach to go about it. Could you please suggest me any better schema or some changes in existing schema which can be more efficient? Just in case you want to know I will be using MariaDB (AWS RDS) as a database and Python 2 as my language.
Thank you,
Ojas
You can add a end_date field in Transaction table instead of duration. You can easily defined end_date as start_date + how many days you will given for selected plan. When user cancel some days then you can reduce end_date as end_date = end_date - number of cancel days. You can check how many valid subscription currently at any days checking through end_date >= today.
Similar to your Plan_Transaction_user design, if u only need to know how many subscribers for any particular date, but not who they are, u can aggregate the table by day. Like
Date user_count
1-1-2017 1
1-2-2017 2
Apologies for the awful title, but I can't think of a better way of explaining this.
I have a field for each ID which can either be 0 or 1, and changes once per day based on the activity of stock in a warehouse. I.e. In stock / out of stock. This is purely used as an approximate percentage for reporting.
E.g. 10 days at 1, 90 days at 0 = 10%
What I'm doing right now is running a cron script once a day to store the current value (with timestamp) in another table, then easily working out the percentage that way.
This is working, but there must be a more efficient method? With 100,000 unique IDs for example, this equates to 26,500,000 rows per year. You can see the problem there.
I can't think of a more efficient way. Maybe there isn't one.
You can query it dynamically using a simple select:
select count(if(field_name=1,1,null))/count(*)*100 as percentage_of_one from yourtable
field_name is the field which has to be 1 to be counted.
Right now I am developing Hotel reservation system.
so I need to store prices on certain date/date range for future days, so the price varies on different days/dates. so I need to store those price & date details in to db. i thought of 2 structures .
1st model :
room_prices :
room_id :
from_date :
to_date :
price:
is_available:
2nd design:
room_prices :
room_id :
date:
price:
is_available
so i found the 2nd method is easy. but data stored grows exponentially
as my hotel list grows.
say suppose if i want to store next(future) 2 months price data for one hotel i need to create 60 records for every hotel.
in case of 1st design, i don't require that many records.
ex:
```
price values for X hotel :
1-Dec-15 - 10-Dec-15 -- $200,
1st design requires : only 1 row
2nd design requires : 10 rows
```
i am using mysql,
does it have any performance degradation while searching
room_prices table.
would someone suggest me any better design ?
I have actually worked on designing and implementing a Hotel Reservation system and can offer the following advice based on my experience.
I would recommend your second design option, storing one record for each individual Date / Hotel combination. The reason being that although there will be periods where a Hotel's Rate is the same across multiple days it is more likely that, depending on availability, it will change over time and become different (hotels tend to increase the room rate as the availability drops).
Also there are other important pieces of information that will need to be stored that are specific to a given day:
You will need to manage the hotel availability, i.e. on Date x there
are y rooms available. This will almost certain vary by day.
Some hotels have blackout periods where the hotel is unavailable for short periods of time (typically specific days).
Lead Time - some Hotels only allow rooms to be booked a certain
number of days in advance, this can differ between Week days and
Weekends.
Minimum nights, again data stored by individual date that says if you arrive on this day you must stay x number of nights (say over a weekend)
Also consider a person booking a week long stay, the database query to return the rates and availability for each day of that stay is a lot more concise if you have a pricing record for each Date. You can simply do a query where the Room Rate Date is BETWEEN the Arrival and Departure Date to return a dataset with one record per date of the stay.
I realise with this approach you will store more records but with well indexed tables the performance will be fine and the management of the data will be much simpler. Judging by your comment you are only talking in the region of 18000 records which is a pretty small volume (the system I worked on has several million and works fine).
To illustrate the extra data management if you DON'T store one record per day, imagine that a Hotel has a rate of 100 USD and 20 rooms available for the whole of December:
You will start with one record:
1-Dec to 31st Dec Rate 100 Availability 20
Then you sell one room on the 10th Dec.
Your business logic now has to create three records from the one above:
1-Dec to 9th Dec Rate 100 Availability 20
10-Dec to 10th Dec Rate 100 Availability 19
11-Dec to 31st Dec Rate 100 Availability 20
Then the rate changes on the 3rd and 25th Dec to 110
Your business logic now has to split the data again:
1-Dec to 2-Dec Rate 100 Availability 20
3-Dec to 3-Dec Rate 110 Availability 20
4-Dec to 9-Dec Rate 100 Availability 20
10-Dec to 10-Dec Rate 100 Availability 19
11-Dec to 24-Dec Rate 100 Availability 20
25-Dec to 25-Dec Rate 110 Availability 20
26-Dec to 31-Dec Rate 100 Availability 20
That is more business logic and more overhead than storing one record per date.
I can guarantee you that by the time you have finished your system will end up with one row per date anyway so you might as well design it that way from the beginning and get the benefits of easier data management and quicker database queries.
I think that the first solution is better and as you already noticed it reduce the number of storage you need to store prices. Another possible approach could be : having a single date and assuming that the date specified is valid up until a new date is found, so basically the same structure you designed in the second approach but implementing an internal logic in which you override a date if you find a new date for a specified period.
Let's say that you have ROOM 1 with price $200 from 1st December and with price $250 from 12 December, then you will have only two rows :
1-Dec-15 -- $200
12-Dec-15 -- $250
And you will assume in your logic that a price is valid from the specified date up until a new price is found.
(Table names in quotes)
Let's say there are "users" that try to sells "products". They earn a commission on all "product_sales" (id, product_id, user_id, total, sale_date). I want to somehow store their commission rate based on certain dates. For example, a user will earn 1% from 2015-01-01 to 2015-01-15, 2% from 2015-01-16 to 2015-01-28, and 3% from 2015-01-29 onwards.
I want to run a query that calculates the total commissions for a user in January.
I want to run a query that calculates daily earnings in January.
How do I store the commission rates? One idea was having a table "user_commissions" that has (id, user_id, commission_rate, from_date, to_date). It would be easy to calculate the rate for (1) if commissions stayed the same, in which case I'd do this:
SELECT (sum(total) * 0.01) as total_commissions FROM product_sales WHERE user_id = 5 and sale_date between '2015-01-01' and '2015-01-31'
But with commission rates variable this is more complex. I need to somehow join the commissions table on each sale to get the right totals.
Another question would be:
How do I store the users' current commission rate that doesn't have an expiration date and include that in the reports? In my example, "3% from 2015-01-29 onwards". This has no end date.
Your table structure is a very reasonable structure and often used for slowly changing dimensions. Storing the effective and end dates in the structure is important for efficiency.
One way to store to_date for the most recent commission is to use NULL. This allows you to do:
select *
from commissions
where to_date is null
to get the most recent record.
However, a better way is to use some far distant date, such as '9999-12-12'. This allows you get the most recent commission using:
where curdate() between from_date and to_date
This is an expression that can also make use of an index on from_date, to_date.
Honestly, I would store user commission percentages and the effective dates of those commissions in one table.
TABLE: COMMISSION
user_id, date_effective, commission
In the other table I would store sales data. With each sale, I would keep the commission the salesperson got on the sale. (Added bonus, you can change the commission on any sale, like an override of sorts.)
TABLE: SALE
sale_id, sale_date, user_id, sale_amount, commission
When you create the row in your program, you can grab the correct commission rate using the following query:
SELECT commission from commission WHERE user_id=[user's id] AND date_effective<=[sale date, today] ORDER BY date_effective ASC;
MySQL Left Joins, and SQL in general, can get really tricky when trying to join on dates that don't exactly match up. (Looking back, basically.) I am struggling with the same problem right now without the luxury of the solution I just suggested.
(This whole post is based on the assumption that you aren't going to be directly interacting with this database through a DBMS but instead through an application.)