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.