За последние 24 часа нас посетили 19035 программистов и 1618 роботов. Сейчас ищут 958 программистов ...

Как из диапазона дат и вывести только повторяющиеся даты?

Тема в разделе "PHP для новичков", создана пользователем drkrol, 5 дек 2016.

  1. drkrol

    drkrol Активный пользователь

    С нами с:
    25 авг 2016
    Сообщения:
    38
    Симпатии:
    1
    Здравствуйте. Есть две таблицы.
    Первая main
    Снимок1.PNG
    Вторая allnomer
    Снимок.PNG
    datestart и dateend - диапазон дат. Этот диапазон включает в себя все промежуточные даты.
    Мне нужно получить только те даты, которые повторились N раз. N - кол-во номеров в таблице allnomer. Lux встречается 3 раза, следовательно n = 3.

    Подробнее:
    id 44 включает в себя такие даты: 2016-12-21, 2016-12-22, 2016-12-23
    id 45 включает в себя такие даты: 2016-12-21, 2016-12-22, 2016-12-23
    id 44 включает в себя такие даты: 2016-12-22, 2016-12-23, 2016-12-24

    3 Раза повторились такие даты: 2016-12-22, 2016-12-23

    Получить N я могу, и делаю я это так:
    PHP:
    1. $nnomer = "lux";
    2.  
    3. const SQL_COUNT_NOMER = '
    4. SELECT COUNT(1) FROM allnomer WHERE type = :type
    5. ';
    6.  
    7. $pdo = new PDO($dsn, $user, $password);
    8. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    9.  
    10. $stmt = $pdo->prepare(SQL_COUNT_NOMER);
    11. $res = $stmt->execute([':type' => $nnomer]);
    12. $row = json_encode(array_pop($stmt->fetch()), JSON_NUMERIC_CHECK); // echo вернёт N
    А как получить повторяющиеся даты - я не знаю. Прошу вашей помощи.
     
  2. Deonis

    Deonis Старожил

    С нами с:
    15 фев 2013
    Сообщения:
    1.521
    Симпатии:
    504
    @drkrol тут обычным запросом не обойдёшься. Придётся писать процедуру, например, такого плана. Я бы, скорее всего, перенес бы решение на плечи php, но решение за вами.
    P.S. array_pop тут лишнее. fetchAll с аргументом FETCH_COLUMN
     
  3. Deonis

    Deonis Старожил

    С нами с:
    15 фев 2013
    Сообщения:
    1.521
    Симпатии:
    504
    Под вечер туплю. Написал сначала правильно (fetchColumn) и что-то перемкнуло с fetchAll.
     
  4. drkrol

    drkrol Активный пользователь

    С нами с:
    25 авг 2016
    Сообщения:
    38
    Симпатии:
    1
    @Deonis, в смысле на плечи php? Оно и так на плечах php... Или я вас не понял...
     
  5. drkrol

    drkrol Активный пользователь

    С нами с:
    25 авг 2016
    Сообщения:
    38
    Симпатии:
    1
    Попробую привести другой пример. Вот такая база. http://sqlfiddle.com/#!9/83e77 .

    Этот модуль, который я с вашей помощью пытаюсь сделать, нужен для бронирования номеров в гостинице. Чтобы избежать повторяющейся брони на одни и те же даты, нужно знать, на какие даты уже номера забронированы. То есть, чтобы 2 разных человека не забронировали на одни и те же даты один и тот же номер. Когда я узнаю уже занятые даты, я могу запретить бронь на эти даты.

    Для визуализации нарисовал схему. Есть 3 номера lux: 301, 302 и 303. На изображении я отобразил овалами забронированные даты. В бд есть только первая и последняя дата, а, по факту, человек будет жить все дни. То есть, если дата заезда 11 числа, а дата выезда 14, то жить он будет 11,12,13 и 14 числа.
    Без kимени-1.jpg
    На изображении 3 раза забронированы только такие даты: 12, 14 и 16. Поэтому я должен от php получить ответ:
    ["2016-12-12", "2016-12-14", "2016-12-16",]
     
  6. Deonis

    Deonis Старожил

    С нами с:
    15 фев 2013
    Сообщения:
    1.521
    Симпатии:
    504
    @drkrol,
    PHP:
    1. <?php
    2. $query = "SELECT
    3.  `numbernomer`
    4. FROM
    5.  `main`
    6. WHERE
    7.  `datestart` BETWEEN CAST('2016-12-17' AS DATE) AND CAST('2016-12-20' AS DATE)
    8.  OR
    9.  `dateend` BETWEEN CAST('2016-12-17' AS DATE) AND CAST('2016-12-20' AS DATE)
    10. GROUP BY `numbernomer`";
    Таким образом, вы вычислите номера, которые уже забронированы и их даты попадают в диапазон с 2016-12-17 по 2016-12-20
     
  7. drkrol

    drkrol Активный пользователь

    С нами с:
    25 авг 2016
    Сообщения:
    38
    Симпатии:
    1
    @Deonis зачем мне номера? Мне даты нужны))) Я потом подставлю эти даты под array https://jsfiddle.net/jw01gosb/60/ и затемню занятые. В фидле 12, 14 и 16 даты - неактивны.
     
  8. Deonis

    Deonis Старожил

    С нами с:
    15 фев 2013
    Сообщения:
    1.521
    Симпатии:
    504
    Это для вас очевидно. Вытаскивайте не номера, а даты, генерируете массив для каждого диапазона и можно идти на пиво:
    PHP:
    1. <?php
    2. function getDatesFromRange($start, $end, $format = 'Y-m-d') {
    3.    $array = [];
    4.    $interval = new DateInterval('P1D');
    5.    $realEnd = new DateTime($end);
    6.    $realEnd->add($interval);
    7.    $period = new DatePeriod(new DateTime($start), $interval, $realEnd);
    8.    foreach($period as $date) {
    9.      $array[] = $date->format($format);
    10.    }
    11.    return $array;
    12. }
    13. print_r(getDatesFromRange('2016-12-12', '2016-12-18'));
    PHP:
    1. (
    2.     [0] => 2016-12-12
    3.     [1] => 2016-12-13
    4.     [2] => 2016-12-14
    5.     [3] => 2016-12-15
    6.     [4] => 2016-12-16
    7.     [5] => 2016-12-17
    8.     [6] => 2016-12-18
    9. )
     
  9. drkrol

    drkrol Активный пользователь

    С нами с:
    25 авг 2016
    Сообщения:
    38
    Симпатии:
    1
    А я номера хотел? У меня просто голова кругом идёт. Я перечитал свой вопрос. Я не просил помочь узнать номера, только даты хотел.

    А зачем в скрипте даты указывать? Нужно достать только те даты, которые повторились 3 раза , где namenomer равен lux.

    Еще раз. Есть вот такая бд http://sqlfiddle.com/#!9/83e77 . Какой нужно сделать запрос в бд или написать скрипт, чтобы ответ был вот такой: ["2016-12-12", "2016-12-14", "2016-12-16",] ?
     
  10. Deonis

    Deonis Старожил

    С нами с:
    15 фев 2013
    Сообщения:
    1.521
    Симпатии:
    504
    Похоже, что вы пытаетесь этим недугом заразить и других :D Пока могу предложить такой вариант - выбираете из БД даты. Например те, которые попадают в диапазон текущего месяца и `namenomer` = 'lux'. Дальше:
    PHP:
    1. <?php
    2. // Результат выборки из БД
    3. $query_result = [
    4.     ['datestart' => '2016-12-12', 'dateend' => '2016-12-18'],
    5.     ['datestart' => '2016-12-11', 'dateend' => '2016-12-14'],
    6.     ['datestart' => '2016-12-16', 'dateend' => '2016-12-19'],
    7.     ['datestart' => '2016-12-12', 'dateend' => '2016-12-12'],
    8.     ['datestart' => '2016-12-14', 'dateend' => '2016-12-16']
    9. ];
    10. $tmp = [];
    11. foreach($query_result as $row) {
    12.     // Фунцкия getDatesFromRange в комментарии выше
    13.     $tmp = array_merge($tmp, getDatesFromRange($row['datestart'], $row['dateend']));
    14. }
    15. // считаем количество одинаковых дат
    16. $same_number_dates = array_count_values($tmp);
    17. // Фильтруем те, у который кол-во == 3
    18. $result = array_filter($same_number_dates, function($v){
    19.     return $v == 3;
    20. });
    21. print_r($result);
    PHP:
    1. (
    2.     [2016-12-12] => 3
    3.     [2016-12-14] => 3
    4.     [2016-12-16] => 3
    5. )
     
  11. drkrol

    drkrol Активный пользователь

    С нами с:
    25 авг 2016
    Сообщения:
    38
    Симпатии:
    1
    @Deonis сейчас у меня есть вот такой код:
    PHP:
    1. $diaposons = array_map(
    2.     function($e) { return array('datestart' => strtotime($e->datestart), 'dateend' => strtotime($e->dateend)); },
    3.     json_decode('[{"datestart":"2016-12-12","dateend":"2016-12-18"},{"datestart":"2016-12-11","dateend":"2016-12-14"},{"datestart":"2016-12-16","dateend":"2016-12-19"},{"datestart":"2016-12-12","dateend":"2016-12-12"},{"datestart":"2016-12-14","dateend":"2016-12-16"}]')
    4. );
    5. usort($diaposons, function($a, $b) { return $a['datestart'] - $b['datestart']; });
    6. $result = [];
    7. $left = $diaposons[0]['datestart'];
    8. $right = $diaposons[0]['dateend'];
    9. $num = count($diaposons);
    10. $day = 24 * 60 * 60;
    11. for ($i = 3; $i < $num; ++$i) {
    12.     if ($diaposons[$i]['datestart'] > $right) {
    13.         $right = $diaposons[$i]['dateend'];
    14.         continue;
    15.     }
    16.     $end = min($right, $diaposons[$i]['dateend']);
    17.     $result[] = array(
    18.         'start' => max($left + $day, $diaposons[$i]['datestart']),
    19.         'end' => $end
    20.     );
    21.     $left = $end;
    22.     $right = max($right, $diaposons[$i]['dateend']);
    23. }
    24. foreach ($result as $r) {
    25.     for ($i = $r['start']; $i <= $r['end']; $i += $day) {
    26.         echo date('Y-m-d', $i), PHP_EOL;
    27.     }
    28. }
    Но он работает с ошибкой. Он возвращает мне 2016-12-14 2016-12-16 , а должен возвращать 2016-12-12 2016-12-14 2016-12-16.
    Третья строчка, как раз и есть структура моей бд.
    @Deonis, если я правильно понял, вашем случае я должен на каждый месяц отправлять запрос в бд. Не целесообразно это.
    Где ошибка в моём скрипте, почему он теряет первое совпадение?
     
  12. Deonis

    Deonis Старожил

    С нами с:
    15 фев 2013
    Сообщения:
    1.521
    Симпатии:
    504
    Неправильно. Условие ставите то, какое вам нужно. Можете выбирать хоть за неделю, хоть за десять лет назад + десять лет вперёд.
     
  13. drkrol

    drkrol Активный пользователь

    С нами с:
    25 авг 2016
    Сообщения:
    38
    Симпатии:
    1
    @Deonis, подскажите пожалуйста, как привести ответ вот в такой вид: ["2016-12-12", "2016-12-14", "2016-12-16",] ?
     
  14. Deonis

    Deonis Старожил

    С нами с:
    15 фев 2013
    Сообщения:
    1.521
    Симпатии:
    504
    Я с вами точно научусь ванговать. Какой "такой"? ХЗ, но возможно вам нужен JSON - json_encode
     
  15. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    Тебе надо искать пересечение диапазонов дат, а не считать какие-то непонятные повторы. Один диапазон это желаемые даты бронирования, то что ввёл пользователь. Другой диапазон это то что у тебя в таблице резервирований.

    Диапазоны a..b и x..y пересекаются если
    Код (Text):
    1. (a <= y) and (x <= b)
    Например, узнать нет ли записей о занятости номера 301 в период с 14го по 16е декабря:
    Код (Text):
    1. SELECT 1
    2. FROM main
    3. WHERE
    4.   (numbernomer = '301') AND
    5.   ('2016-12-14' <= dateend) AND (datestart <= '2016-12-16')
    если вернулась запись с 1, значит занято. Если пустой набор, то свободно.
    --- Добавлено ---
    P.S. мешать в названии рус и eng это фуууууууууууууууууууууууу.
    numbernomer, OH MY БОХ!