Быстрое определение интервалов в запросе




Принцип обмена данными из 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='\

41 Comments

  1. spezc

    Спасибо за статью, интересно.

    Reply
  2. Evil Beaver

    Вот люблю я статьи маэстро! Они делают меня лучше

    Reply
  3. Поручик

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

    Reply
  4. amiralnar

    Бесподобно!

    Reply
  5. 1cmax

    Предполагаю, что у автора фундаментальное мат. образование, мало таких в мире 1с

    Reply
  6. ildarovich

    (5) 1cmax, нет, образование у меня техническое

    Reply
  7. _also

    Ничего не понял, но, на всякий случай, плюсанул )))))

    Reply
  8. qwinter

    А банальный обход этой таблицы в цикле решает эту задачу медленнее?

    Reply
  9. ildarovich

    (8) qwinter, думаю, что

    банальный обход этой таблицы в цикле

    вне запроса решает эту задачу быстрее. Даже с учетом необходимости сортировки. СКД или оконные функции в других СУБД тоже могут помочь. Однако, если интервалы нужны в самом запросе для последующей обработки, то выгоднее использовать этот подход.

    Reply
  10. qwinter

    (9) запрос конечно веселый)) 900 строчек)))

    Reply
  11. Sevg

    Хороший новый способ взгляда на проблему.

    Reply
  12. ildarovich

    Кажется, нашел еще одно важное применение данного алгоритма. Это поиск пропущенных номеров! Задача актуальна, если судить по дискуссии в Интересная задачка для решения на SQL (Найти в диапазоне номеров пропуск).

    Приведенный в статье алгоритм позволит найти все пропуски как интервалы между номерами, которые больше, чем 1. И сделает это быстро!

    Это если стоит задача найти все пропуски. Если нужно найти только первый, то данный алгоритм будет не нужен. Подойдет запрос с условием

    ГДЕ Номер + 1 НЕ В (ВЫБРАТЬ * ИЗ МножествоНомеров)

    Но в нем тоже могут быть подводные камни, связанные с возможным использованием NESTED LOOP для этой проверки. Это можно обойти через прием с группировкой.

    Reply
  13. kint

    В свое время пришлось разбираться с интервалами.

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

    Оставлю здесь ссылку на свои заметки,- возможно, кому-то пригодится.

    Самое вкусное (алгоритмы свертки и соединения), правда, там осталось за кадром (руки не дошли). Но основные понятия даны.

    Дмитрий Малюгин

    Reply
  14. ildarovich

    (13) kint, большое спасибо, интересно

    Reply
  15. BlizD

    Добрый день.

    Спасибо, за такой удивительный запрос.

    Ощущается магия какая-то.

    Применил данный метод на поиск свободных штрихкодов.

    Тридцать два тура выбрано потому, что этого будет достаточно, чтобы охватить период с 1 января 1980-го до 2 февраля 2116 года (для интервалов, измеряемых секундами).

    Правильно понимаю, что 32 тура позволяет искать до того, пока разница секунд не будет больше 4294512000?

    Т.е. в моем случае пока штрихкоды от 0 до 4294512000?

    Сформулирую по другому вопрос:

    если ищу свободные штрихкоды в интервале

    от 210000000000

    до 210001000000

    это 1 миллион штрихкодов, будет ли достаточно 32 туров?

    Reply
  16. ildarovich

    (15) BlizD, все правильно, 32 тура это 4 294 967 296 секунд — больше четырех миллиардов.

    А для миллиона штрих-кодов будет достаточно 20-ти туров, так как 2#k8SjZc9Dxk20 = 1 048 576 > 1 000 000.

    В принципе, если штрих-код у вас в базе, то это, скорее всего, строка. Поэтому для поиска пропущенных штрих-кодов можно воспользоваться отчетом из статьи Поиск пропусков в нумерации документов запросом, заменив в запросе выборку номеров документов на выборку штрих-кодов. В отчете из той статьи сразу решается задача преобразования строки в число в запросе. Для 12-ти значного штрих-кода понадобится 40 туров.

    Reply
  17. BlizD

    (16)

    Спасибо, значит все правильно сделал.

    Да, штрих-код у нас это строка и преобразование в число делал из той статьи , что указали:D

    Ищу в заданном интервале, поэтому остановлюсь на 32 турах.

    Reply
  18. Gesperid

    (13) Круто. Не могли бы, Вы, накидать источников (литературы, ссылок)? Особенно интересно по темам в разделе «К описанию» 🙂

    Reply
  19. emir99

    Мне надо узнать длительность каждого действия. Отрезков может быть несколько десятков тысяч поэтому выбрал этот алгоритм, но меня отчего-то результат не верный.

    Reply
  20. ildarovich

    (19) Если отчет на СКД, попробуйте в последний запрос добавить в принципе ненужные поля НижняяГраница, ВерхняяГраница. СКД может исключать эти поля из промежуточных запросов, что приводит к таким казусам.

    Reply
  21. emir99

    Нет, не на СКД — пока только запрос.

    Reply
  22. ildarovich

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

    Reply
  23. emir99
    Reply
  24. emir99

    Может быть эта задача так не решается? Может надо по-другому делать? Мне кажется перебором по ТЗ будет медленно — мне надо три таких набора разобрать

    Reply
  25. ildarovich

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

    Судя по запросу, определяются интервалы между отметками времени, соответствующими одинаковым действиям.

    В (19) четыре отметки, отмеченные «Пусто» и четыре отметки «ХХШ».

    С моей точки зрения, в линейке «Пусто» верно определены три интервала:

    5:01 — 6:02 — 6:13 — 7:02,

    а в линейке ХХШ определены три интервала:

    6:11 — 7:13 — 7:26 — 8:02.

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

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

    Reply
  26. ildarovich

    (24) Думаю, решается. Решение нужно всего лишь немного подправить, вы на верном пути.

    Reply
  27. emir99

    Мне бы хотелось получить следующее:

    Reply
  28. emir99

    Это данные с датчиков в автомобиле — двигатель работает/не работает, машина — движется/стоит. Соответственно надо посчитать, сколько времени ехала, сколько стояла со включенным двигателем. Плюс разные другие датчики — уровни топлива в разных баках, работа второго двигателя. Проблема в том, что разные наборы датчиков посылают сигналы в разное время. По-началу я группировал всё до минуты, собирал всё в одну таблицу и её обходил. Но на нескольких сутках округление даёт, в таком случае, большую погрешность. Если не объединять до минут, то надо делать три таких запроса, три обхода — будет долго.

    Reply
  29. ildarovich

    (28) Теперь более-менее понятно.

    Тогда действительно метод не вполне подходящий (одного его недостаточно).

    Он предназначен для поиска интервалов между событиями, а вам еще нужно будет найти: в какие интервалы постоянства значений наборов датчиков входит этот интервал.

    То есть соединить полученную таблицу интервалов с таблицей каждого набора датчиков по условию Tдатчика <= НачалоИнтервала, найти максимум Tдатчика с группировкой по индексу интервала, а затем соединить полученный максимум с таблицами, чтобы определить состояние набора датчиков.

    Это задача множественного среза последних.

    Проблемой будет поиск максимума для каждого минимального интервала.

    Это в целом затратная операция, так как ее трудоемкость NхM/2, где N — количество минимальных интервалов, а М — число значений в таблице значений наборов датчиков.

    Тут есть пара довольно головоломных подходов.

    Один на основе https://infostart.ru/public/201526/, второй попроще — упрощенная версия https://infostart.ru/public/551583/ (пока в черновиках).

    Но прежде чем их применять, нужно замерить время решения через ТЗ.

    Если оно будет удовлетворительным, ломать голову не стоит.

    Reply
  30. boln

    (0)

    Общеизвестным методом решения этой задачи в запросе является запрос следующего вида:

    ВЫБРАТЬ

    Слева.Дата КАК НачалоИнтервала,

    МИНИМУМ(Справа.Дата) КАК КонецИнтервала

    ИЗ

    Даты КАК Слева

    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Даты КАК Справа

    ПО Слева.Дата < Справа.Дата

    СГРУППИРОВАТЬ ПО

    Слева.Дата

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

    Reply
  31. ildarovich

    (30) Этого замечания я не понял.

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

    То есть если в исходной таблице есть отметки времени 5:01; 6:02; 6:13; 7:02, то должны получиться интервалы:[5:01, 6:02], [6:02, 6:13], [6:13, 7:02]. Задача, в которой интервалы имеют нулевую длину (когда отметки повторяются), не рассматривается, поскольку, как минимум, требует доопределения (еще одного поля для упорядочивания). Можно считать, что отметки времени выбираются запросом ВЫБРАТЬ РАЗЛИЧНЫЕ, что исключает их дублирование.

    Reply
  32. boln

    (31) А, я невнимательно посмотрел запрос. У меня другая таблица: два поля — Начало и Конец. У Вас одно поле — Дата. И задача у меня немного другая: я определяю запросом промежутки между интервалами. Моя ошибка, извиняюсь.

    Reply
  33. Tolpinski

    Спрошу тут, задача похожая.

    Исходная таблица c пропущенным интервалом, таких может быть несколько.

    t1-t2

    t2-t3

    t4-t5

    Как запросом добавить пропущенные интервалы?

    Задача из ЗУП, когда на период задано что-то плановое, а внутри идут временные перемещения. Промежутки между временными нужно заполнить основным.

    Reply
  34. ildarovich

    (33) Тут требуется уточнение. Если отрезки не пересекаются, то все просто. Для определения пропусков нужно соединить таблицу интервалов саму с собой примерно так:

    ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(МАКСИМУМ(Слева.До), ДЕНЬ, 1) КАК От,
    ДОБАВИТЬКДАТЕ(Интервалы.От, ДЕНЬ, -1) КАК До
    ИЗ
    Интервалы КАК Интервалы
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Интервалы КАК Слева
    ПО (Слева.До < Интервалы.От)
    СГРУППИРОВАТЬ ПО
    Интервалы.От
    ИМЕЮЩИЕ
    ДОБАВИТЬКДАТЕ(МАКСИМУМ(Слева.До), ДЕНЬ, 1) < Интервалы.От

    Показать

    Еще можно пронумеровать все крайние точки интервалов подряд и соединить соседние четные с нечетными. Так делают в SQL, где есть функция RowNumber(). Задача называется Gaps and Islands Problem.

    Если интервалы в исходной таблице могут пересекаться, то тут два пути:

    Первый: Соединить таблицу с производственным календарем, чтобы определить дни, не входящий ни в один интервал, а затем сгруппировать соседние дни, чтобы получить сами интервалы. По принципу задачи 14 из Минимализмы.

    Второй: посчитать нарастающий итог, где начало интервала делает +1, а конец -1. Тогда пропуски будут соответствовать интервалам, где нарастающий итог нулевой.

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

    Reply
  35. adva

    Есть ли какое-нибудь математическое «решение», у следующей задачи:

    Дано:

    в телесистему (виалон) можно сделать запрос по одному/нескольким объектам, но строго за один интервал;

    в системе 1С у объектов интервалы по большому счету разные (дата выезда и убытия по ПЛ);

    количество запросов в телесистему лимитировано;

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

    Необходимо как-то минимизировать количество запросов в телесистему, и при этом минимизировать запись в системе 1С

    Reply
  36. ildarovich

    Задача кажется интересной, нужной, сложной, но данных для выбора способа решения недостаточно.

    Нужно знать структуру полей таблицы, возвращаемой по запросу из Виалон, метод определения стоимости запроса (стоимость определяется или числом запросов, числом строк в запросе, числом объектов и прочее), а также структуру регистра для хранения полученных данных в 1С.

    Reply
  37. adva

    В 1С предполагает примерно такая структура (упрощенно), РС: измерения — ИдОбъекта (строка), ДатаУбыт, ДатаПриб (даты со временем), ВидКэша (по сути вид таблицы из виалон), ресурсы — моточасы, пробег, время, начальное и конечное положение (строка) и другие числовые.

    Тут еще думаю добавить измерение Сутки (чтобы можно было наборы писать посуточно), в итоге записей по объекту может быть 4-100 за сутки.

    Сколько объектов может быть запрошено в запросе максимально, пока не знаю, но ограничения точно есть, и их полно: если за 5 минут запрос не выполняется, он прерывается, поэтому например за час данные можно получить сразу по всем объектам, а за сутки, уже надо снижать количество объектов (пробовал запрашивать по 100 объектам, за сутки выдавало результат). Передать в запрос тоже можно ограниченное число объектов для запроса, но как уже отметил, максимальное значение не знаю. Строк возвращается всегда разное количество, ответ (json) разбит на 4 таблицы, в 2х из них есть несоприкасающиеся интервалы, а в двух других, просто определенные моменты времени (даты заправок, и т.п.).

    Из ограничений еще такое важное (ограничения указаны на сайте виалона, не знаю, можно ли тут ссылки приводить): по одному пользователю можно запросить за 5 минут не более 200 запросов (вроде как это по всем сессиям пользователя, и, не совсем понял, ранее у них не было указано, что это запросы определенного типа, а сейчас уточнено, что report/exec_report . Этот момент пока не тестировал, но в целом, на один такой запрос, приходится 6-7 других

    Объектов может быть порядка 1200-1500 (может и больше, но у меня в имеющихся тестовых базах примерно столько)

    Если этой информации недостаточно, то к сожалению, на текущей момент другой не владею

    Reply
  38. adva

    Как предполагал решить задачу сам: сначала загрузить за сутки по группам объектов (скажем по 100 штук), а затем, если, границы интервала по объекту «попадают» в загруженные подинтервалы, то эти подинтервалы разделить исходя из нужных границ.

    В принципе такую реализацию сейчас и буду доделывать, но первая версия показала, что выполняется это очень долго (порядка 10 часов), с учетом ночных «перегрузок» сервера 1С неприемлемо, задача, сократить хотя бы до 5 часов.

    Думаю тут выигрыш дадут запись наборами за сутки, и сейчас у меня разбивка поинтервалов идет по каждой из «таблиц» виалона (т.е. 2 таблицы на 2 границы — до 4х запросов в виалон), это сокращу строго на 1 запрос. В принципе если смогу сократить до 5 часов загрузку, то задача решена, и наверное переделывать не буду.

    Пробовал еще такую версию: разбивал сутки на подпериоды по всем границам ПЛ и запрашивал сразу по всем объектам, но в итоге загрузка также была в пределах 10 часов, и обеспечить «посуточную» загрузку оказалось сложно, в случае ошибки при загрузке (или не успел придумать вариант). Тут наверное как то надо было разделять с учетом ИД объектов для каждой границы, но я не сообразил, как

    Reply
  39. adva

    Сергей, спасибо за участие.

    В итоге выяснил, что самый большой проигрыш почти половина времени уходила на разбор «самописными» процедурами JSON. Переделал на платформенное, и еще некоторые моменты поправил, в итоге добился 3х часов.

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

    Reply
  40. adva

    А еще хотел бы узнать, есть ли какая-то возможность «сгруппировать» в запросе по интервалам ? Не по периодам, а именно, что интервалы в записях:

    1 Дата1, Дата2

    2 Дата1, Дата3

    3 Дата1, Дата3

    В итоге записи 2 и 3 — это «группа интервалов»

    Reply
  41. XelOla

    потуплю…)

    можете помочь с простым скд ?

    надо отобрать реализации группируя их по 15 мин, условно начиная с 9:00:00?

    Reply

Leave a Comment

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