I am trying to create an event calendar with recurring events (ex. weekly or monthly) but I cannot wrap my head around it. Can anyone give me some pointers? What is the best way to go about doing this? Any help is greatly appreciated. Thanks.
Create three tables with a structure like:
event table
-id
-schedule_type
-schedule_id
-title
etc.
schedule table
-id
-event_id
-datetime
schedule_recurring table
-id
-event_id
-date
-time
In your event table, the schedule_type field will be either 0 or 1, which would indicate to the application which table the scheduling information is stored in, as well as how to interpret that information.
A non-recurring event will be stored in schedule with a datetime: 2011-09-06 00:00:00, and recurring events will be stored in schedule_recurring with a date: 'every 1st Monday' and a time: 09:30,12:20 (If the event occurs twice on every first Monday of the month).
Maybe this will help get you started!
I know this is an old post but I was thinking the same thing too.
I like the persons solution about using multiple tables to do it, but in fact it can all be done from one table.
Create a table with the following data...
event_title
event_text
event_image
and_other_fields_about_event
recur_code (text)
recur_mode (integer)
start_date (date)
end_date (date)
recur_end_date (date)
recur_mode can have three states -
0 = no recurrence
1 = recurrence with end date
2 = ongoing with no end date (e.g. if you want to add something like 1st Jan as New Years Day)
recur_code would store either NULL or a code in it if the date recurs. The code that should be used there would be the PHP DateInterval code (i.e. P1Y for 1 year or P3M (3 months) or P7D (7 days), etc) - if you want to shrink the data a bit you could chop off the initial 'P' and add it back later as the initial 'P' is always going to be P, it stands for Period so "Period 3 Months" "Period 7 Days", etc.
Then when your retrieving data from the database - you retrieve all data with the following searches
( end_date >= CURDATE () ) OR ( ( recur_mode = 1 ) AND ( recur_end_date >= CURDATE () ) ) OR ( recur_mode = 2 )
(please note this isn't proper SQL - it's just a basic example of the or statement you'd need)
then once you've retrieved the data use PHP with a while loop and DateInterval to increase the start_date until you get to the next re-occurrence also making sure that if recur_mode is set to 1 the start date is not after the recur_end_date.
All done in one table - and also if you want an input form to put the code in then use a hidden field with the dateinterval value in whilst using various radio buttons to select the interval - then use jQuery onchange to update the hidden value with the new selector values.
Related
I have a structural MySQL question about storing events in a database with dates.
Say that an organiser would select a range of dates, eg:
["19/12/2014","20/12/2014","26/12/2014","27/12/2014","02/01/2015","03/01/2015","09/01/2015","10/01/2015"]
The event needs to be saved into a table, I'm thinking about creating a many-to-many table with the structure:
event_id | start_date | end_date
Now when thinking about it, this would mean that I'd need to convert the date array into an array of object with beginning - end date.
Now the alternative would be to just create a table that looks like this:
event_id | event_date
And create a separate record for every date.
The purpose is obviously to check which events should be sent back to the client within a given date range.
Which of the two options seems to common / viable?
It is pretty crucial for the setup.
Depends. If the first event ends on the date of the second event, you can go with event_id | event_date, but otherwise I'd go with the first option.
If you don't have the end date somehow, then how will you be able to tell the client the range of dates for the event?
I would go with setup that contains event duration (in seconds) - it's flexible.
event_id (int) | start_date (datetime) | duration (int)
In this case when event duration does not matter - put 0 there in other case just put the number o seconds so you will be able to store event which lasts days or just a few hours or minutes.
my aim is to be able to incriment student form each year
i.e form 1 next year to be form 2.i tried using **mysql events **but seem to be complicated for my situation.
i have the following tables
id student_id form year
----------
1 2013-04-04888 1 2013
2 2013-04-01920 2 2013
after one year i want to have something like this
id student_id form year
----------
1 2013-04-04888 1 2013
2 2013-04-01920 2 2013
3 2013-04-04888 2 2014
4 2013-04-01920 3 2014
any one with the idea or suggestion on how i can do in this the proffesional way
Since this is a process that runs once a year, I'd create an external process that runs that query (one more page in your admin module, if this is a web app, or something similar depending on what you're doing); that way, users can control exactly when it happens, and also it is very simple to implement.
A cron job or some other agent maybe?
The insert would be something like this:
INSERT INTO people(student_id,form,year)
SELECT student_id,max(form)+1,max(year)+1
FROM people
WHERE form < 10 -- or something
GROUP BY student_id
It sounds like you have a poor database design here. You have a student, and for each student you want to know what form they are in. You seem to have access to the year they joined, and so you could store a "form 1" year.
For sake of precision lets say that the form year starts on the 1st of April.
The database table would look like
students (
-- added an int primary key for use later
id INT PRIMARY,
-- update this as needed
student_id VARCHAR(20) UNIQUE ,
yearStartedForm1 INT UNSIGNED
)
Using your example you might have the following records
id student_id yearStartedForm1
1 2013-04-04888 2013
2 2013-04-01920 2012
From this "form 1" year you could then calculate their current year by using something like the following two. Firstly if you wanted to replace form>4 with 'completed'
SELECT
d.id,
d.student_id,
IF(d.form > 4,'completed',d.form) AS form,
d.yearStartedForm1
FROM (
SELECT
s.id,
s.student_id,
v.currentFormYear - s.yearStartedForm1 + 1 AS form,
s.yearStartedForm1
FROM students s,(
SELECT IF(MONTH(CURRENT_DATE)<4,YEAR(CURRENT_DATE)-1,YEAR(CURRENT_DATE)) AS currentFormYear
) v
) d
Secondly if you wanted to limit form to a max of 5
SELECT
s.id,
s.student_id,
LEAST(v.currentFormYear - s.yearStartedForm1 + 1,5) AS form,
s.yearStartedForm1
FROM students s,(
SELECT IF(MONTH(CURRENT_DATE)<4,YEAR(CURRENT_DATE)-1,YEAR(CURRENT_DATE)) AS currentFormYear
) v
This would give you the current form number dynamically without the need for duplicate rows. Of course you could change yearStartedForm1 into a DATE field easily enough, and work off DATEDIFF or similar functions. The idea is still the same, as at present you are duplicating each student row when in fact all you are trying to do is derive a form number based on time having elapsed.
I am trying to create an event calendar which whilst initially quite small could turn out to be quite large. To that end, when trying to future proof it as much as possible, all events that occur in the past will be deleted from the database. However, is it bad practise to alter the start date of recurring events once they have happened to indicate when the next event will start? This makes it easier to perform search queries because theoretically no events will start more than say a week in the past, depending on how often the database is updated.
Is there a better way to do this?
My current intention is to have a table listing the event details along with a column for whether it is a yearly, monthly, weekly or daily recurrence. When somebody then searches for events between 2 dates, I simply look at each row and check if (EVENT START <= SEARCH FINISH && EVENT FINISH >= SEARCH START). This then gets all the possible events, and the recurring ones then need to be checked to see if they occur during the time period given. This is where I come a little unstuck, as to how to achieve this specifically. My thoughts are as follows:
Yearly: if EVENT START + 1 YEAR <= SEARCH FINISH || EVENT FINISH + 1 Year >= SEARCH START; repeat for +2 YEARS etc until EVENT START + NO YEARS > SEARCH FINISH.
Monthly: As above but + 1 month each time.
Weekly: As above but EVENT START and EVENT FINISH will be plus 7 DAYS BETWEEN RECURRENCE each iteration until EVENT START + 7 DAYS REPEATED > SEARCH FINISH.
Daily: As above but NO OF DAYS DIFFERENCE instead of 7 days for a week. This could be used to specify things like every 14 days (fortnight), every 10 days. Even every week could use this method.
However, when I think about the query that would have to be built to achieve this, I cannot help think that it will be very cumbersome and probably slow. Is there a better way to achieve the results I want? I have still not found a way to do things like occurs on the first Monday of a month or the last Friday of a month, or the second Saturday of April each year. Are these latter options even possible?
-- Edit: added below:
It might help a bit if I explain a bit more about what I am creating. That way guidance can be given with respect to that.
I am creating a website which allows organisations to add events, whether they are a one-off or recurring (daily, weekly, monthly, first Tuesday of a month etc.). The user of the site will then be able to search for events within a chosen distance (arbitrary 10, 25, 50, 100miles, all of country) on a set date or between 2 given dates which could be from 1 day apart up to a couple of years apart (obviously events that far into the future will be minimal or non-existant depending on the dates used).
The EVENTS table itself currently holds a lot of information about the event, such as location, cost, age group etc. Would it be better to have this in a separate table which is looked up once it has been determined if the event is within the specified search parameters? Clearly not all of this information is needed until the detailed page view, maybe just a name, location, cost and brief description.
I appreciate there are many ways to skin a cat but I am unsure how to skin this one. The biggest thing I am struggling with is how to structure my data so that a query will know if the recursion is within the specified date. Also, given that the mathematics to calculate distance between 2 lat/longs is relatively complex, I need to be able to build this calculation into my query, otherwise I will be doing the calculation in PHP anyway. Granted, there will be less results to process this way, but it still needs to be done.
Any further advice is greatly appreciated.
Creating events for each recurrence is unnecessary. It is much better to store the details that define how the event recurs. This question has been answered many times on SO.
One way to do this is to use a structure like this -
tblEvent
--------
id
name
description
date
tblEventRecurring
-----------------
event_id
date_part
end_date
Then you could use a query like this to retrieve events -
SELECT *
FROM `tblEvent`
LEFT JOIN `tblEventRecurring`
ON `tblEvent`.`id` = `tblEventRecurring`.`event_id`
WHERE (`tblEvent`.`date` = CURRENT_DATE AND `tblEventRecurring`.`event_id` IS NULL)
OR (
CURRENT_DATE BETWEEN `tblEvent`.`date` AND `tblEventRecurring`.`end_date`
AND (
(`tblEventRecurring`.`date_part` = 'D') OR
(`tblEventRecurring`.`date_part` = 'W' AND DAYOFWEEK(`tblEvent`.`date`) = DAYOFWEEK(CURRENT_DATE)) OR
(`tblEventRecurring`.`date_part` = 'M' AND DAYOFMONTH(`tblEvent`.`date`) = DAYOFMONTH(CURRENT_DATE))
)
)
UPDATE Added the following example of returning events for a given date range.
When returning dates for a given date range you can join the above query to a table representing the date range -
SET #start_date = '2012-03-26';
SET #end_date = '2012-04-01';
SELECT *
FROM (
SELECT #start_date + INTERVAL num DAY AS `date`
FROM dummy
WHERE num < (DATEDIFF(#end_date, #start_date) + 1)
) AS `date_list`
INNER JOIN (
SELECT `tblEvent`.`id`, `tblEvent`.`date`, `tblEvent`.`name`, `tblEventRecurring`.`date_part`, `tblEventRecurring`.`end_date`
FROM `tblEvent`
LEFT JOIN `tblEventRecurring`
ON `tblEvent`.`id` = `tblEventRecurring`.`event_id`
WHERE `tblEvent`.`date` BETWEEN #start_date AND #end_date
OR (`tblEvent`.`date` < #end_date AND `tblEventRecurring`.`end_date` > #start_date)
) AS `events`
ON `events`.`date` = `date_list`.`date`
OR (
`date_list`.`date` BETWEEN `events`.`date` AND `events`.`end_date`
AND (
(`events`.`date_part` = 'D') OR
(`events`.`date_part` = 'W' AND DAYOFWEEK(`events`.`date`) = DAYOFWEEK(`date_list`.`date`)) OR
(`events`.`date_part` = 'M' AND DAYOFMONTH(`events`.`date`) = DAYOFMONTH(`date_list`.`date`))
)
)
WHERE `date_list`.`date` BETWEEN #start_date AND #end_date
ORDER BY `date_list`.`date`;
You can replace the SQL variables with PHP vars if you would prefer. To display days without any events you can change the INNER JOIN between the two derived tables, date_list and events, to a LEFT JOIN.
The table dummy consists of a single column with numbers from 0 to whatever you anticipate needing. This example creates the dummy table with enough data to cover one month. You could easily populate it using an INSERT... SELECT... on the AI PK of another table -
CREATE TABLE `dummy` (
`num` SMALLINT UNSIGNED NOT NULL PRIMARY KEY
);
INSERT INTO `dummy` VALUES
(00), (01), (02), (03), (04), (05), (06), (07), (08), (09),
(10), (11), (12), (13), (14), (15), (16), (17), (18), (19),
(20), (21), (22), (23), (24), (25), (26), (27), (28), (29),
(30), (31);
Break it up
Have one table for vents that haven't happened yet with a reccurring event ID. So you can just poke one offs in there with recurring veent id of null. Get rid /archive past ones etc.
Have another for the data about recurring events.
When an event marked as recurring happens, go back to recurring table, check to see if it's enabled (you might want to add a range to them ie do this every wek for three months), and if all is okay, add a new record for the next time it occurs.
One way to do it anyway, and it gets rid of the problem of using event start for two different things which is why your code is getting complicated.
If you want future jobs from this. ie everything needed to do in the next month.
The it would be a union query. One to get all teh "current jobs", unioned with one to get all the jobs that will recur in the next month.
Can't stress this enough, get the data design right the code "just happens". If you data is messed up as in one field "start date" serving two different needs, then every time you go near it, you have to deal with that dual use. Forget it once and you get anything from a painful mess to a disaster.
Adding a Recurring_Start_Date column would be better than your current plan, wouldn't it. You wouldn't be asking this question, beacseu your data would fit your needs.
I assume you'll be searching through events much more frequently than you will be creating new ones. During event creation, I would create records for each occurrence of the event up to so reasonable amount of time (maybe for the next year or two).
It would also make things like "The third thursday of each month" a little easier. If you tried to do any of the calculations in a query it would be difficult and probably slow.
I'm building a system that shows "events for this month", listed by day and hour.
When I create the event, I set a start date, an end date and a hour.
Let's say that one event starts at 07-10-2011 and ends 07-12-2011. The problem is that some days in this date range will not feature the event. As an example, this event may happen all days and at the same hour, except some few days where it will not happen or has a different hour (think about a show with an opening date different than the rest of the days).
I'm using PHP, MySQL and Codeigniter and my doubt is about the right way to save those dates in the database. Another table with all the dates and the event ID, or save them all in a field inside the event row? Or something else?
Thanks
I'd create two tables. The first table is an events table, and the other is an events_dates table. This way you can create a single event and have as many dates linked to it as you want.
The events_dates table can be as detailed or simple as you want. If it were me, I'd probably have a start_time and end_time column, as well as an event_id and any other data you want.
I would store the range date in a table and then create an "exception" table where you can store your exceptions.
For simplicity sake, I have two fields within a table:
Date 1 (YYYY-MM-DD format)
Day (single or two digit day format, 1-31)
I want to be able to update Date 1 using the value within Day but I DO NOT want to make multiple calls to do so (first a select, fetch results, then update with the result from the same table).
ultimately, the 'design' of my call (which does not work) would be:
UPDATE table SET Date 1 =
DATE(Y-(M+1)-(value of Day));
or in php:
date("Y-m-d", mktime(0,0,0,date('m')+1, VALUE(Day), date('Y')));
is this possible?
UPDATE
==
While I have been able to utilize some of the code below, I am not sure MYSQL is 'smart' enough to run the calculation as I have it. My new code is:
UPDATE table SET Date 1=
CONCAT(YEAR(CURDATE()),'-',MONTH(ADDDATE(CURDATE(),
INTERVAL 1 MONTH)),'-',Day1)
While this returns the correct 'new month' and 'new day', the year will be wrong WHEN the current month is December.
For example: If the current date is 2010-12-02. The preferred data in the Day field is 12. Once our script has processed, the Date 1 field should be updated to 2011-01-12 but in the code above it will only output to 2010-01-12.
not tested, but i think what you're missing is CONCAT:
UPDATE table SET datefield = CONCAT(YEAR(datefield),'-',MONTH(datefield),'-',dayfield);
after rereading you questioon, it sounds like you want to add the days, that would be like this (not tested, too - take a look at DATE_ADD and INTERVAL):
UPDATE table SET datefield = DAT_ADD(datefield, INTERVAL dayfield DAYS);