Виртуальная таблица «Остатки» регистра накопления и избыточные блокировки




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

16 Comments

  1. Yashazz

    Почему в виртуальной таблице нет условий на контрагента и организацию? Это, если верно помню, убыстряет дело, они ведь — первые два измерения. Смыслово это кажется избыточностью, но — Гилёв рекомендует… И второе — кто вам вот так легко даст переделывать регистры? Надо не только архитектуру курочить, но и к имеющейся верные запросы писать.

    Reply
  2. headMade

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

    И насколько я понимаю суть индексов, то даже измерение порядка следования без указания условий отбора по организации и Контрагенту не позволит на все 100 использовать индекс т.к. еще доп будет сканирование таблицы или индекса для отбора по этим измерениям.

    Reply
  3. khaoos

    Действительно, в данном случае нужно было использовать отбор в виртуальной таблице на организацию и контрагента, чтобы не «портить» регистр и использовать уже имеющийся индекс. Так как если регистр «тяжело» используется в конфигурации, нужно перелопачивать конфигурацию в поисках запросов к этому регистру с целью их исправления. Приходилось этим заниматься один раз, но это было оправдано — прирост производительности составил до шести раз. А вот индексация договора — правильная идея: именно для отбора в таблице движений, так как договор мельче «гранулирует» таблицу движений, тем самым меньше записей будет блокироваться. Автору статьи рекомендую почитать статьи с диска ИТС (раздел про вопросы крупных внедрений, кажется), там все эти вещи про блокировки есть. А также книжку Габец «Реализация прикладных задач в системе 1С Предприятие 8.2» из серии «Профессиональная разработка». Хотя если удобнее через профайлер изучать — тоже способ :).

    Reply
  4. erem

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

    Reply
  5. erem

    (2) headMade, Я смотрел план выполнения запроса в профайлере и по таблице итогов Clustered index seek выполняется без дополнительных условий (секции «Predicates» нет) и все поля отбора попадают в секцию «Seek predicates» — из этого я делаю вывод, что доп. сканирования таблицы не происходит…

    Reply
  6. director04

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

    Дело в том, что измерения «Организация» и «Контрагент» — это базовые группировки для любых отчетов. Теперь же, все остальные отчеты, которые будут использовать любые таблицы данного регистра, резко понизят свое быстродействие. Особенно, это будет сказываться при декартовом соединении.

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

    Reply
  7. 1cspecialist

    (0) Для полноты статьи я бы порекомендовал автору добавить информацию о том, почему вообще в его примере возникли блокировки, т.к. не для всех очевидно, что чтение в транзакции накладывает блокировки.

    Reply
  8. director04

    (0) Не совсем понятно, для чего вообще используется синтаксис

    ЕСТЬNULL(СУММА(РасчетыПоПриобретениюВВалютеОрганизацииОстатки.СуммаВзаиморасчетовОстаток), 0)

    Если не используется соединение, то откуда там может взяться этот самый NULL?

    А так, в целом, за пытливость ума и ковыряние в носу — наверное плюс

    Reply
  9. AlexO

    (8) director04,

    ну откуда берутся NULL в базе 1с? там все возможно словить..

    Автор же просто привел тестовый пример ))

    Reply
  10. Altair777

    (0) Очень частный и не убедительный пример. Регистр РасчетыПоПриобретениюВВалютеОрганизации, имхо, не такой критичный по производительности в базе данных.

    Есть гораздо более «тяжелые» регистры. Почему автор не приводит пример их «успешной» переделки?

    Reply
  11. vvr908

    (8) director04,

    Мне вообще неочевиден смысл конструкции ISNULL(СУММА(Поле1),0).

    Думается, что на NULL надо проверять до суммирования: СУММА(ISNULL(Поле1, 0)).

    Reply
  12. director04

    (11) vvr908, NULL в запросах может появляться в одном случае — при соединении двух таблиц. Другие случаи мне неизвестны.

    В данном случае, соединение не используется, то есть, проверка на NULL — тоже замедляет (в некоторой степени) выполнение запроса.

    Reply
  13. vvr908

    (12) director04, мы говорим о разных вещах.

    Понятно, что в данном конкретном случае проверка вообще лишняя. Я просто заметил, что имеет смысл проверять значение на NULL перед тем, как мы попытаемся что-то с ним сделать (например, взять сумму), а не после.

    Reply
  14. director04

    (13) vvr908, Согласен, сумму с NULL брать неразумно ))))

    Reply
  15. erem

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

    Reply
  16. direktorSan

    (12) director04, Если мне не изменяет память, то есть еще один случай, когда в запросе даже БЕЗ соединений может появиться NULL.

    Если для реквизита справочника свойство «Использовать» = «Для элемента» («Для группы»), то в запросе из справочника для групп (элементов) значение этого реквизита = NULL!

    Reply

Leave a Comment

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