Простой способ индексирования интервалов




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

21 Comments

  1. Makushimo

    абалдеть !

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

    Reply
  2. Alias

    Очень красиво, спасибо большое. И вдобавок ещё и полезно. 🙂

    Reply
  3. igormiro

    Очередная статья мастера. По заголовку статьи знаешь, чья статься. Спасибо.

    Reply
  4. Sergey.Noskov

    Очень интересно и наглядно.

    Вопрос, Не пытались архитектуру регистра заточить под использование кластерного индекса? Судя по последнему плану запроса основную часть ресурсов потребляет операция «Поиск ключа», судя по всему это проверка части условия «..ДатаСменыСтатуса > &Т0..» + получение ресурса «Статус», сделав ДатаСменыСтатуса в составе измерений, уже можно получить профит, а если кластерным будет ключ [Проекция, Период, ДатаСменыСтатуса…], то должны получить максимальную скорость.

    Reply
  5. ildarovich

    (4) Sergey.Noskov, такая мысль была.

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

    Reply
  6. vspl

    Не хочу придираться к сути самой статьи, там все, наверное, правильно,

    но для чистоты эксперимента в «Задаче о статусах заявок» нужно все-таки сравнивать быстродействие вот с этим запросом:

    ВЫБРАТЬ * ИЗ РегистрСведений.СтатусыЗаявок.СрезПоследних(&Т0, Статус В (&Новый, &ВРаботе))

    При условии, конечно, что Статус — индексированный

    В этом случае результаты будут ИМХО не такие радужные

    Reply
  7. ildarovich

    (6) vspl, это не эквивалентные запросы.

    Reply
  8. kare

    Спасибо, красиво!

    Reply
  9. speshuric

    (6) При небольшом количестве статусов (я думаю, до 50, могу проверить) СрезПоследних без явного отбора по заявкам всегда будет уходить в скан. Про это и была исходная задача. Задача описанная в этой статье — это вторая часть той задачи и она не эквивалентна срезу да и не решается только через срез.

    ildarovich на самом деле молодец, сразу увидел главную граблю задачи по интервалам.

    Reply
  10. bulpi

    Задача со статусами , ИМХО, проще решается путем ввода условия

    РегистрСведений.СтатусыЗаявок.СрезПоследних(&Т0, Период>=&ДатаПотериИнтереса)

    , где ДатаПотериИнтереса =Т0- ИнтервалИнтересаВСекундах

    , где ИнтервалИнтересаВСекундах — интервал, дальше которого пофиг на все незакрытые статусы. Такой интервал в реальных задачах всегда есть. Пусть даже большой. Например, год или два.

    Reply
  11. speshuric

    (10) bulpi, Если этот интервал больше 10% от общего времени жизни — будут сканы. Точнее так: в 8.2 будет сканирование большого диапазона кластеризованного индекса, в 8.3 скорее всего будет полный скан (судя по освещённым в Sergey.Noskov изменениям).

    Reply
  12. Sergey.Noskov

    (5) а в диаграмме значения «Без индексирования» и «С индексированием» это сравнение каких запросов? Можно план запроса для «Без индексирования» увидеть?

    Reply
  13. ildarovich

    (12) Sergey.Noskov, «с индексированием» это запрос с отбором по проекции:

    ВЫБРАТЬ * ИЗ РегистрСведений.Дано
    ГДЕ Проекция В (&МассивПроекций) И &МоментВремени МЕЖДУ НачалоИнтервала И КонецИнтервала

    Без индексирования — исходный запрос без такого отбора:

    ВЫБРАТЬ * ИЗ Дано
    ГДЕ &МоментВремени МЕЖДУ НачалоИнтервала И КонецИнтервала

    Вот его план (в двух видах в приложенных файлах):

    Не увидел там ничего интересного, поэтому не стал приводить в статье.

    Reply
  14. user614720_vinzax

    Правда есть один забавный момент — есть такая константа «НеИспользоватьРазделениеПоОбластямДанных», так вот чтобы был доступен пункт с дополнительными отчетами и обработками она должна быть установлена в «Истина». Устанавливаться она должна, по идее, автоматически, но, видимо, разработчики БСП где-то что-то упустили и по умолчанию установлено значение «Ложь».

    Можно зайти через «все функции», найти эту константу и установив значение «Истина» получить доступ к настройке внешних отчетов и обработок.

    Reply
  15. Serg O.

    крутатень! Ещё одно доказательство, что Правильная архитектура — очень важна!

    мат. подход нужен, когда уже не помогает подход с матом

    Reply
  16. kasper076

    Решил «реиндексировать» РС «СостоянияСогласованияЗаявок» УПП. При заполнении реквизита «Проекция» периодически возникала ошибка «Неправильное значение аргумента встроенной функции (Log)». Анализ показал, что в случае когда соседние записи в РС идут с одной и той же датой в поле период, то код

    log(Макс(До — От, От — До)

    выдает ошибку, т.к. log(0) не существует. Как решить эту проблему? Я понимаю, что записи в пределах секунды разнесены, но как учесть это в алгоритме расчета проекции не понимаю.

    Reply
  17. Release

    (17)Почему бы для До, От не использовать МоментВремени()?

    Reply
  18. kasper076

    (18) между моментами времени нельзя вычислить разность.

    Reply
  19. ildarovich

    (17) Проще всего заменить выражение

    Масштаб = pow(2, 99 — Цел(99 — Окр(log(Макс(До — От, От — До)) / log(2), 10)));

    выражением

    Масштаб = 1;

    Тогда в следующем цикле будет выполняться больше итераций, а результат будет тем же. Ну, а если все же хочется убрать «лишние» итерации, то нужно проверить условие «От = До». Если оно выполняется, то масштаб принять равным единице:

    Масштаб = ?(От = До, 1, pow(2, 99 — Цел(99 — Окр(log(Макс(До — От, От — До)) / log(2), 10))));

    Кажется, так.

    Reply
  20. kasper076

    (20) Спасибо. Сейчас попробую.

    Еще такой момент. Возможно обработка «Реиндексация» писалась на скорую руку и предполагалось, что конечный пользователь сам ее оптимизирует. Спасибо и за это. А возможно что-то упускаю и нужно было сделать именно так, как сделано.

    Вот что есть сейчас:

    НаборЗаписей = РегистрыСведений.СтатусыЗаявок.СоздатьНаборЗаписей();
    НаборЗаписей.Прочитать();
    Таблица = НаборЗаписей.Выгрузить();
    Таблица.Сортировать(«Заявка, Период»);
    

    При наличии в ТЗ ~2,5 млн строк Таблица.Сортировать(«Заявка, Период») выполняется оч долго. Сделал так:

    Запрос = Новый Запрос(
    «ВЫБРАТЬ *
    |ИЗ
    | РегистрСведений.СостоянияСогласованияЗаявок КАК СостоянияСогласованияЗаявок
    |
    |УПОРЯДОЧИТЬ ПО
    | Заявка,
    | Период
    |АВТОУПОРЯДОЧИВАНИЕ»);
    
    Таблица = Запрос.Выполнить().Выгрузить();
    

    Показать

    Reply
  21. ildarovich

    (21) Если вы про обработку «Реиндексация» из примеров к статье, то все так и было.

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

    Reply

Leave a Comment

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