$a = 1950-05-01
$b = 1965-08-10
$c = 1990-12-30
$d = 1990-12-29
$e = 2012-09-03
Dates are retrieved from a mysql database ordered by date ascending.
I need a mysql or PHP script to get the two CONSECUTIVE dates with the maximum days difference.
Explaination: Script should calculate the number of days between $a and $b, $b and $c, $c and $d, $d and $e, $e and $a, then output the two dates with the maximum days difference.
Is there a way to do this with a fast mysql/php code or should I make some loops with the following script (found it on another question here on stackoverflow)?
$now = time(); // or your date as well
$your_date = strtotime("2010-01-01");
$datediff = $now - $your_date;
echo floor($datediff/(60*60*24));
Query that lists dates:
SELECT date AS count FROM table WHERE column1 = 'YES' AND data BETWEEN 1950-01-01 AND 2012-09-04
MySQL Solution
Assuming that each date has a sequential id. See it in action.
Schema
CREATE TABLE tbl (
id tinyint,
dt date);
INSERT INTO tbl VALUES
(1, '1950-05-01'),
(2, '1965-08-10'),
(3, '1990-12-30'),
(4, '1990-12-29'),
(5, '2012-09-03')
Query
SELECT a.dt AS date1,
(SELECT dt FROM tbl WHERE id = a.id - 1) AS date2,
DATEDIFF(a.dt, b.dt) AS diff
FROM tbl a
LEFT JOIN tbl b ON b.id = a.id -1
GROUP BY a.id
ORDER BY diff DESC
LIMIT 1
Result
| DATE1 | DATE2 | DIFF |
--------------------------------------------------------------------------
| August, 10 1965 00:00:00-0700 | December, 30 1990 00:00:00-0800 | 9273 |
PHP Solution
$array = array('1950-05-01', '1965-08-10', '1990-12-30', '1990-12-29', '2012-09-03');
$maxDiff = 0;
$maxStart = NULL;
$maxEnd = NULL;
for($i = 1; $i <= count($array); $i++) {
if(isset($array[$i])) {
$diff = (strtotime($array[$i]) - strtotime($array[$i-1])) / (60*60*24);
if($diff > $maxDiff) {
$maxDiff = $diff;
$maxStart = $array[$i-1];
$maxEnd = $array[$i];
}
}
}
echo "The maximum days difference is between $maxStart and $maxEnd, with a difference of $maxDiff days";
Result
The maximum days difference is between 1965-08-10 and 1990-12-30, with a difference of 9273.0416666667 days
Update 1
With regards to the PHP solution, if your dates are not in order, you can sort the array before the loop using sort($array);.
You can use this single-statement solution:
SELECT a.date date1,
b.date date2,
DATEDIFF(b.date, a.date) ddiff
FROM (
SELECT #a_rn:=#a_rn+1 ascrank,
date
FROM tbl
CROSS JOIN (SELECT #a_rn:=0) var_init
WHERE date BETWEEN '1950-05-01' AND '2012-09-04'
ORDER BY date
) a
JOIN (
SELECT #b_rn:=#b_rn+1 ascrank,
date
FROM tbl
CROSS JOIN (SELECT #b_rn:=-1) var_init
WHERE date BETWEEN '1950-05-01' AND '2012-09-04'
ORDER BY date
) b ON a.ascrank = b.ascrank
ORDER BY ddiff DESC
LIMIT 1
Query Breakdown
Given this example data-set:
CREATE TABLE tbl (
date DATE
);
INSERT INTO tbl VALUES
('1950-05-01'),
('1965-08-10'),
('1990-12-30'),
('1990-12-29'),
('2012-09-03');
We want to find the biggest difference between two consecutive dates (meaning, given the dates ordered in ascending order, find the maximum day difference of the dates and their immediate prior dates).
We would expect to output:
+-------------+------------+--------+
| date1 | date2 | ddiff |
+-------------+------------+--------+
| 1965-08-10 | 1990-12-29 | 9272 |
+-------------+------------+--------+
Because the biggest consecutive date difference is between 1965-08-10 and 1990-12-29.
Step 1:
The first thing we want to do in order to get the previous and next dates beside each other (to facilitate the DATEDIFF function) is to attach a rank number to each date based on the ascending order of the dates.
Because the order of the dates can't rely upon anything but themselves (not an auto-incrementing ID or rank field, etc.) we must manually calculate the rank ourselves.
We do this by using MySQL variables. Other solutions that use variables require that you execute three or more separate statements. My technique of initializing the variables right in the query itself (via CROSS JOIN) keeps it contained in a single statement.
SELECT #a_rn:=#a_rn+1 ascrank,
date
FROM tbl
CROSS JOIN (SELECT #a_rn:=0) var_init
WHERE date BETWEEN '1950-05-01' AND '2012-09-04'
ORDER BY date
Renders:
+----------+------------+
| ascrank | date |
+----------+------------+
| 1 | 1950-05-01 |
| 2 | 1965-08-10 |
| 3 | 1990-12-29 |
| 4 | 1990-12-30 |
| 5 | 2012-09-03 |
+----------+------------+
SQLFiddle Demo
Note the WHERE condition that the dates have to be in between two specified dates. This is where you would insert your start/end date parameters from your script.
Step 2:
Now that we have ranked each date, we now need to perform a shifted inner join of the result onto itself based on the ascrank field so that we get the consecutive dates beside each other. We do this by wrapping the result in a subselect.
Since we need to self-join a derived result, we must duplicate the step above only with a slightly adjusted parameter:
SELECT *
FROM (
SELECT #a_rn:=#a_rn+1 ascrank,
date
FROM tbl
CROSS JOIN (SELECT #a_rn:=0) var_init
WHERE date BETWEEN '1950-05-01' AND '2012-09-04'
ORDER BY date
) a
JOIN (
SELECT #b_rn:=#b_rn+1 ascrank,
date
FROM tbl
CROSS JOIN (SELECT #b_rn:=-1) var_init
WHERE date BETWEEN '1950-05-01' AND '2012-09-04'
ORDER BY date
) b ON a.ascrank = b.ascrank
Renders:
+----------+-------------+----------+------------+
| ascrank | date | ascrank | date |
+----------+-------------+----------+------------+
| 1 | 1950-05-01 | 1 | 1965-08-10 |
| 2 | 1965-08-10 | 2 | 1990-12-29 |
| 3 | 1990-12-29 | 3 | 1990-12-30 |
| 4 | 1990-12-30 | 4 | 2012-09-03 |
+----------+-------------+----------+------------+
SQLFiddle Demo
The "slightly adjusted parameter" is just that the ascrank variable (#b_rn) in the second subselect starts from -1 instead of 0. That way, the join condition of a.ascrank = b.ascrank joins the next date in the ascending order. We could have also kept both variables initialized at 0, but joined on the condition of a.ascrank = b.ascrank-1, which would have rendered the same result.
But wait, what happened to the date with the ascrank of 5? Since that is the last date in the order, there would be no dates after it to take the difference from, so it doesn't need to appear in the left side of the result, it only needs to be compared with its immediate prior date.
Step 3:
Now that we have the consecutive dates beside each other, we can take the date difference (via DATEDIFF()) between the two:
SELECT a.date date1,
b.date date2,
DATEDIFF(b.date, a.date) ddiff
FROM (
SELECT #a_rn:=#a_rn+1 ascrank,
date
FROM tbl
CROSS JOIN (SELECT #a_rn:=0) var_init
WHERE date BETWEEN '1950-05-01' AND '2012-09-04'
ORDER BY date
) a
JOIN (
SELECT #b_rn:=#b_rn+1 ascrank,
date
FROM tbl
CROSS JOIN (SELECT #b_rn:=-1) var_init
WHERE date BETWEEN '1950-05-01' AND '2012-09-04'
ORDER BY date
) b ON a.ascrank = b.ascrank
Renders:
+-------------+------------+--------+
| date1 | date2 | ddiff |
+-------------+------------+--------+
| 1950-05-01 | 1965-08-10 | 5580 |
| 1965-08-10 | 1990-12-29 | 9272 |
| 1990-12-29 | 1990-12-30 | 1 |
| 1990-12-30 | 2012-09-03 | 7918 |
+-------------+------------+--------+
SQLFiddle Demo
Step 4:
Now it's a simple matter of selecting the maximum ddiff value. We do this by using an ORDER BY / LIMIT 1 technique on the ddiff field:
SELECT a.date date1,
b.date date2,
DATEDIFF(b.date, a.date) ddiff
FROM (
SELECT #a_rn:=#a_rn+1 ascrank,
date
FROM tbl
CROSS JOIN (SELECT #a_rn:=0) var_init
WHERE date BETWEEN '1950-05-01' AND '2012-09-04'
ORDER BY date
) a
JOIN (
SELECT #b_rn:=#b_rn+1 ascrank,
date
FROM tbl
CROSS JOIN (SELECT #b_rn:=-1) var_init
WHERE date BETWEEN '1950-05-01' AND '2012-09-04'
ORDER BY date
) b ON a.ascrank = b.ascrank
ORDER BY ddiff DESC
LIMIT 1
Renders:
+-------------+------------+--------+
| date1 | date2 | ddiff |
+-------------+------------+--------+
| 1965-08-10 | 1990-12-29 | 9272 |
+-------------+------------+--------+
SQLFiddle Demo of Final Result
And we have arrived at our final result.
I am using the table scheme of njk - and checked it on my mysql db.
SCHEME
CREATE TABLE tbl (
id tinyint,
dt date);
INSERT INTO tbl VALUES
(1, '1950-05-01'),
(2, '1965-08-10'),
(3, '1990-12-30'),
(4, '1990-12-29'),
(5, '2012-09-03')
QUERY
SELECT a.id, b.id, ABS(DATEDIFF(a.dt, b.dt)) AS ddiff
FROM tbl AS a
JOIN tbl AS b ON (a.id = (b.id + 1)) OR (a.id = (SELECT id FROM tbl ORDER BY id ASC LIMIT 1) AND b.id = (SELECT id FROM tbl ORDER BY id DESC LIMIT 1))
ORDER BY ddiff DESC
LIMIT 1
I am joining all consecutive rows (a.id = (b.id + 1)) and the first row with the last one like this: (a.id = (SELECT id FROM tbl ORDER BY id ASC LIMIT 1) AND b.id = (SELECT id FROM tbl ORDER BY id DESC LIMIT 1)) which looks strange but works very fine. In case you have only the 5 rows you mentioned this would be
SELECT a.id, b.id, ABS(DATEDIFF(a.dt, b.dt)) AS ddiff
FROM tbl AS a
JOIN tbl AS b ON (a.id = (b.id + 1)) OR (a.id = 1 AND b.id = 5)
ORDER BY ddiff DESC
LIMIT 1
EDIT: The result is 1=$a and 5=$e
Try this query -
SELECT
t1.dt,
#dt_next := (SELECT dt FROM tbl WHERE dt > t1.dt ORDER BY dt LIMIT 1) dt_next,
DATEDIFF(#dt_next, t1.dt) max_diff
FROM tbl t1
ORDER BY max_diff DESC LIMIT 1;
+------------+------------+----------+
| dt | dt_next | max_diff |
+------------+------------+----------+
| 1965-08-10 | 1990-12-29 | 9272 |
+------------+------------+----------+
Just example:
mysql> SELECT MIN(version) AS version FROM schema_migrations UNION SELECT MAX(version) FROM schema_migrations;
+----------------+
| version |
+----------------+
| 20120828071352 |
| 20120830100526 |
+----------------+
2 rows in set (0.00 sec)
if the dates are on a table you can do something like (this is not the T-SQL, its just an algorithm, to get the previous_date you would need to rn another select top 1 on the same table with an aclias X for example where X.date<=date)
select date, datediff(date, previous_date)
and order by the second column desc, so the first row would be the date you want
Start with a subquery that creates a result set that has the dates in ascending order and an INT field (dateOrder) that starts at 1 and increments by 1.
SET #a := 0;
SELECT date, (#a:=#a+1) AS dateOrder FROM dateTable ORDER BY date
Now we can get consecutive dates by joining this result set to another copy of itself with a.dateOrder = b.dateOrder -1. In that result set, each row contains a pair of consecutive dates from the original table, and it is easy to calculate the difference and sort the result set to find the biggest difference.
SET #a := 0; SET #b := 0;
SELECT a.date as firstDate, b.date as secondDate,
datediff(b.date, a.date) AS difference FROM (
SELECT date, (#a:=#a+1) AS dateOrder FROM dateTable ORDER BY date ) a JOIN (
SELECT date, (#b:=#b+1) AS dateOrder FROM dateTable ORDER BY date ) b
ON a.dateOrder = b.dateOrder - 1
ORDER BY difference desc;
You can put a 'limit 1' clause at the end of the query to only get the first row, which has the biggest value of 'difference'. Note that you have to use two different variables to generate date order for the two subqueries.
Your query that returns date values is non-deterministic... absent an ORDER BY clause in your query, there is NO GUARANTEE that the rows will be returned in any particular order.
In MySQL, a query can return the result set you specified. Here is one approach:
SELECT ABS(DATEDIFF(d.mydate,#prev_date)) AS days_diff
, DATE_ADD(#prev_date,INTERVAL 0 DAY) AS date1
, #prev_date := d.mydate AS date2
FROM ( SELECT #prev_date := NULL) i
JOIN ( SELECT d1.*
FROM ( -- query to return rows in a specific order
SELECT mydate
FROM mytable3
WHERE 1
ORDER BY foo
) d1
UNION ALL
SELECT d2.*
FROM ( -- query to return rows in a specific order (again)
SELECT mydate
FROM mytable3
WHERE 1
ORDER BY foo
LIMIT 1
) d2
) d
ORDER BY days_diff DESC
NOTES:
The ABS() function is needed only if you want to consider the number of days between the dates, irrespective of whether the first date is before or after the second date, since the DATEDIFF function can return a negative value.
The DATE_ADD( ,INTERVAL 0 DAY) function around the #prev_date user variable is just there to cast the return value to datatype DATE. A `STR_TO_DATE( ,'%Y-%m-%d') function would work as well. (The difference is that the DATE_ADD function will work with DATE, DATETIME and TIMESTAMP columns without having to specify a format string to include hours, minutes, seconds.)
The inline views aliased as d1 and d2 contain the query that returns the list of dates in the SPECIFIED order you want the rows (dates) compared in. You need the order of those rows to be deterministic if you want to guarantee a consistent result from the query.
The query in the inline view aliased as d2 is identical to the query in d1, except for the addition of the LIMIT 1 clause. Because you specified that you wanted to compare $e to $a, we "tack on" that first row from the query to the end so we can compare that first row with the last row from the query.
The date1 column in the result set is not a DATE datatype, but it can easily be cast to a DATE
If you want other columns returned from the two rows, along with the date value, that can be easily handled using the same approach. The queries in d1 and d2 just need to return the additional columns:
SELECT ABS(DATEDIFF(d.mydate,#prev_date)) AS days_diff
, #prev_foo AS foo1
, #prev_date AS date1
, #prev_foo := d.foo AS foo2
, #prev_date := d.mydate AS date2
FROM ( SELECT #prev_date := NULL, #prev_foo := NULL) i
JOIN ( SELECT d1.*
FROM ( -- query to return rows in a specific order
SELECT mydate, foo
FROM mytable3
WHERE 1
ORDER BY foo
) d1
UNION ALL
SELECT d2.*
FROM ( -- query to return rows in a specific order (again)
SELECT mydate, foo
FROM mytable3
WHERE 1
ORDER BY foo
LIMIT 1
) d2
) d
ORDER BY days_diff DESC
LIMIT 1
To setup test case:
CREATE TABLE `mytable3` (`foo` varchar(1), `mydate` date);
INSERT INTO mytable3 VALUES
('a','1950-05-01'),
('b','1965-08-10'),
('c','1990-12-30'),
('d','1990-12-29'),
('e','2012-09-03');
Here's PHP solution
$dates = array('1970-05-01', '1975-08-10', '1990-12-30', '1990-12-29', '2012-09-03');
$sorted = array();
foreach($dates as $i => $date) {
$date2 = isset($dates[$i+1]) ? $dates[$i+1] : $dates[0];
$diff = (strtotime($date2) - strtotime($date))/(60 * 60 * 24);
$sorted[abs($diff)] = array('start' => $date, 'end' => $date2);
}
ksort($sorted);
$result = end($sorted);
I would use some simple PHP since it's quick and neat:
function get_the_two_consecutive_dates_with_the_maximum_days_difference($dates) {
foreach ($dates as $i => $date) {
$previousDate = $dates[$i - 1];
if (!$previousDate) continue;
$diff = strtotime($date) - strtotime($previousDate);
if ($maxDiff < $diff) {
$maxDiff = $diff;
$dateA = $previousDate;
$dateB = $date;
}
}
return array($dateA, $dateB, $maxDiff);
}
// Usage
$arr = Array ( '2012-01-01', '2012-02-01', '2012-03-01', '2012-04-12',
'2012-05-10', '2012-08-05', '2012-09-01', '2012-09-04' );
var_dump(get_the_two_consecutive_dates_with_the_maximum_days_difference($arr));
I have gone for a solution using PHP's DateTime class. The reason for this is that strtotime() does not have a way of specifying the format of the dates passed to it. In my mind this creates an ambiguity over what will be returned, so I have stopped using it in favour of DateTime.
As the example dates you gave are not in the correct order, I have assumed that they need to be sorted first. The following function achieves this:-
/**
* Sorts an array of dates in given format into date order, oldest first
* #param array $dates
* #param type $format Optional format of dates.
*
* #return array with dates in correct order.
*/
function sortArrayOfDates(array $dates, $format = 'Y-m-d')
{
$result = array();
foreach($dates as $date){
$timeStamp = DateTime::createFromFormat($format, $date)->getTimestamp();
$result[$timeStamp] = $date;
}
sort($result);
return $result;
}
Now we can write a function to do the job:-
/**
* Returns the longest gap between sets of dates
*
* #param array $dates
* #param string Optional. Format of dates.
*
* #return array Containing the two dates with the longest interval and the length of the interval in days.
*/
private function longestGapBetweenDates(array $dates, $format = 'Y-m-d')
{
$sortedDates = sortArrayOfDates($dates);
$maxDiff = 0;
$result = array();
for($i = 0; $i < count($dates) - 1; $i++){
$firstDate = DateTime::createFromFormat($format, $sortedDates[$i]);
$secondDate = DateTime::createFromFormat($format, $sortedDates[$i + 1]);
$diff = $secondDate->getTimestamp() - $firstDate->getTimestamp();
if($diff > $maxDiff){
$maxDiff = $diff;
$result = array($firstDate->format($format), $secondDate->format($format), $firstDate->diff($secondDate)->days);
}
}
return $result;
}
With your example list:-
$a = '1950-05-01';
$b = '1965-08-10';
$c = '1990-12-30';
$d = '1990-12-29';
$e = '2012-09-03';
var_dump(longestGapBetweenDates(array($a, $b, $c, $d, $e)));
Output:-
array
0 => string '1965-08-10' (length=10)
1 => string '1990-12-29' (length=10)
2 => int 9272
As a bonus my function gives you the number of days between the two dates too.
Select t1.date as 'Date1', t2.date AS 'Date2', DATEDIFF(t2, t1) as 'DateDiff'
From YourTable t1
Left outer join YourTable t2
ON t1.Id <= t2.Id
OR (t1.Id = (Select Max(Id) From YourTable) AND t2.Id=(SELECT Min(Id) From YourTable) )
Left outer join YourTable t3
ON t1.Id < t3.Id AND t3.Id < t2.Id
WHERE t3.Id IS NULL
ORDER BY 'DateDiff' DESC
Limit 1,1
For this to work around, use an array with this function:
<?php
$date_array = array('1950-05-01','1965-08-10','1990-12-30','1990-12-29','2012-09-03');
function get_max_difference_dates($dates=array()){
if(!count($dates)){
return false;
}
$max_dates_diff = 0;
$max_dates_diff_index = 0;
for($i=0;$i<count($dates)-1;$i++){
$temp_diff = strtotime($dates[$i+1]) - strtotime($dates[$i]);
if($temp_diff>$max_dates_diff){
$max_dates_diff = $temp_diff;
$max_dates_diff_index = $i;
}
}
return $max_dates_diff_index;
}
var_dump(get_max_difference_dates($date_array));
the answer to the above as per my compilation is "1".
after the index you ca fetch the dates by the returned index and by adding one to it.
$indx = get_max_difference_dates($date_array);
$date1 = $date_array[$indx];
$date2 = $date_array[$indx+1];
Related
I have a table in my db, which contains following data:
————————————————————————————————————————————————————————————————————————
Id startDate availabilityStatus Hotel_Id
————————————————————————————————————————————————————————————————————————
1 2016-07-01 available 2
2 2016-07-02 available 2
3 2016-07-03 unavailable 2
4 2016-07-04 available 3
5 2016-07-05 available 3
6 2016-07-06 available 3
7 2016-07-07 unavailable 4
8 2016-07-08 available 4
9 2016-07-09 available 4
10 2016-07-10 available 4
Now, user wants to see all the Hotels which have 3 continuous days availability in July’16.
I am able to make the query to get the availability, but not sure how to fetch the Continuous date availability.
As per the above data, in July only Hotel Id 3, 4 have the continuous available dates, but as 2 also have the dates available. so how should we remove 2 and show just 3, 4 via MySQL query.
Please advise?
You can use the following query:
SELECT DISTINCT t1.hotel_id
FROM mytable AS t1
JOIN mytable AS t2
ON t1.hotel_id = t2.hotel_id AND
DATEDIFF(t1.startDate, t2.startDate) = 2 AND
t1.availabilityStatus = 'available' AND
t2.availabilityStatus = 'available'
LEFT JOIN mytable AS t3
ON t1.hotel_id = t3.hotel_id AND
t3.startDate < t2.startDate AND t3.startDate > t1.startDate AND
t3.availabilityStatus = 'unavailable'
WHERE t3.hotel_id IS NULL
The query is written in such a way, so that it can easily be adjusted in order to accommodate longer availability periods.
Edit:
Here's a solution using variables:
SELECT DISTINCT hotel_id
FROM (
SELECT hotel_id,
#seq := IF(#hid = hotel_id,
IF(availabilityStatus = 'available', #seq + 1, 0),
IF(#hid := hotel_id,
IF(availabilityStatus = 'available', 1, 0),
IF(availabilityStatus = 'available', 1, 0))) AS seq
FROM mytable
CROSS JOIN (SELECT #seq := 0, #hid := 0) AS vars
ORDER BY hotel_id, startDate) AS t
WHERE t.seq >= 3
You can test it with your actual data set and tell us how it compares with the first solution.
Try something like that. It works for any number of days. Replace N with 3.
SELECT DISTINCT A.Hotel_Id FROM table A
WHERE
A.availabilityStatus = 'available' AND
N-1 = (
SELECT count(DISTINCT startDate) FROM table B
WHERE B.availabilityStatus = 'available'
AND A.Hotel_Id = B.Hotel_Id
AND B.startDate
BETWEEN DATE_ADD(A.startDate, INTERVAL 1 DAY)
AND DATE_ADD(A.startDate, INTERVAL N-1 DAY)
)
It works like that: for each available date, count available dates in N-1 next days. If their count is N-1, add hotel_id to results.
Try this. I didn't get chance to test it as sqlfiddle is not working, but the general idea is to take 2 more instance of table by adding 1 and 2 days to the start date respectively.
Then join them based on derived dates and hotel id.
select t1.hotelid from
(select * from Table1 where availabilityStatus='available' ) t1
inner join
(select a.*, DATE_ADD(startDate,INTERVAL 1 DAY) as date_plus_one
from Table1 where availabilityStatus='available' ) t2
on t1.start_date=t2.date_plus_one and t1.hotelid=t2.hotelid
inner join
(select a.*, DATE_ADD(startDate,INTERVAL 2 DAY) as date_plus_two
from Table1 where availabilityStatus='available' ) t3
on t1.start_date=t3.date_plus_two and t1.hotelid=t3.hotelid
This query uses double self-join to find the same hotel available at day a, b and c, split by a day (function ADDDATE).
SELECT DISTINCT a.Hotel_Id
FROM table a
INNER JOIN table b ON a.Hotel_Id=b.Hotel_Id
INNER JOIN table c ON a.Hotel_Id=c.Hotel_Id
WHERE ADDDATE(a.startDate , INTERVAL 1 DAY) = b.startDate
AND ADDDATE(a.startDate , INTERVAL 2 DAY) = c.startDate
AND a.availabilityStatus = 'available'
AND b.availabilityStatus = 'available'
AND c.availabilityStatus = 'available'
Its working fine...
SELECT a.hotel_id FROM `mytable` as a WHERE
(select COUNT(id) from mytable as a1 where
DATE(a1.startDate)=DATE_ADD(a.startDate,INTERVAL 1 DAY) and
a1.hotel_id=a.hotel_id and
a1.availabilityStatus="Available"
) >0
and
(select COUNT(id) from mytable as a1 where
DATE(a1.startDate)=DATE_ADD(a.startDate,INTERVAL -1 DAY) and
a1.hotel_id=a.hotel_id and
a1.availabilityStatus="Available"
) >0
and
(select COUNT(id) from mytable as a1 where
DATE(a1.startDate)=DATE(a1.startDate) and
a1.hotel_id=a.hotel_id and
a1.availabilityStatus="Available"
) >0
This is the structure of the two tables
Table A
+----+-----+----+----------------------+--------------------+----------+
| id | ... |....| time_start | time_end | total |
+----+-----+----+----------------------+--------------------+----------+
1 2015-12-06 10:00:00 2015-12-06 12:00:00 200
2 2015-12-07 10:00:00 2015-12-07 12:00:00 300
Table B
+----+----------+------+------+------+------+
| id | idTableA | val1 | val2 | val3 | val4 |
+----+----------+------+------+------+------+
1 1 10 10 10 10
2 1 10 10 10 10
3 2 10 10 10 10
The goal is the following : given a time_start and a time_end date , display the SUM of the totals (table A) and the SUM of the val1,val2,val3,val4
Example :
time_start = 2015-12-01 00:00:00
time_end = 2015-12-30 23:59:59
Result expected : sum of total = 500 , sum of val(1-4) = 120
I have tried so :
$myquery = "";
$myquery .= "SELECT SUM(tableA.total) AS myTotal,";
$myquery .= "SUM(tableB.val1) + SUM(tableB.val2) + SUM(tableB.val3) + SUM(tableB.val4) AS myValTotal ";
$myquery .= "FROM tableA INNER JOIN tableB ON tableA.id = tableB.idTableA ";
$myquery .= "WHERE tableA.time_start >='".$dateStart."' AND tableA.time_end <='".$dateEnd."'";
The SUM of the val(1-4) is correct , but the SUM of total not.
Aggregate your data before you join, so you don't mistakenly consider values multifold.
select sum(a.total) as mytotal, sum(b.sumval) as myvaltotal
from tablea a
left join
(
select idtablea, sum(val1+val2+val3+val4) as sumval
from tableb
group by idtablea
) b on b.idtablea = a.id
where a.time_start >= #start and a.time_end <= #end;
Here is the same with a subquery in the SELECT clause. It's simpler and circumvents the issue described by Juan Carlos Oropeza in below comments.
select
sum(total) as mytotal,
sum((
select sum(val1+val2+val3+val4)
from tableb
where idtablea = tablea.id
)) as sumvaltotal
from tablea
where time_start >= #start and time_end <= #end;
You shouldn't aggregate on multiplication of rows . Rather aggregate two tables independently and then join them, something like:
select * from
(
SELECT SUM(tableA.total) AS myTotal
FROM tableA
WHERE tableA.time_start <= #dateEnd
AND tableA.time_end >= #dateStart
) x join (
SELECT SUM(tableB.val1) + SUM(tableB.val2) +
SUM(tableB.val3) + SUM(tableB.val4) AS myValTotal
FROM tableB join tableA ON tableA.id = tableB.idTableA
WHERE tableA.time_start <= #dateEnd
AND tableA.time_end >= #dateStart
) y;
You have to check what kind of data range overlaps you want, if partial or total.
Determine Whether Two Date Ranges Overlap
Also the best way to test it is copy the query direct on the db first.
Check the data range in the WHERE
SET #dateStart= '2015-12-01 00:00:00';
SET #dateEnd = '2015-12-30 23:59:59';
SELECT myTotal, myValTotal
FROM
(
SELECT SUM(tableA.total) AS myTotal
FROM tableA
WHERE tableA.time_start >= #dateStart
AND tableA.time_end <= #dateEnd ;
) T1
CROSS JOIN
(
SELECT SUM(tableB.val1 + tableB.val2 + tableB.val3 + tableB.val4) AS myValTotal
FROM tableA
INNER JOIN tableB
ON tableA.id = tableB.idTableA
WHERE tableA.time_start >= #dateStart
AND tableA.time_end <= #dateEnd
) T2;
As a starting point, this seems easier to read...
$myquery =
"
SELECT SUM(a.total) myTotal
, SUM(b.val1 + b.val2 + b.val3 + b.val4) myValTotal
FROM tableA a
JOIN tableB b
ON b.idTableA = a.id
WHERE a.time_start >='$dateStart'
AND a.time_end <='$dateEnd'
";
You can declare variable of int type and store the sum values , and again sumup the stored values to get value total
declare #val1 int
declare #val2 int
declare #val3 int
declare #val4 int
declare #newval int
select #val1= SUM(isnull(val1,0)) , #val2 =
sum(isnull(val2,0)), #val3=sum(isnull(val3,0)),#val4 =
sum(isnull(val2,0)) from TableB
select #newval = #val1 +#val2+#val3+#val4
#newval will include the sum of val1 to val4
SELECT
SUM(tableB.val1) + SUM(tableB.val2) + SUM(tableB.val3) + SUM(tableB.val4) AS myValTotal,
(SELECT SUM(total) from tableA where tableA.time_start >='2015-12-01 00:00:00' AND tableA.time_end <= '2015-12-30 23:59:59') as myTotal
FROM tableA INNER JOIN tableB ON tableA.id = tableB.idTableA
WHERE tableA.time_start >='2015-12-01 00:00:00' AND tableA.time_end <= '2015-12-30 23:59:59'
I have a MySQL-Table
id mydate content
----------------------------------
1 2015-06-20 some content
2 2015-06-20 some content
3 2015-06-22 some content
Now I want to count the entries for each day:
SELECT DATE(mydate) Date, COUNT(DISTINCT id) dayCount FROM mytable
GROUP BY DATE(mydate) HAVING dayCount > -1 ORDER BY DATE(mydate) DESC
This works for me, result:
2015-06-20 = 2
2015-06-22 = 1
How can I fetch days without any entries? In my example the result should be:
2015-06-19 = 0
2015-06-20 = 2
2015-06-21 = 0
2015-06-22 = 1
2015-06-23 = 0
Based on this:
<?php
$today = date("Y-m-d");
$mystartdate = date_create($today);
date_sub($mystartdate, date_interval_create_from_date_string('14 days'));
$mystartdate = date_format($mystartdate, 'Y-m-d');
?>
Finaly I want to output the counts of the last 14 days, also with "0-days". Hope you understand my problem.
For this you can create new table that holds the increment numbers, but it's not a great idea. However, if doing it this way, use this table to construct a list of dates using DATE_ADD.
LEFT JOIN onto your table of data based on the time portion to achieve your list of dates
for more info go through the link
MySQL how to fill missing dates in range?
try below-
SELECT a.date_field, COUNT(DISTINCT b.id) dayCount FROM
(SELECT date_field FROM
(
SELECT
MAKEDATE(YEAR(NOW()),1) +
INTERVAL (MONTH(NOW())-1) MONTH +
INTERVAL daynum DAY date_field
FROM
(
SELECT t*10+u daynum
FROM
(SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) A,
(SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7
UNION SELECT 8 UNION SELECT 9) B
ORDER BY daynum
) AA
) AAA
WHERE MONTH(date_field) = MONTH(NOW()) ) a
LEFT JOIN mytable b ON a.date_field=DATE(b.mydate)
GROUP BY a.date_field HAVING dayCount > -1 ORDER BY a.date_field DESC;
This has me a stummped...
If I have a this MySQL table:
UserId | Commission | Date Of Commission
1 | 200.00 | 2014-02-12
1 | 50.00 | 2014-04-01
2 | 10.00 | 2014-04-05
and I would like to display the Total Commission for a specific user per week starting from his/her first record, and display 0 for that range if there's no record.
how would I go about it?
Sample Output
UserId | Date Range | Total Commission
1 | 02/10/14 - 02/16/14 | 200.00
1 | 02/17/14 - 02/23/14 | 0.00
...
1 | 03/31/14 - 04/06/14 | 50.00
I'm not a seasoned coder so any help will be much appreciated.
Thanks!
Edit:
I have tried this:
SELECT IFNULL(SUM(Commisssion),0) Total ,DATE_SUB(`DateOfCommission`,INTERVAL 7 DAY)
AS RangStart,DATE_SUB(`DateOfCommission`,INTERVAL 1 DAY) AS RangeEnd
FROM `comms` WHERE `UserId` = '$UserID' GROUP BY DATE(`DateOfCommission`) DESC
but it starts the week with whatever date the first record was entered..
This is very tricky to accomplish. Here is what I managed to do with small modifications it should work they way it needs to be. I have done it for userid = 1 and this could be done for other users as well.
In the query I have 2 lines
where a.Date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
and
WHERE date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
The query will try to generate the list of dates using the min() date of transaction for the user till today. Instead of now() this could be used as max() date of transaction for the user as well.
select
t1.date_range,
coalesce(SUM(t1.Commission+t2.Commission), 0) AS Commission
from
(
select
a.Date as date,
concat(
DATE_ADD(a.Date, INTERVAL(1-DAYOFWEEK(a.Date)) +1 DAY),
' - ',
DATE_ADD(a.Date, INTERVAL(7- DAYOFWEEK(a.Date)) +1 DAY)
) as date_range,
'0' as Commission
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as date
from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) a
where a.Date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
)t1
left join
(
SELECT date ,
coalesce(SUM(Commission), 0) AS Commission
FROM transactions
WHERE date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
AND UserId = 1
GROUP BY date
)t2
on t2.date = t1.date
group by t1.date_range
order by t1.date_range asc
DEMO
So, this is sort of an algorithm you could use:
$Result = select distinct userid from table(this will fetch all userids from table)
while(There are rows in $Result)
{
$Userid = $Result['userid']
$StartDateRes = mysql_query(select Date, WEEKOFYEAR(Date) as week from table where userid = Userid order by date asc limit 1)
$StartDateRow = mysql_fetch_assoc($StartDateRes)
$StartDate = $StartDateRes['Date']
$StartWeekNumber = $StartDateRes['week']
$EndDateRes = mysql_query(select Date, WEEKOFYEAR(Date) as week from table where userid = Userid order by date desc limit 1)
$EndDateRow = mysql_fetch_assoc($EndDateRes)
$EndDate = $EndDateRes['Date']
$EndWeekNumber = $EndWeekRes['week']
for($i=$StartWeekNumber; $i<=$EndWeekNumber; $i++)
{
$StartDateOfWeek = FuncToFindStartDateOfWeek($i)
$EndDateOfWeek = FuncToFindEndDateOfWeek($i)
$Result2 = mysql_query(select sum(commission) as sum from table where date between StartDateOfWeek and EndDateOfWeek group by userid)
$Row2= mysql_fetch_assoc($Result2)
$Sum = $Row2['sum']
mysql_query("insert into OutputTable values($UserId, $StartDateOfWeek. '-'. $EndDateOfWeek ,$Sum");
}
}
SELECT UserId, COALESCE(SUM(Commission),0), YEARWEEK(DateOfCommission) AS TheWeek
GROUP BY UserId, TheWeek
ORDER BY UserId, TheWeek;
This will not print the nice date range, but should get you started in a SQL-only direction where the sum is broken down by the week of the year. I think you could take it from this point to add the nicer formatting of the Year/Week column. YEARWEEK() should give you pretty fast results.
The easiest way I can think of doing this as follows
Step 1: Get the date of the first record
"SELECT dateofcommission FROM comissionstable WHERE id='userid' ORDER BY dateofcommission ASC LIMIT 1"
The above query will return the first date of commission only
Step 2: Create a loop which starts from the date you got in Step 1 and continue the loop till the date is greater than or equal to today's date. Increment this date using PHP date function.
date('Y-m-d', strtotime($dateofcommission. ' + 7 days'));
Step 3: In this loop you can get the commission with-in the starting date and ending date. Starting date will be the date before adding 7 days and ending date will be the one after you have added 7 days.
SELECT SUM(commission) FROM commissiontable WHERE dateofcommission>= startingdate AND dateofcomission < endingdate AND id='userid'
The above logic should work. If you end up having some issues with this logic feel free to post in comments. I would be happy to help
The following is another solution
function getStartAndEndDate($week, $year) {
$time = strtotime("1 January $year", time());
$day = date('w', $time);
$time += ((7*$week)+1-$day)*24*3600;
$return[0] = date('Y-n-j', $time);
$time += 6*24*3600;
$return[1] = date('Y-n-j', $time);
return $return;
}
$query = mysqli_query($con, "SELECT userid, COALESCE( SUM( commission ) , 0 ) AS thecommission , YEARWEEK( doc ) AS TheWeek FROM commission GROUP BY userid, TheWeek ORDER BY userid, TheWeek");
while ($array = mysqli_fetch_array($query)) {
$test = $array['TheWeek'];
$store_array = getStartAndEndDate(substr($test,4,2), substr($test,0,4));
echo $array['userid']."-".$array['thecommission']."-".$store_array[0]."/".$store_array[1]."<br>";
}
I have the below query running on mysql
SELECT DATE(field) AS day, COUNT( * ) AS totalSessions
FROM table
GROUP BY DATE(field)
ORDER BY field DESC
LIMIT 7
It returns
day | totalSessions
2013-12-17 | 5
2013-12-15 | 1
What would I need to change on my query so that I would get the results
day | totalSessions
2013-12-17 | 5
2013-12-16 | 0
2013-12-15 | 1
2013-12-14 | 0
2013-12-13 | 0
2013-12-12 | 0
2013-12-11 | 0
You may need to store (somewhere) the dates you want to return. I think a stored procedure can help you:
delimiter $$
create procedure getStuff(d0 date, d1 date)
begin
declare d date;
drop table if exists temp_dates;
create temporary table temp_dates (
d date not null primary key
);
set d = d0;
while d <= d1 do
insert into temp_dates values (d);
set d = date_add(d, interval +1 day);
end while;
select
a.d as day,
count(b.field) as totalSessions
from
temp_dates as a
left join yourTable as b on a.d = b.dateField -- Assuming "dateField" holds the date
group by
a.d;
end $$
delimiter ;
Hope this helps
Not the best looking solution but worth a shot:
SELECT
FAKE.dt,
COUNT(YT.id) AS totalSessions
FROM (
SELECT DATE(NOW()) as dt
UNION SELECT DATE(DATE_SUB(NOW(), INTERVAL 1 DAY)) as dt
UNION SELECT DATE(DATE_SUB(NOW(), INTERVAL 2 DAY)) as dt
UNION SELECT DATE(DATE_SUB(NOW(), INTERVAL 3 DAY)) as dt
UNION SELECT DATE(DATE_SUB(NOW(), INTERVAL 4 DAY)) as dt
UNION SELECT DATE(DATE_SUB(NOW(), INTERVAL 5 DAY)) as dt
UNION SELECT DATE(DATE_SUB(NOW(), INTERVAL 6 DAY)) as dt
) FAKE
LEFT JOIN yourtable YT ON YT.datefield = FAKE.dt
GROUP BY FAKE.dt
ORDER BY FAKE.dt DESC
So you select 7 dates backwards starting today, union the results, left join the data you need, group and order by date.