Optimized SQL for timetable - mysql

I want to create a SQL table in my database that can hold a school timetable but every way i tried to proceed with it it was no optimal and over the course of 1-2 changes to the timetable the database table got a bit too big.
It goes a bit like this:
Mo Tu Wed Thu Fri
07:00
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
This is the basic layout of the timetable and in the table the first 2 columns are timetableid and classid. My first approach was to have columns for each day with the classes in one column but that was a bit buggy, my next approach was to get the columns like this mo_7, mo_8, mo_9 etc. until i reach fri_16 but that took up too much space.
So my question is, what is the most optimal way to save a timetable in a database, in what table layout.
Thank you in advance.

Your problem isn't your database, it's because you're trying to use it like a spreadsheet.
You need to backtrack a little - store a table of classes, along with the various attributes. If each class has only one session, then add 'time' as an attribute. If there's multiple sessions, your probably then need to separate out the schedule into a seperate table.
E.g.:
Table 'classes':
ClassID.
description
teacher
other stuff.
Table 'sessions':
session ID
ClassID
start_time
end_time
day
location
And then you'd use a select to query e.g.
SELECT classID, start_time, day, location
FROM sessions
WHERE day like "Monday"
ORDER BY start_time
Which'd give you your list for Monday. To extend it, you might add a join with the 'classes' table.
SELECT classes.description, classes.teacher, sessions.start_time, sessions.day, sessions.location
FROM sessions, classes
JOIN ON classes.ClassID = sessions.ClassID
WHERE day LIKE 'Monday'
ORDER BY sessions.start_time
Something like that anyway (apologies if the syntax is a bit off, I'll check/amend if I can).
But the point is - let the database store the actual data and use the queries to transform it into a layout that you like. That way you don't need to rewrite your database each time you want to change something minor.

Related

How to count days between 2 dates except holiday and weekend?

I started a HR management project and I want to count days between 2 dates without counting the holidays and weekends. So the HR can count employee's day off
Here's the case, I want to count between 2018-02-14 and 2018-02-20 where there is an office holiday on 2018-02-16. The result should be 3 days.
I have already created a table called tbl_holiday where I put all weekends and holidays in one year there
I found this post, and I tried it on my MariaDB
Here's my query:
SELECT 5 * (DATEDIFF('2018-02-20', '2018-02-14') DIV 7) +
MID('0123444401233334012222340111123400012345001234550', 7 *
WEEKDAY('2018-02-14') + WEEKDAY('2018-02-20') + 1, 1) -
(SELECT COUNT(dates) FROM tbl_holiday WHERE dates NOT IN (SELECT dates FROM tbl_holiday)) as Days
The query works but the result is 4 days, not 3 days. It means the query only exclude the weekends but not the holiday
What is wrong with my query? Am I missing something? Thank you for helping me
#RichardDoe, from the question comments.
In a reasonable implementation of a date table, you create a list of all days (covering a sufficient range to cope with any query you may run against it - 15 years each way from today is probably a useful minimum), and alongside each day you store a variety of derived attributes.
I wrote a Q&A recently with basic tools that would get you started in SQL Server: https://stackoverflow.com/a/48611348/9129668
Unfortunately I don't have a MySQL environment or intimate familiarity with it to allow me to write or adapt queries off the top of my head (as I'm doing here), but I hope this will illustrate the structure of a solution for you in SQL Server syntax.
In terms of the answer I link to (which generates a date table on the fly) and extending it by adding in your holiday table (and making some inferences about how you've defined your holiday table), and noting that a working day is any day Mon-Fri that isn't a holiday, you'd write a query like so to get the number of working days between any two dates:
WITH
dynamic_date_table AS
(
SELECT *
FROM generate_series_datetime2('2000-01-01','2030-12-31',1)
CROSS APPLY datetime2_params_fxn(datetime2_value)
)
,date_table_ext1 AS
(
SELECT
ddt.*
,IIF(hol.dates IS NOT NULL, 1, 0) AS is_company_holiday
FROM
dynamic_date_table AS ddt
LEFT JOIN
tbl_holiday AS hol
ON (hol.dates = ddt.datetime2_value)
)
,date_table_ext2 AS
(
SELECT
*
,IIF(is_weekend = 1 OR is_company_holiday = 1, 0, 1) AS is_company_work_day
FROM date_table_ext1
)
SELECT
COUNT(datetime2_value)
FROM
date_table_ext2
WHERE
(datetime2_value BETWEEN '2018-02-14' AND '2018-02-20')
AND
(is_company_work_day = 1)
Obviously, the idea for a well-factored solution is that these intermediate calculations (being general in nature to the entire company) get rolled into the date_params_fxn, so that any query run against the database gains access to the pre-defined list of company workdays. Queries that are run against it then start to resemble plain English (rather than the approach you linked to and adapted in your question, which is ingenious but far from clear).
If you want top performance (which will be relevant if you are hitting these calculations heavily) then you define appropriate parameters, save the lot into a stored date table, and index that table appropriately. This way, your query would become as simple as the final part of the query here, but referencing the stored date table instead of the with-block.
The sequentially-numbered workdays I referred to in my comment on your question, are another step again for the efficiency and indexability of certain types of queries against a date table, but I won't complicate this answer any further for now. If any further clarification is required, please feel free to ask.
I found the answer for this problem
It turns out, I just need to use a simple arithmetic operator for this problem
SELECT (SELECT DATEDIFF('2018-02-20', '2018-02-14')) - (SELECT COUNT(id) FROM tbl_holiday WHERE dates BETWEEN '2018-02-14' AND '2018-02-20');

Mysql Function multiple entries in one cell

So I'm working on a schedule system for my job a basically i wanted to know if there is a way where mysql can do something like:
|Monday |tuesday|wendsday|total
|Dan |5am-7am |7am-6pm|6am-11am|
11am-2pm| |2pm-7pm |
5pm-12am|
where i can enter multiple shifts on 1 day for each person in the same cell if needed instead of the name repeating several times like:
Dan|5-4|
Dan|6-8|
and if there is a function to calculate total time in one cell with multiple shifts
There is a way (representing the data as string), but you wouldn't want to do this - you will loose all calculations, searches etc.
You should not try to represent the data in the database exactly as how it looks on paper.
I would make a table like this:
ShiftID|Person|StartTime|EndTime
Making StartTime & EndTime columns of type DATETIME, you will store not only the HH:mm of a shift's start, but also the day. This is helpful when you have a shift which starts on one day and ends in the next, like starting on Monday 2017-05-15 23:00 and ending on Tuesday 2017-05-16 02:00.
You can extract the date only from this filed using MySQL DATE() function and select only those entires which start OR end on this day.
To calculate the shift's duration you can use MySQL function TIMESTAMPDIFF()
You can even use DAYOFWEEK() to get if it is Monday, Tuesday, etc.
About duplicating the person's name - I would make another table, which will match users with their data to IDs an use ID in the column Person, but for a starter and if your data is not big and if speed is not an issue and if typo errors (like Den instead of Dan) are not a problem ... you could use the name directly in this table.
After storing the data in a table like this you could represent it as you wish in HTML (or print).
You can create a third table with the following columns:
person_id int,
start_time datetime,
end_time datetime
Where person_id would be foreign key to Person table and start_time and end_time would be datetime columns. You can then store multiple records for a person in this table and use MySQL's date functions with GROUP BY to generate the report similar to the one in question.

Storing date periods in database

I would like to discuss the "best" way to storage date periods in a database. Let's talk about SQL/MySQL, but this question may be for any database. I have the sensation I am doing something wrong for years...
In english, the information I have is:
-In year 2014, value is 1000
-In year 2015, value is 2000
-In year 2016, there is no value
-In year 2017 (and go on), value is 3000
Someone may store as:
BeginDate EndDate Value
2014-01-01 2014-12-31 1000
2015-01-01 2015-12-31 2000
2017-01-01 NULL 3000
Others may store as:
Date Value
2014-01-01 1000
2015-01-01 2000
2016-01-01 NULL
2017-01-01 3000
First method validation rules looks like mayhem to develop in order to avoid holes and overlaps.
In second method the problem seem to filter one punctual date inside a period.
What my colleagues prefer? Any other suggestion?
EDIT: I used full year only for example, my data usually change with day granularity.
EDIT 2: I thought about using stored "Date" as "BeginDate", order rows by Date, then select the "EndDate" in next (or previous) row. Storing "BeginDate" and "Interval" would lead to hole/overlap problem as method one, that I need a complex validation rule to avoid.
It mostly depends on the way you will be using this information - I'm assuming you do more than just store values for a year in your database.
Lots of guesses here, but I guess you have other tables with time-bounded data, and that you need to compare the dates to find matches.
For instance, in your current schema:
select *
from other_table ot
inner join year_table yt on ot.transaction_date between yt.year_start and yt.year_end
That should be an easy query to optimize - it's a straight data comparison, and if the table is big enough, you can add indexes to speed it up.
In your second schema suggestion, it's not as easy:
select *
from other_table ot
inner join year_table yt
on ot.transaction_date between yt.year_start
and yt.year_start + INTERVAL 1 YEAR
Crucially - this is harder to optimize, as every comparison needs to execute a scalar function. It might not matter - but with a large table, or a more complex query, it could be a bottleneck.
You can also store the year as an integer (as some of the commenters recommend).
select *
from other_table ot
inner join year_table yt on year(ot.transaction_date) = yt.year
Again - this is likely to have a performance impact, as every comparison requires a function to execute.
The purist in me doesn't like to store this as an integer - so you could also use MySQL's YEAR datatype.
So, assuming data size isn't an issue you're optimizing for, the solution really would lie in the way your data in this table relates to the rest of your schema.

Database schema and logic for this

I am trying to create a timetable for classes in MySQL. The columns are the days of the week and the rows are the time intervals ( i.e. 7:00 - 8:00 ). I am also trying to have 30-minute time intervals and when a particular subject is 2 hours, it will span 4 rows. Each cell contains the name of the course and room. This is part of a small program I am trying to develop. So far, I know what data to insert but I'm stuck on creating the right database schema. Really need the logic on how to get this started (i.e. what data types should I use ). Thanks!
How does this sound:
CREATE TABLE TimeTable (
ID int(8) NOT NULL AUTO_INCREMENT,
Subject varchar(32) NOT NULL,
Room varchar(8) NOT NULL,
StartTime datetime NOT NULL,
EndTime datetime NOT NULL,
Day enum('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday') NOT NULL,
PRIMARY KEY (ID)
);
You only store the data about the subjects.
The other stuff you mentioned is really down to whatever language you're using to output the data. You can't really put intervals in the middle of a database. As Damien_The_Unbeliever said, databases aren't exactly spreadsheets.
Hope this make sense.
You can set one column for day (you can value days from 1 to 7), one column for starting time and one column for finish time. This way your queries will become more simple.
Example:
Day | Start | Finish
-----------------------------
1 8:30 12:00
1 13:00 15:00
This will also come in handy when you don't want to stuck with 30 minutes.
The columns are the days of the week and the rows are the time intervals ( i.e. 7:00 - 8:00 )
That's a bad start. Your columns should be properties of a single lesson and your rows should be all the different lessons throughout the week.
Your columns could be Day of Week, Lesson of Day, Duration, Subject, Room, Teacher, et cetera. In this example, your primary key would be (Day of Week, Lesson of Day).
This way, if you want the schedule for a particular day you just select everything with Day of Week = 2 to get the schedule for Tuesday for example.
Each cell contains the name of the course and room.
This also defys the laws of normalization. You should always have no more than one value per "cell".
Really need the logic on how to get this started (i.e. what data types should I use )
Use the most intuitive type. When you store a number choose integer, when storing few characters choose varchar(n), when using lots of characters use text and so on...
I would create it this way where it is normalized and gives more chance to expand this app further.
Tables (attributes):
timeslots(id, start_time, end_time)
subjects(id, name)
timetable(timeslot_id, subject_id)

Select day of week from date

I have the following table in MySQL that records event counts of stuff happening each day
event_date event_count
2011-05-03 21
2011-05-04 12
2011-05-05 12
I want to be able to query this efficiently by date range AND by day of week. For example - "What is the event_count on Tuesdays in May?"
Currently the event_date field is a date type. Are there any functions in MySQL that let me query this column by day of week, or should I add another column to the table to store the day of week?
The table will hold hundreds of thousands of rows, so given a choice I'll choose the most efficient solution (as opposed to most simple).
Use DAYOFWEEK in your query, something like:
SELECT * FROM mytable WHERE MONTH(event_date) = 5 AND DAYOFWEEK(event_date) = 7;
This will find all info for Saturdays in May.
To get the fastest reads store a denormalized field that is the day of the week (and whatever else you need). That way you can index columns and avoid full table scans.
Just try the above first to see if it suits your needs and if it doesn't, add some extra columns and store the data on write. Just watch out for update anomalies (make sure you update the day_of_week column if you change event_date).
Note that the denormalized fields will increase the time taken to do writes, increase calculations on write, and take up more space. Make sure you really need the benefit and can measure that it helps you.
Check DAYOFWEEK() function
If you want textual representation of day of week - use DAYNAME() function.