AntonKravchenko
@AntonKravchenko

Как оптимизировать алгоритм заполнения шахматки?

Здравствуйте!
Прошу подсказки или совета :) в части оптимизации алгоритма составления шахматки. Суть в следующем - компания занимается сдачей квартир в аренду. Шахматка - это таблица, строки которой это квартиры, а столбцы это даты подневно. В ячейке цифрой указывается стоимость аренды квартиры на указанный день, а цветом выделяется заявка на аренду в зависимости от статуса (забронировано/заселён) и источника заявки (booking и т.п. одним цветом, остальное другим). Кроме того, за каждой квартирой закреплена 1-2 ответственных сотрудника, по которым нудно группировать квартиры в шахматке.
Выглядит вот так:
453c7ebcaa6e4233b594b8a2738f8524.PNG
Сейчас формирование шахматки полностью выполняется на сервере по следующему алгоритму:
1. Формируется массив дат за период отчёта
2. Формируется массив уникальных сочетаний сотрудников, ответственных за квартиры
3. Запускается цикл по массиву уникальных сочетаний сотрудников, ответственных за квартиры
4. Внутри этого цикла запускается цикл по квартирам, за которые ответственны текущая комбинация сотрудников
5. Внутри цикла по квартире запускается цикл по датам из п.1
6. На каждом шаге этого цикла ищется заявка на аренду квартиры, попадающая на текущую дату из цикла. Также проверяется статус и источник заявки для формирования цвета ячейки в шахматке.
7. Также на каждом шаге ищется специальное значение цены квартиры из другой таблицы. Если оно не найдено, то берётся стандартное значение стоимости из карточки квартиры
8. Собранный массив данных передаётся через smarty на вывод шахматки.
Проблема в том, что при одновременной работе 3-10 сотрудников эта конструкция за период 30 дней открывается до 2 минут. Загрузка процессора составляет при этом 100%.
Возможно, есть какие-то идеи по оптимизации алгоритма сбора массива данных для отображения?
  • Вопрос задан
  • 893 просмотра
Решения вопроса 1
AntonKravchenko
@AntonKravchenko Автор вопроса
1. MySQL на том же сервере
2. php->smarty
3. ниже

// установка дат начала и окончания отчёта
if ($_REQUEST['date1']) $date1 = date("d.m.Y",strtotime(form_eng_time($_REQUEST['date1'])));
else $date1 = date("d.m.Y", mktime(0, 0, 0, date("m"), date("j")-2, date("Y")));                   
if ($_REQUEST['date2']) $date2 = date("d.m.Y",strtotime(form_eng_time($_REQUEST['date2'])));
else $date2 = date("d.m.Y", mktime(0, 0, 0, date("m"), date("j")+16, date("Y")));
$daysOfWeek = array('вс', 'пн', 'вт', 'ср', 'чт' , 'пт', 'сб');

    // массив с датами от $date1 до $date2
for ($i = strtotime($date1); $i <= strtotime($date2); $i+=86400) {
    $Report['ReportDate'] = date("d.m.Y", $i);
    $Report['isToday'] = ($Report['ReportDate']==date("d.m.Y")) ? "true" : "false";
    $Report['dayOfWeek'] = $daysOfWeek[date("w", $i)];
    $Report2['ReportDate'] = date("d.m", $i);
    $Report2['isToday'] = (date("d.m.Y", $i)==date("d.m.Y")) ? "true" : "false";
    $Report2['dayOfWeek'] = $daysOfWeek[date("w", $i)];
    $ReportDates[] = $Report;
    $ReportDates2[] = $Report2;
    $ReportDates_short[] = $Report['ReportDate'];    // массив для внутреннего цикла в шахматке
}

$arr = array('Забронировано', 'Заселён', 'Оплата', 'Продление', 'Переезд', 'Долг', 'Выезд', 'Закрыто'); // массив для статусов "платных" размещений
if ($_REQUEST['_showfree']=='true') $show_ = 'yes';

    // селект источников бронирования
$sourcesList = '<option></option>';
$res = data_select_field(130, 'id, f1740 AS name', "status=0 AND f1840='Да' ORDER BY f1740");
while ($row = sql_fetch_assoc($res)) {
    $i = $row['id'];
    $n = $row['name'];
    $sourcesList .= '<option value="'.$i.'">'.$n.'</option>';
}

    // формируем массив сочетаний по горничным, ответственным за квартиры
$maids = array();
$res = data_select_field(20, 'f2780 AS maid', "status=0");
while ($row = sql_fetch_assoc($res)) {
    $key = $row['maid'].'~~';
    if (!array_key_exists($key, $maids)) {
        $m = explode("-", $row['maid']);
        foreach ($m as $maid) {
            $maid = preg_replace('/\D/i', '', $maid);
            if ($maid) {
                $res_m = sql_query("SELECT fio FROM ".USERS_TABLE." WHERE id='".$maid."' LIMIT 1");
                $row_m = sql_fetch_assoc($res_m);
                $ms .= $row_m['fio'].'~~';  
            }      
        }        
        $maids[$key]['maids'] = $ms;
        $maids[$key]['color'] = ($i%2) ? 'NavajoWhite' : 'BurlyWood';     // разные цвета для чётных и нечётных
        $ms='';
        $i++;          
    }
}

    // сама шахматка
    // первый цикл по массиву сочетаний горничных
$last = 'start';
foreach ($maids as $key_maids=>$value_maids) {
    $maids = substr($key_maids, 0, -2);
        // цикл - по квартирам
    $res1 = data_select_field(20, 'id, f150 AS Name, f360 AS Price', "status=0 AND f2780='".$maids."' ORDER BY f3640");
    while ($row1 = sql_fetch_assoc($res1)) {
        if ($key_maids != $last) {
            $last = $key_maids;
            $ReportFlats_['flat'] = str_replace("~~", "<br>", substr($value_maids['maids'], 0, -2));
            $ReportFlats_['isMaid'] = 'true';
            $ReportFlats_['background'] = $value_maids['color'];
            $ReportFlats[] = $ReportFlats_;
            unset($ReportFlats_);
            $data1['isMaid'] = 'true';
            $lines[] = $data1;
            unset($data1);
            
        }
        $ReportFlats_['flat'] = $row1['Name'];
        $ReportFlats_['background'] = $value_maids['color'];    // раскрашиваем
        $flat = $row1['id'];
        foreach ($ReportDates_short as $reportDate) {
                // формируем даты в верном формате для вычислений
            $shortDate = date("Y-m-d", strtotime($reportDate));    // дата для "шапки" отчёта
            $date_start = date("Y-m-d H:i:s", strtotime($shortDate.' 12:00:01'));   
            $date_start_null = date("Y-m-d H:i:s", strtotime($shortDate.' 00:00:00'));
            $date_end = date("Y-m-d H:i:s", strtotime($shortDate.' 12:00:00')+86400);    
                // определяем стоимость квартиры на день из цикла
            $res_pp = data_select_field(180, 'f2170 AS customPrice', "status=0 AND f2150='".$flat."' AND f2160='".$date_start_null."' ORDER BY add_time DESC LIMIT 1");
            $row_pp = sql_fetch_assoc($res_pp);        
           
                // находим размещение на дату из цикла $reportDate
            $res3 = data_select_field(30, 
                                     'id, f260 AS account, f460 AS Stage, f280 AS startDate, f290 AS endDate, f470 AS comments, f1600 AS debt, f350 AS AllRevenue, f340 AS AddRevenue, f330 AS Additions, f1610 AS source, f1590 AS avance', 
                                     "status=0 AND f270='".$flat."' AND f280<='".$date_end."' AND f290>='".$date_start."' AND f460 NOT IN ('Отказ', 'Снял бронь', 'Передано партнёрам', 'Не заезд', 'Новая заявка', 'В работе', 'Клиент')");
            $row3 = sql_fetch_assoc($res3);
            $row3_startDate = date("Y-m-d 12:00:01", strtotime($row3['startDate']));
                // коды
            if (!$row3['id']) $isfree[$flat] = true;
            $data1[$reportDate]['tdId'] = 'td_'.$shortDate.'.'.$flat;    // Id ячейки td
            $data1[$reportDate]['orderId'] = $row3['id'];
            //$data1[$reportDate]['value'] = (in_array($row3['Stage'], $arr)) ? (($row_pp['customPrice']) ? form_local_number($row_pp['customPrice'], '0/10') : form_local_number($row1['Price'], '0/10')) : 0; // стоимость размещения
            //$data1[$reportDate]['value_nonformat'] = (in_array($row3['Stage'], $arr)) ? (($row_pp['customPrice']) ? $row_pp['customPrice'] : $row1['Price']) : 0;
            $data1[$reportDate]['price'] = ($row_pp['customPrice']) ? $row_pp['customPrice'] : $row1['Price']; // стоимость квартиры
                      
            $Result1_[$reportDate]++;    // прибавляем 1 для учёта квартир
                // учёт продлений, для этого находим предыдущее размещение
            $res3_ = data_select_field(30, 'id', "status=0 AND f270='".$flat."' AND f290='".$row3_startDate."' AND f460='Продление' AND f260='".$row3['account']."' LIMIT 1");
            $row3_ = sql_fetch_assoc($res3_);
            $isExtension = ($row3_['id']) ? 'true' : 'false';    // цвет LightGreen для продления
                // учёт забронированных
            if ($row3['Stage'] == 'Забронировано') {
                $data1[$reportDate]['color'] = ($row3_startDate == $date_start) ? ((!$row3['avance']) ? '#FA8072' : '#DC143C') : '#40E0D0';    // цвет Salmon для забронированных без аванса, Crimson для брони с авансом, 1 день проживания и Turqouse для забронированных, проживание 2+ день
                $Result1_[$reportDate]--;    // отнимаем 1 для учёта свободных квартир
            }
            if (in_array($row3['Stage'], $arr) && $row3['Stage'] != 'Забронировано') {
                $data1[$reportDate]['color'] = ($isExtension=='true') ? '#90EE90' : (($row3_startDate == $date_start) ? '#FFFF00' : '#00FF00');    // цвет LightGreen для продления, цвет Lime для проживания и цвет Yellow для первого дня проживания
                $Result1_[$reportDate]--;    // отнимаем 1 для учёта свободных квартир
            }
            $data1[$reportDate]['isDebt'] = ($row3['debt']>0) ? 'true' : 'false';
                    // учёт цвета для часовых заселений
            $data1[$reportDate]['color'] = (in_array('На часы', explode("\r\n", $row3['Additions']))) ? ((in_array($row3['Stage'], $arr) && $row3['Stage'] != 'Забронировано') ? '#FFA500' : '#4169E1') : $data1[$reportDate]['color'];    // цвет Orange для часовых заселений и цвет RoyalBlue для забронированных часов
                // учёт цвета для букинга
            if ($row3['source']=='1' && $row3['Stage'] == 'Забронировано') $data1[$reportDate]['color'] = ($row3_startDate==$date_start) ? '#4B0082' : '#483D8B';    // цвет Indigo для booking, 1 день проживания и DarkSlateBlue для booking, проживание 2+ день
                        // учёт дней роста цен
            if (!$data1[$reportDate]['orderId']) {
                $res_peak = data_select_field(140, 'f1820 AS value, f1830 AS comments', "status=0 AND f1810='".$date_start_null."' LIMIT 1");
                $row_peak = sql_fetch_assoc($res_peak);
                $data1[$reportDate]['color'] = ($row_peak['value']) ? '#C0C0C0' : '';    // светло-серый 
                if ($row_peak['value']) $data1[$reportDate]['tooltip'] = $row_peak['value'].'%'.(($row_peak['comments']) ? ', '.$row_peak['comments'] : '');
            }     
        }        
        if (!$show_ || ($show_=='yes' && $isfree[$flat])) $lines[] = $data1;
        $ReportFlats[] = $ReportFlats_;
        unset($data1);
        unset($ReportFlats_);
    }
}


$result = sql_select_field(SCHEMES_TABLE, "color3", "active='1'");
$row = sql_fetch_assoc($result);
   
    // отправляем блок данных через smarty на вывод отчёта
$smarty->assign("color3", $row['color3']);
$smarty->assign("date1", $date1);
$smarty->assign("date2", $date2);
$smarty->assign("lines", $lines);
$smarty->assign("show_", $show_);   
$smarty->assign("ReportDates", $ReportDates);  
$smarty->assign("ReportDates2", $ReportDates2);
$smarty->assign("ReportFlats", $ReportFlats); 
$smarty->assign("Result1", $Result1);
$smarty->assign("Result2", $Result2);
$smarty->assign("Result3", $Result3);
$smarty->assign("Result4", $Result4);
$smarty->assign("Result5", $Result5);
$smarty->assign("Result6", $Result6);
$smarty->assign("Result7", $Result7);
$smarty->assign("Result8", $Result8);
$smarty->assign("Result9", $Result9);
$smarty->assign("Result10", $Result10);
$smarty->assign("Result11", $Result11);
$smarty->assign("Result12", $Result12);
$smarty->assign("Result13", $Result13);
$smarty->assign("sourcesList", $sourcesList);
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
x67
@x67
1. Как хранятся данные?
2. На каком языке все это реализовано? (полагаю, это php)
3. Где, собственно, код?
Я совсем не разбираюсь в ПХП и тем более не представляю функции, выполняемые Smarty, но на мой непрофессиональный взгляд, при правильном формировании sql запроса, все должно делаться на лету буквально за секунды. У вас ведь не десятки тысяч квартир с сотнями тысяч сотрудников и диапазоном дат в тысячу лет, значит даже если алгоритмы не оптимизированы, код должен работать быстро, никак не 2 минуты. Поэтому в первую очередь проблемы надо искать в нем.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы