I'm working on a big MySQL time based statistic table.
I have a fixed time range (start and end date time objects) and get an interval string in ISO-8601 interval string like P1DT6H as PHP object where the start date in the range is also the start point of the interval and also defines the timezone used for the interval.
Now I want to select all data within the given time range grouped by this interval but I can't make it work even after lot of hours :(
For example I get a time range of 2015-09-01/2015-09-06 and an interval of P1DT6H and the following example table:
TIMESTAMP | count
2015-09-01 00:00:00 | 1
2015-09-01 02:00:00 | 1
2015-09-01 04:00:00 | 1
2015-09-01 06:00:00 | 1
2015-09-01 08:00:00 | 1
2015-09-01 10:00:00 | 1
2015-09-01 12:00:00 | 1
2015-09-01 14:00:00 | 1
2015-09-01 16:00:00 | 1
2015-09-01 18:00:00 | 1
2015-09-01 20:00:00 | 1
2015-09-01 22:00:00 | 1
2015-09-03 00:00:00 | 1
2015-09-03 02:00:00 | 1
2015-09-03 04:00:00 | 1
2015-09-03 06:00:00 | 1
2015-09-03 08:00:00 | 1
2015-09-03 10:00:00 | 1
2015-09-03 12:00:00 | 1
2015-09-03 14:00:00 | 1
2015-09-03 16:00:00 | 1
2015-09-03 18:00:00 | 1
2015-09-03 20:00:00 | 1
2015-09-03 22:00:00 | 1
2015-09-05 00:00:00 | 1
2015-09-05 02:00:00 | 1
2015-09-05 04:00:00 | 1
2015-09-05 06:00:00 | 1
2015-09-05 08:00:00 | 1
2015-09-05 10:00:00 | 1
2015-09-05 12:00:00 | 1
2015-09-05 14:00:00 | 1
2015-09-05 16:00:00 | 1
2015-09-05 18:00:00 | 1
2015-09-05 20:00:00 | 1
2015-09-05 22:00:00 | 1
With that I want to have the following result:
TIMESTAMP | count
2015-09-01 00:00:00 | 12
2015-09-02 06:00:00 | 6
2015-09-03 12:00:00 | 6
2015-09-04 18:00:00 | 12
For sure the interval can be more complicated, the time range can be very big and the data table is also a big table.
This needs to handle months where nearly every month have a different amount of days incl. leap year and also dst changes where a day can have 23, 24 or 25 hours. (Means a one day interval is different than a 24 hours interval)
It would be really really helpful if someone has a solution or can me point to the right direction for that kind of problem.
Thanks!
PS: I have a script that creates SQL an expression in base of a given db column, the start, end and interval objects but it only works for very simple intervals like P1D. I don't past it here as I don't want to ping all great brains into a non working direction that I already have ;)
What I have right now but it doesn't work with mixed intervals.
Examples:
Timezone handling:
if ($db->getTimezone()->getName() !== $start->getTimezone()->getName()) {
$col = 'CONVERT_TZ(' . $col
. ', ' . $this->quote($this->getTimezone()->getName())
. ', ' . $this->quote($start->getTimezone()->getName())
. ')';
}
P1M:
$m = ($interval->y * 12) + $interval->m;
if ($m) {
if ($m > 1) {
$mod = $start->format('Ym') % $m;
$mod = $mod ? ' + ' . $mod : '';
$expr = 'EXTRACT(YEAR_MONTH FROM ' . $col . ')';
$expr = $mod ? '(' . $expr . $mod . ')' : $expr;
$expr = ' - INTERVAL ' . $expr . ' % ' . $m . ' MONTH';
$sqlIntervalMonth = $expr;
}
$sqlIntervalDay = ' - INTERVAL DAY(' . $col . ') - 1 DAY';
if ($start->format('d') > 1) {
$sqlIntervalDay .= ' + INTERVAL ' . ($start->format('d') - 1) . ' DAY';
}
}
P1D:
$d = $interval->d;
if ($d) {
$days = $start->diff(new DateTime('0000-00-00'))->days;
$mod = $days % $d;
$mod = $mod ? ' + ' . $mod : '';
$expr = 'TO_DAYS(' . $col . ')';
$expr = $mod ? '(' . $expr . $mod . ')' : $expr;
$expr = ' - INTERVAL ' . $expr . ' % ' . $d . ' DAY';
$sqlIntervalDay = $expr;
}
EDIT 1: pointed out timezone, dst and leap year requirement.
EDIT 2: added PHP snippets
One idea is to convert the value of the time stamp to seconds and then round that value to the appropriate interval (36 hours in your case). Something like this:
select min(timestamp) as timestamp, sum(count)
from t
group by floor(to_seconds(timestamp) / 60 * 60 * 36) -- * 60 * 60 * 36
order by timestamp;
This uses the min() of the datetime value, because you seem to have that value in the table. Alternatively, you can convert the rounded seconds back to a datetime value.
This is the closest I could get. Though time periods without any data will not be represented (don't know if that is a problem or not):
select yourTimestamp,sum(yourCount)
from
(
select t.yourTimestamp,t.yourCount,
'2015-09-01' + interval (36*60)*(floor(timestampdiff(MINUTE,'2015-09-01',t.yourTimestamp)/(36*60))) minute as recordGrpTime
from t
where t.yourTimestamp between '2015-09-01' and '2015-09-06'
) t
group by recordGrpTime;
Related
Don,t think I can find this answer in this forum.
How to get the first week number in every month where month start by Monday. This month first week is 36 how to get that? Having this code. But don't work.
//get first week number in month
$month = 9;
$year = 2018;
$day = 1;
$firstday = new DateTime("$year-$month-1");
$dow = (int)$firstday->format('w');
$firstday->add(new DateInterval('P' . ((8 - $dow) % 7) . 'D'));
$weeknumber = $firstday->format('W');
echo $weeknumber ;
I think this code will do what you want. It first creates a DateTime object for the first of the month, then it moves that date forward to make it a Monday. Finally it prints the week of the year using format('W').
Edit
Updated code to print first Monday and week number for whole year
$year = 2018;
echo "Month | First Monday | Week\n";
for ($month = 1; $month <= 12; $month++) {
$firstday = DateTime::createFromFormat('Y-n-j', "$year-$month-1");
$dow = (int)$firstday->format('w');
// update to a monday (day 1)
$firstday->add(new DateInterval('P' . ((8 - $dow) % 7) . 'D'));
echo sprintf("%5d | %s | %4d\n", $month, $firstday->format('Y-m-d'), $firstday->format('W'));
}
Output:
Month | First Monday | Week
1 | 2018-01-01 | 1
2 | 2018-02-05 | 6
3 | 2018-03-05 | 10
4 | 2018-04-02 | 14
5 | 2018-05-07 | 19
6 | 2018-06-04 | 23
7 | 2018-07-02 | 27
8 | 2018-08-06 | 32
9 | 2018-09-03 | 36
10 | 2018-10-01 | 40
11 | 2018-11-05 | 45
12 | 2018-12-03 | 49
I'm having trouble to determine whether booking period is over or not?
i have 2 unix timestamp like this start =>2017-06-20 12:00:00 and End => 2017-06-23 12:00:00
on each time the query is run i want to check whether time is elapsed or not (i,e booking period is reached or not) from my current date (which i can pass using php)
my pseudo code:
select timestampdiff(DAY, '2017-06-20 12:00:00', '2017-06-23 12:00:00'); returns 3
returnedDate = 3; // returns difference either in date format or no.of days
if((returnedDate - mycurrentDate) == 0){
//your booking period is over
}else{
// no of remaining days
}
Desired Solution: i'm looking for mysql specific solution, good php solution is also welcomed.
Question: how to know the pre-booking date is expired?
please help me to solve this, thanks in advance!!!
Assuming mycurrentDate is NOW(), start and end are the dates in which the event occurs, and the threshold for booking is the end date.
You could use DATEDIFF to determine the number of days remaining until the event ends and check if the end date has already passed for the event to show 0.
EG: How many days until the event(s) ends
Demo: http://sqlfiddle.com/#!9/95b548/2
Assuming NOW() is 2017-06-21 12:00:00 using a past, current, and a future events.
| id | start | end |
|----|---------------------|---------------------|
| 1 | 2017-06-20T12:00:00 | 2017-06-23T12:00:00 | #current
| 2 | 2017-06-01T12:00:00 | 2017-06-03T12:00:00 | #past
| 3 | 2017-07-01T12:00:00 | 2017-07-03T12:00:00 | #future
SELECT
id,
NOW() AS `current_date`,
`tn`.`start` AS `starts_on`,
`tn`.`end` AS `ends_on`,
IF(`tn`.`end` >= NOW(),
DATEDIFF(`tn`.`end`, NOW()), #show days until event ends
0 #the event has already passed
) AS `days_remaining`
FROM `table_name` AS `tn`
Results:
| id | current_date | start | end | days_remaining |
|----|---------------------|------------------------|------------------------|----------------|
| 1 | 2017-06-21T12:00:00 | June, 20 2017 12:00:00 | June, 23 2017 12:00:00 | 2 |
| 2 | 2017-06-21T12:00:00 | June, 01 2017 12:00:00 | June, 03 2017 12:00:00 | 0 |
| 3 | 2017-06-21T12:00:00 | July, 01 2017 12:00:00 | July, 03 2017 12:00:00 | 12 |
If end is stored as a unix timestamp, you can use FROM_UNIXTIME() to convert it to a DATETIME value:
IF(FROM_UNIXTIME(`tn`.`end`) >= NOW(),
DATEDIFF(FROM_UNIXTIME(`tn`.`end`), NOW()),
0
) AS `days_remaining`
Table Name: directives
dir_id | directive | due_date |
-----------------------------------
1 | some text | 2017-03-30 02:00:00
2 | some text | 2016-04-30 02:00:00
3 | some text | 2017-04-30 02:00:00
4 | some text | 2016-03-30 02:00:00
5 | some text | 2015-04-30 02:00:00
6 | some text | 2016-04-30 02:00:00
using three conditions.
if there are 60 days left to due date show green elseifthere are 5 days left show yellow else if there are zero(0) days or minus days passed due date show red .
Also if possible get number of rows respectively *You can use different query for row count* e.g rows with 60 days left = 5, data with 5 days left = 4, data with 0 days or minus = 4
'Lovely day to all'
Thank you
** Final Edit "Working Code for row count"**
//For Zero days
$this->db->where('due_date < now()');
$query0 = $this->db->count_all_results('directives');
//For five days left
$this->db->where('due_date between now() - interval 6 day and now() + interval 5 day');
$query5 = $this->db->count_all_results('directives');
//For more than 5+ days left
$this->db->where('due_date > now() + interval 6 day');
$query6 = $this->db->count_all_results('directives');
In case I did not follow the standard procedure please let me know.
Thank you
/UNTESTED/
select case
when due_date between now() - interval 5 day and sysdate()
then "red"
when due_date between now() - interval 6 day and now() - interval 59 day
then "Yellow"
when due_date > now() + interval 60 day
then "Green"
end as age,
sum(case when due_date between now - interval 5 day and sysdate() then 1 else 0 end) red_count
///// sum others to get counts
from table
I have a table for events/parties that store the day, month, year, hour, and minute in different fields for each event, in this way:
+--------+-----+-------+--------+--------+----------+
| event | day | month | year | hour | minute |
+--------+-----+-------+--------+--------+----------+
| event1 | 2 | 12 | 2015 | 11 | 25 |
| event2 | 3 | 1 | 2016 | 12 | 30 |
| event3 | 4 | 2 | 2016 | 13 | 45 |
+--------+-----+-------+--------+--------+----------+
Using this structure I can do a query for the exact current time in this way:
SELECT * FROM of2ab_jcalpro_events
WHERE day = " . date("j") . "
AND month = " . date("n") . "
AND year = " . date("Y") . "
AND hour = " . date("G") . "
AND minute = " . date("i") . "
ORDER by minute ASC
LIMIT 0,3
Now... My problem is how do I select the next three events? I mean from right now the next 3 events? It would be easy if I have a datetime, but I can not change the table structure and this is what I have, any idea?
Let put these values as example:
date("j") = 2
date("n") = 12
date("Y") = 2015
date("G") = 20
date("i") = 45
This mean: 2015-12-02 20:45
So, how to get the next rows after right now?
Create a mysql date and time string by concatenating the field values and you can compare this to the current time and order by this calculated value.
select * events
where concat(year, '-', month, '-', day, ' ', hour, ':', minute,':00')>=now()
order by concat(year, '-', month, '-', day, ' ', hour, ':', minute,':00') desc limit 3;
If month, day, hour, minute data are not stored in 2 digits, then you may have to convert them to a 2-digit format.
Since you don't have single timestamp column, you can't do a single ORDER BY to get what you want. But you can collectively ORDER BY the combination of the date fields. I added LIMIT 3 to get the 3 events closest to the current time.
ORDER BY year DESC, month DESC, day DESC, hour DESC, minute DESC
LIMIT 3
With the help of the guys above, specially #shadow I found out the perfect query that works for me under the condition explained above.
SELECT title, day, month, year, hour, minute FROM of2ab_jcalpro_events
WHERE
STR_TO_DATE(concat(day, ',', month, ',', year, ' ', hour, ':', minute,':00'),'%d,%m,%Y %H:%i:%s') > now()
order by STR_TO_DATE(concat(day, ',', month, ',', year, ' ', hour, ':', minute,':00'),'%d,%m,%Y %H:%i:%s') asc
limit 4
In PHP and MySQL - how to determine if the Store is Open or Close (return true or false)?
Also how to get the next opening hours if the store is closed?
Example of Opening_Hours table:
+----+---------+----------+-----------+------------+---------+
| id | shop_id | week_day | open_hour | close_hour | enabled |
+----+---------+----------+-----------+------------+---------+
| 1 | 1 | 1 | 16:30:00 | 23:30:00 | 1 |
| 2 | 1 | 2 | 16:30:00 | 23:30:00 | 1 |
| 3 | 1 | 3 | 16:30:00 | 23:30:00 | 0 |
| 4 | 1 | 4 | 16:30:00 | 23:30:00 | 1 |
| 5 | 1 | 5 | 10:00:00 | 13:00:00 | 1 |
| 6 | 1 | 5 | 17:15:00 | 00:30:00 | 1 |
| 7 | 1 | 6 | 17:15:00 | 01:30:00 | 1 |
| 8 | 1 | 7 | 16:30:00 | 23:30:00 | 0 |
+----+---------+----------+-----------+------------+---------+
The open_hour and close_hour are TIME type fields. Table design ok?
Example of current times:
Current time: Tue 23:00, - Output: Open, 'Open at Tue 16:30 - 23:30'
Current time: Tue 23:40, - Output: Close, 'Open at Thur 16:30 - 23:30'
Open on Thursday because Opening_Hours.week_day = 3 is disabled
Now how to handle the midnight time? This get more complicated.
As you can see, on Saturday (Opening_Hours.week_day = 5), it is open from 17:15 PM to 01:30 (closed next day Sunday)
If the current time is Sunday 01:15 AM, then the store would still be open base on Opening_Hours.week_day = 5.
Output: Open, 'Open at Sat 17:15 - 01:30'
In the past, I've handled this by using a time stamp without a date (seconds since midnight). So for Saturday, the open would be 62100 and the close would be 91800.
My thought was this removes some of the logic needed when a close crosses midnight, as you only need to compare the seconds since the start of the date to the time range.
And it's pretty easy to check if it's still open from 'yesterday' - just add 86400 to the current 'time' (seconds since the start of the day) and check against the previous day.
Probably all a single SQL statement.
You can use the PHP date() function and compare it to your opening hours.
You can do something like this recursive function (not working PHP code, but PHP combined with pseudo-code):
/* $current_time should be in the format of date("His") */
function check_hours($current_day, $current_time)
{
Get the MySQL row for today here
if (Opening_Hours.enabled == 1 WHERE Opening_Hours.week_day == $current_day)
{
if ((date("His") >= Opening_Hours.open_hour) and ($current_time <= Opening_Hours.close_hour))
{
// convert_numeric_day_to_full_representation isn't a real function! make one
return 'Open: ' . convert_numeric_day_to_full_representation($current_day) . ' ' . Opening_Hours.open_hour . ' – ' . Opening_Hours.close_hour;
}
elseif (date("His") < Opening_Hours.open_hour)
{
return 'Closed: Next opening hours: ' . convert_numeric_day_to_full_representation($current_day) . ' ' . Opening_Hours.open_hour . ' – ' . Opening_Hours.close_hour;
}
else
{
return check_hours($tomorrow, '000000');
}
}
else
{
return check_hours($tomorrow, '000000');
}
}