Задача — как подобрать из бухт кабеля двух длин на складе (бухты не режутся) количество с минимальным отклонением от заказа клиента




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2024-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

23 Comments

  1. logarifm

    С русским очень плохо, боюсь скачивать даже сей шедевр. Не вобиду но я не понимаю зачем вообще такое выкладывать, кому оно вообще надо? У меня есть масса одноразовых или специфичиских работ но я же ими не засоряю ис…

    Reply
  2. TODD22

    Не очень понятно требование к задаче решить это одним запросом. А несколькими запросами и объектной моделью уже не модно? Или в чём принципиальность?

    Reply
  3. Артано

    (2) TODD22, не задавай вопросы, быстрее скачивай все файлы =)

    Reply
  4. TODD22

    (3) Артано, Ну тогда уж лучше «Нет времени объяснять, качай все файлы» 🙂

    Reply
  5. mpudy

    Консоль в которой проверяется запрос — лол.

    Reply
  6. ser6702

    Коллеги, задачка только для того чтобы размять мозги. Задавалась на собеседовании. На мисте обсуждалось как ее решить. Было сказано, что решение выложено на инфостарте. Но как такого решения не было. Дополнил пробел.

    Reply
  7. Cooler

    Ну, мы тут уже размяли мозги: http://forum.infostart.ru/forum26/topic127610/message1323669/

    Reply
  8. ser6702

    может кому то поможет пройти собеседование

    Reply
  9. ser6702

    И в данной ветке вы только разминали мозги

    а здесь предложно конкретное решение. Рабочее. Быстрое. А мозги размять размяли — но результат как обычно российский))) языками почесать

    Reply
  10. ser6702

    7 — и решены ваши сомнения в том, что задачка решается одним запросом

    Reply
  11. TODD22

    (8)

    может кому то поможет пройти собеседование

    За это спасибо. Но там где дают такие задачи делать скорее всего нечего.

    Reply
  12. ildarovich

    Не хватает ссылки на исходное обсуждение:

    Задачка про оптимальный запрос.

    В комментарии /17/ того обсуждения я приводил свое решение. Оно довольно простое. Повторю его здесь:

    ВЫБРАТЬ
    0 КАК Х
    ПОМЕСТИТЬ Число0_1
    
    ОБЪЕДИНИТЬ
    
    ВЫБРАТЬ
    1
    ;
    
    ////////////////////////////////////////////////////////////­­////////////////////
    ВЫБРАТЬ
    Инь.Х + 2 * Янь.Х КАК Х
    ПОМЕСТИТЬ Число0_3
    ИЗ
    Число0_1 КАК Инь,
    Число0_1 КАК Янь
    ;
    
    ////////////////////////////////////////////////////////////­­////////////////////
    ВЫБРАТЬ
    Инь.Х + 4 * Янь.Х КАК Х
    ПОМЕСТИТЬ Число0_15
    ИЗ
    Число0_3 КАК Инь,
    Число0_3 КАК Янь
    ;
    
    ////////////////////////////////////////////////////////////­­////////////////////
    ВЫБРАТЬ
    Инь.Х + 16 * Янь.Х КАК Х
    ПОМЕСТИТЬ Число0_255
    ИЗ
    Число0_15 КАК Инь,
    Число0_15 КАК Янь
    ;
    
    ////////////////////////////////////////////////////////////­­////////////////////
    ВЫБРАТЬ
    Инь.Х + 256 * Янь.Х КАК Х
    ПОМЕСТИТЬ Число0_65535
    ИЗ
    Число0_255 КАК Инь,
    Число0_255 КАК Янь
    ;
    
    ////////////////////////////////////////////////////////////­­////////////////////
    ВЫБРАТЬ
    Число.Х КАК По70,
    ВЫБОР
    КОГДА &НужноМетров <= Число.Х * 70
    ТОГДА 0
    ИНАЧЕ ВЫРАЗИТЬ((&НужноМетров — Число.Х * 70) / 30 + 0.499999 КАК ЧИСЛО(10, 0))
    КОНЕЦ КАК По30,
    Число.Х * 70 + ВЫБОР
    КОГДА &НужноМетров <= Число.Х * 70
    ТОГДА 0
    ИНАЧЕ ВЫРАЗИТЬ((&НужноМетров — Число.Х * 70) / 30 + 0.499999 КАК ЧИСЛО(10, 0))
    КОНЕЦ * 30 — &НужноМетров КАК Остаток
    ПОМЕСТИТЬ Варианты
    ИЗ
    Число0_65535 КАК Число
    ГДЕ
    Число.Х <= &Остаток70
    И ВЫРАЗИТЬ((&НужноМетров — Число.Х * 70) / 30 + 0.499999 КАК ЧИСЛО(10, 0))<= &Остаток30
    ;
    
    ////////////////////////////////////////////////////////////­­////////////////////
    ВЫБРАТЬ
    Варианты.По70,
    Варианты.По30,
    Варианты.Остаток
    ИЗ
    Варианты КАК Варианты
    ГДЕ
    Варианты.Остаток В
    (ВЫБРАТЬ
    МИНИМУМ(Варианты.Остаток)
    ИЗ
    Варианты)

    Показать

    В статье другое решение?

    Reply
  13. omut

    (12) ildarovich, все верно. Строим заведомо достаточное поле сочетаний и просто выбираем наиболее подходящий под условия задачи. Но в целом и правда не ясно, зачем делать все одним запросом, накладывая — пусть и теоретическое — ограничение на максимально возможную сумму комбинации. По идее, можно ведь и текст запроса генерировать в обработке динамически (например, ограничив уже практической необходимостью максимальные значения по остаткам на складе и соотношению длин бухт и общей необходимой длины. Т.е. совсем просто соединив две таблицы, в одной из которых последовательность длин по первой номенклатуре, во второй — по второй и высчитав отклонение результата по модулю. Формально задача решена (запрос один), но сам запрос будет разный в зависимости от исходных данных. Зато на все случаи жизни.

    Поэтому, можно согласиться с (11)

    Reply
  14. ildarovich

    (13) omut, я против «тупого» лишнего перебора. Когда типоразмеров бухт ДВА, то выбрав число бухт-семидесяток, мы однозначно получаем нужное число бухт-тридцаток. То есть задача очень простая: ОДНОМЕРНАЯ.

    В моем запросе «цикл» поиска делается по бухтам бОльшей размерности, чтобы сократить число «итераций». То есть запрос максимально быстрый. Такой же подход нужно применять при решении задачи в коде.

    Если Вы под универсальным подходом понимаете динамическое построение текста запроса для бухт трех, четырех и так далее типоразмеров, то все равно лучше сокращать перебор, исключая лишний перебор числа бухт последнего типоразмера.

    Кстати, запрос не обязательно строить динамически:

    в статье Запрос-комбинатор показано, как «скомбинировать», например, длины бухт для нахождения решения перебором.

    Reply
  15. ser6702

    (12) решение похожее. Но не такое ).

    Reply
  16. ser6702

    (11) значит в R-Stily делать Вам нечего

    Reply
  17. ser6702

    (13) да — именно по такому пути решена задачка

    Reply
  18. ser6702

    (14) Дело в том, что можно предложить клиенту варианты… сколько бухт какой размерности он может взять, чтоб набрать нужную длину. может он предпочтет взять примерно равное количество разных бухт. Или одну бухту одной длинны, а остальные все другие

    Reply
  19. ildarovich

    (18) любую задачу теоретически можно решить перебором. Можно сложить радиодетали в ящик и трясти его пока не получится телевизор. Вопрос в том, сколько это займет времени.

    Знание закономерностей сокращает перебор. Иногда, как в данной задаче, весьма существенно. Игнорирование закономерностей (тупой подбор, а не вычисление количества бухт второй размерности) — это бессмысленное прожигание времени. Даже если это работает на текущих данных, может не работать (слишком долго работать) в других случаях.

    Для примера задайте требуемую длину 4587450 метров. — Сколько ваша обработка потратит времени на нахождение ответа (пусть текущие остатки это допускают)? — А мой запрос? — Сравните и сделайте выводы!

    Было бы легче общаться, если бы ваш запрос (или результат построения запроса) был приведен прямо в статье. А так остается только предположения строить о том, что на самом деле вы имеете ввиду.

    Reply
  20. TODD22

    (16) И что такое R-style? Я что то не в курсе.

    Гугл говорит что есть такой «системный интегратор», но я у них вакансии 1сника не увидел.

    Reply
  21. omut

    (19) ildarovich, я, видимо, не совсем ясно выразился в части «перебора». Перебор подразумевает создание таблицы не так, как в вашем случае (создание последовательности чисел от 1 до n), а через создание текста запроса, который выдаст эту последовательность от 1 до заданного n. Не велика разница за исключением того, что вы получаете заранее известное максимальное значение числа, полученную таблицу потом ограничиваете через условие (но таблица все равно изначально создана полная). В предложенном мной варианте таблица ограничена изначально нужным числом. Вы тоже можете добавить такое ограничение на этапе формирования Число0_65535 через сравнение Х с максимально необходимым. Это будет некоторой оптимизацией.

    Reply
  22. ser6702

    (20) R-Style

    Reply
  23. ser6702

    (19) — да, на этих данных работать будет долго

    Но с практической точки зрения сколько в реальности может быть бухт на складе? Ну пусть по несколько тысяч…

    какая тогда разница по скорости выполнения между запросам?

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *