Получите две последовательные даты с наибольшим периодом между ними

$a = 1950-05-01
$b = 1965-08-10
$c = 1990-12-30
$d = 1990-12-29
$e = 2012-09-03

Даты извлекаются из базы данных mysql, упорядоченной по дате по возрастанию.

Мне нужно mysql или PHP script, чтобы получить две даты CONSECUTIVE с максимальной разницей дней.

Объяснение: script должно вычислять количество дней между $a и $b, $b и $c, $c и $d, $d и $e, $e и $a, затем выводить две даты с максимальной разницей дней.

Есть ли способ сделать это с помощью быстрого кода mysql/php, или я должен сделать некоторые циклы со следующим script (нашел ли он другой вопрос здесь в stackoverflow)?

$now = time(); // or your date as well
$your_date = strtotime("2010-01-01");
$datediff = $now - $your_date;
echo floor($datediff/(60*60*24));

Запрос, в котором перечислены даты:

SELECT date AS count FROM table WHERE column1 = 'YES' AND data BETWEEN 1950-01-01 AND 2012-09-04

Ответ 1

Решение MySQL

Предполагая, что каждая дата имеет последовательный id. Смотрите в действии.

Схема

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

Результат

|                         DATE1 |                           DATE2 | DIFF |
--------------------------------------------------------------------------
| August, 10 1965 00:00:00-0700 | December, 30 1990 00:00:00-0800 | 9273 |

Решение PHP

$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";

Результат

The maximum days difference is between 1965-08-10 and 1990-12-30, with a difference of 9273.0416666667 days

Обновление 1

Что касается решения PHP, если ваши даты не в порядке, вы можете отсортировать массив до цикла с помощью sort($array);.

Ответ 2

Вы можете использовать это решение с одной заявкой:

SELECT   a.date date1,
         b.date date2,
         DATEDIFF(b.date, a.date) ddiff
FROM     (
         SELECT     @a_rn:[email protected]_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:[email protected]_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

Разрушение запроса


Учитывая этот пример, набор данных:

CREATE TABLE tbl (
  date DATE
);

INSERT INTO tbl VALUES
('1950-05-01'),
('1965-08-10'),
('1990-12-30'),
('1990-12-29'),
('2012-09-03');

Мы хотим найти самую большую разницу между двумя последовательными датами (то есть, учитывая даты, упорядоченные в порядке возрастания, найдите максимальную разницу в день дат и их немедленную дату).

Мы ожидаем вывода:

+-------------+------------+--------+
| date1       | date2      | ddiff  |
+-------------+------------+--------+
| 1965-08-10  | 1990-12-29 | 9272   |
+-------------+------------+--------+

Потому что самая большая разница между датами между 1965-08-10 и 1990-12-29.


Шаг 1:

Первое, что мы хотим сделать, чтобы получить предыдущие и следующие даты рядом друг с другом (чтобы облегчить функцию DATEDIFF), - это привязать номер ранга к каждой дате в зависимости от возрастания дат.

Поскольку порядок дат не может опираться ни на что, кроме самих себя (а не на автоматическое инкрементное идентификатор или поле ранга и т.д.), мы должны вручную рассчитать ранг самостоятельно.

Мы делаем это, используя переменные MySQL. Другие решения, использующие переменные, требуют выполнения трех или более отдельных операторов. Моя методика инициализации переменных прямо в самом запросе (через CROSS JOIN) сохраняет его в одном утверждении.

SELECT     @a_rn:[email protected]_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 

Визуализирует:

+----------+------------+
| ascrank  | date       |
+----------+------------+
| 1        | 1950-05-01 |
| 2        | 1965-08-10 |
| 3        | 1990-12-29 |
| 4        | 1990-12-30 |
| 5        | 2012-09-03 |
+----------+------------+

SQLFiddle Demo

Обратите внимание на условие WHERE, что даты должны быть между двумя указанными датами. Здесь вы можете вставить свои параметры даты начала и окончания из script.


Шаг 2:

Теперь, когда мы оценили каждую дату, теперь нам нужно выполнить смещенное внутреннее соединение результата на себя, основанное на поле ascrank, чтобы мы получили последовательные даты рядом друг с другом. Мы делаем это, обертывая результат в подзапрос.

Так как нам нужно самосоединеться с производным результатом, мы должны дублировать шаг выше только с слегка скорректированным параметром:

SELECT   *
FROM     (
         SELECT     @a_rn:[email protected]_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:[email protected]_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

Визуализирует:

+----------+-------------+----------+------------+
| 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

"слегка скорректированный параметр" - это просто, что переменная ascrank (@b_rn) во втором подзапросе начинается с -1 вместо 0. Таким образом, условие объединения a.ascrank = b.ascrank присоединяется к следующей дате в порядке возрастания. Мы могли бы также сохранить обе переменные, инициализированные в 0, но присоединились к условию a.ascrank = b.ascrank-1, что сделало бы тот же результат.

Но подождите, что случилось с датой с ascrank 5?. Поскольку это последняя дата в заказе, после нее не будет никаких дат, чтобы от нее отличиться, поэтому он не должен появляться в левой части результата, его нужно сравнить только с его непосредственной датой.


Шаг 3:

Теперь, когда у нас есть последовательные даты рядом друг с другом, мы можем использовать разницу дат (через DATEDIFF()) между двумя:

SELECT   a.date date1,
         b.date date2,
         DATEDIFF(b.date, a.date) ddiff
FROM     (
         SELECT     @a_rn:[email protected]_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:[email protected]_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

Визуализирует:

+-------------+------------+--------+
| 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


Шаг 4:

Теперь просто выбрать максимальное значение ddiff. Мы делаем это с помощью метода ORDER BY / LIMIT 1 в поле ddiff:

SELECT   a.date date1,
         b.date date2,
         DATEDIFF(b.date, a.date) ddiff
FROM     (
         SELECT     @a_rn:[email protected]_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:[email protected]_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

Визуализирует:

+-------------+------------+--------+
| date1       | date2      | ddiff  |
+-------------+------------+--------+
| 1965-08-10  | 1990-12-29 | 9272   |
+-------------+------------+--------+

SQLFiddle Демо окончательного результата

И мы пришли к нашему окончательному результату.

Ответ 3

Я использую схему таблицы njk - и проверил ее на моей mysql db.

СХЕМА

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

Я соединяю все последовательные строки (a.id = (b.id + 1)) и первую строку с последней такой: (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)), которая выглядит странно, но работает очень хорошо. Если у вас есть только 5 строк, которые вы упомянули, это будет

 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: результат: 1 = $a и 5 = $e

Ответ 4

Попробуйте этот запрос -

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 |
+------------+------------+----------+

Ответ 5

Только пример:

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)

Ответ 6

если даты указаны в таблице, вы можете сделать что-то вроде этого (это не T-SQL, а просто алгоритм, чтобы получить предыдущий_date, который вам понадобится для выбора другого верхнего 1 в той же таблице с помощью aclias X например, где X.date <= date)

select date, datediff(date, previous_date)

и порядок по второму столбцу desc, поэтому первой строкой будет дата, которую вы хотите

Ответ 7

Начните с подзапроса, который создает набор результатов, который имеет даты в порядке возрастания и поле INT (dateOrder), которое начинается с 1 и увеличивается на 1.

SET @a := 0;
SELECT date, (@a:[email protected]+1) AS dateOrder FROM dateTable ORDER BY date

Теперь мы можем получить последовательные даты, присоединив этот результирующий набор к другой копии самого себя с помощью a.dateOrder = b.dateOrder -1. В этом наборе результатов каждая строка содержит пару последовательных дат из исходной таблицы, и легко вычислить разницу и отсортировать набор результатов, чтобы найти самую большую разницу.

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:[email protected]+1) AS dateOrder FROM dateTable ORDER BY date ) a JOIN (
    SELECT date, (@b:[email protected]+1) AS dateOrder FROM dateTable ORDER BY date ) b 
  ON a.dateOrder = b.dateOrder - 1
ORDER BY difference desc;

Вы можете поместить предложение "limit 1" в конце запроса, чтобы получить только первую строку, которая имеет наибольшее значение "разность". Обратите внимание, что для генерации порядка даты для двух подзапросов вам необходимо использовать две разные переменные.

Ответ 8

Ваш запрос, возвращающий значения даты, не является детерминированным... отсутствует предложение ORDER BY в вашем запросе, нет никакой ГАРАНТИИ, что строки будут возвращены в любом конкретном порядке.

В MySQL запрос может вернуть заданный вами результат. Вот один из подходов:

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

ПРИМЕЧАНИЯ:

Функция ABS() необходима, только если вы хотите рассмотреть количество дней между датами, независимо от того, является ли первая дата до или после второй даты, так как функция DATEDIFF может возвращать отрицательное значение.

Функция DATE_ADD( ,INTERVAL 0 DAY) вокруг пользовательской переменной @prev_date должна просто указать значение возвращаемого значения в datatype DATE. Функция `STR_TO_DATE (, '% Y-% m-% d') будет работать. (Разница в том, что функция DATE_ADD будет работать с столбцами DATE, DATETIME и TIMESTAMP без указания строки формата, включающей часы, минуты, секунды.)

Встроенные представления с псевдонимом d1 и d2 содержат запрос, который возвращает список дат в СПЕЦИФИЧЕСКОМ порядке, в котором вы хотите сравнить строки (даты). Вам нужно, чтобы порядок этих строк был детерминированным, если вы хотите гарантировать согласованный результат запроса.

Запрос в встроенном представлении с псевдонимом как d2 идентичен запросу в d1, за исключением добавления предложения LIMIT 1. Поскольку вы указали, что хотите сравнить $e с $a, мы "привязываем" эту первую строку от запроса до конца, чтобы мы могли сравнить эту первую строку с последней строкой из запроса.

Столбец date1 в результирующем наборе не является типом данных DATE, но его можно легко отличить до DATE

Если вы хотите, чтобы другие столбцы возвращались из двух строк вместе с значением даты, которое можно легко обрабатывать с использованием того же подхода. Запросы в d1 и d2 просто нужно вернуть дополнительные столбцы:

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

Чтобы установить тестовый пример:

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');

Ответ 9

Здесь PHP-решение

$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);

Ответ 10

Я бы использовал простой PHP, потому что он быстрый и аккуратный:

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));

Ответ 11

Я пошел на решение, используя класс PHP DateTime. Причиной этого является то, что strtotime() не имеет способа указать формат прошедших к нему дат. На мой взгляд, это создает двусмысленность в отношении того, что будет возвращено, поэтому я прекратил использовать его в пользу DateTime.

В качестве дат примера, которые вы указали, не в правильном порядке, я предположил, что их нужно сначала отсортировать. Эта функция выполняет следующее: -

/**
 * 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;
}

Теперь мы можем написать функцию для выполнения задания: -

/**
 * 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;
}

В списке примеров: -

$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)));

Вывод: -

array
  0 => string '1965-08-10' (length=10)
  1 => string '1990-12-29' (length=10)
  2 => int 9272

В качестве бонуса моя функция также дает вам количество дней между двумя датами.

Ответ 12

    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

Ответ 13

Чтобы обойти это, используйте массив с этой функцией:

<?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));

ответ на предыдущий вопрос по моей компиляции "1".

после индекса вы получите даты по возвращенному индексу и добавив его к нему.

$indx = get_max_difference_dates($date_array);
$date1 = $date_array[$indx];
$date2 = $date_array[$indx+1];