Сложные запросы в динамическом списке или как не надо писать запросы для них




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

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

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

<?php // Полная загрузка сервисных книжек, создан 2025-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='\

38 Comments

  1. rozer

    а где НЕ В ИЕРАРХИИ? вот где жесть-то )

    Reply
  2. pbazeliuk

    1. На перед, не известно какой план запроса получится

    2. Логику кэширования лучше оставить. У нашей организации бывают пользователи что 14 дней базу не закрывали, и так же обработки. Регламентные работы 1 раз в 14 дней, максимально сеансов бывает и до 600, максимальных соединений до 200.

    1,4. Такие запросы признак проблем с проектированием

    Reply
  3. vasyak319

    Хаус + запросы = ildarovich

    Лучше смените иллюстрацию, а то складывается негативное впечатление, будто вы товарный знак стырили.

    Я не говорю, что вы так и сделали, но впечатление складывается.

    Reply
  4. Nefertary

    (3) true SQL DBA,

    ну если еще глубже посмотреть, то СГРУППИРОВАТЬ ПО оптимальнее чем ВЫБРАТЬ РАЗЛИЧНЫЕ

    Reply
  5. vandalsvq

    Предпочитаю регистры сведений с доп. данными для отдельно взятых случаев (вроде 1 и 4), при этом запись в эти регистры стараюсь делать в фоновых заданиях.

    В случаях 2 и 3 обычно как параметр не ставлю в запрос, а накладываю отбор.

    Reply
  6. Aleksey.Bochkov

    (4) vasyak319,

    Эх.. на выбор картинки ушло больше времени, чем на написание текста :).

    Поменял.

    Reply
  7. vasyak319

    А вы уверены, что Пример 1 работоспособен? А то 1С утверждает, что когерентные запросы возможны только в секции «ГДЕ» и мои попытки игнорировать это утверждение всегда натыкались на то, что оно оказывалось верным.

    Пример 2: можно просто переписать условие как «Р.Склад В (срез последних)» и никаких тебе соединений таблиц с одной стороны и никаких необходимостей переоткрывать форму — с другой.

    Пример 3: А вы смотрели в SQL профайлере, как 1С преобразует конструкцию «В ИЕРАРХИИ»? Я, если что, сам не смотрел, но там по-любому должно быть что-то своё, одинэсовское, потому что SQL про группы не в курсе и без такого анализа ценность вашего предложения нулевая. Вполне возможно, что сервер 1С сам делает преобразование, аналогичное предложенному вами, так что ваш способ будет тормозить даже сильнее, потому что у вас два обращения к серверу вместо одного и передача списка ссылок вместо одной.

    Пример 4: «Как мне кажется»

    В таких случах надо смотреть план запроса, иначе это не оптимизация, а гадание.

    Reply
  8. Aleksey.Bochkov

    (3) true SQL DBA,

    незачет :).

    Во вложенной таблице выборка без фильтра.

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

    А план запроса покажет перебор большого числа записей из счет-фактур.

    Reply
  9. Aleksey.Bochkov

    (1) rozer,

    На самом деле, такая же ситуация как в третьем примере. Избавление от «В ИЕРАРХИИ» несколько улучшит ситуацию.

    Reply
  10. Aleksey.Bochkov

    (2) pbazeliuk,

    1. На перед, не известно какой план запроса получится

    Вполне известно — получается лучше.

    2. Логику кэширования лучше оставить. У нашей организации бывают пользователи что 14 дней базу не закрывали, и так же обработки. Регламентные работы 1 раз в 14 дней, максимально сеансов бывает и до 600, максимальных соединений до 200.

    Тут нужно найти баланс. Проведение вычислений при каждом обновлении динамического списка мне видится нецелесообразным.

    Как вариант — в форме можно сделать обработчик ожидания, срабатывающий каждый час и обновляющий эти значения.

    1,4. Такие запросы признак проблем с проектированием

    Каким вы видите оптимальное архитектурное решение в обоих случаях?

    Reply
  11. Aleksey.Bochkov

    (7) vasyak319,

    А вы уверены, что Пример 1 работоспособен? А то 1С утверждает, что когерентные запросы возможны только в секции «ГДЕ» и мои попытки игнорировать это утверждение всегда натыкались на то, что оно оказывалось верным.

    Данный пример вполне работоспособный.

    Пример 2: можно просто переписать условие как «Р.Склад В (срез последних)» и никаких тебе соединений таблиц с одной стороны и никаких необходимостей переоткрывать форму — с другой.

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

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

    Пример 3: А вы смотрели в SQL профайлере, как 1С преобразует конструкцию «В ИЕРАРХИИ»? Я, если что, сам не смотрел, но там по-любому должно быть что-то своё, одинэсовское, потому что SQL про группы не в курсе и без такого анализа ценность вашего предложения нулевая.

    Все четыре примера успешно опробованы не раз на разных базах. «В ИЕРАРХИИ» — это запрос в цикле.

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

    Вы не обладаете информацией, а делаете далеко идущие выводы.. проверить то не сложно :).

    Не делает он такого преобразования, и обращений к серверу СУБД в моем случае получается меньше.

    Пример 4: «Как мне кажется»

    В таких случах надо смотреть план запроса, иначе это не оптимизация, а гадание.

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

    Кстати, в типовом Документообороте подход с кэшированием успешно применяется для ряда объектов.

    Reply
  12. Aleksey.Bochkov

    (6) vandalsvq,

    Предпочитаю регистры сведений с доп. данными для отдельно взятых случаев (вроде 1 и 4), при этом запись в эти регистры стараюсь делать в фоновых заданиях.

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

    Информация обновляется оперативнее, да и сложность реализации меньшая.

    В случаях 2 и 3 обычно как параметр не ставлю в запрос, а накладываю отбор.

    На самом деле, точно такая же ситуация — если делать отбор «В ГРУППЕ», то при каждом обновлении динамического списка будет происходить выборка иерархии.

    Выбрав иерархию самостоятельно и передав массив объектов в отбор просто с видом сравнения «В СПИСКЕ» можно ускорить обновление динамического списка.

    Reply
  13. vasyak319

    (12)

    Тут даже планы запросов не надо смотреть, чтобы увидеть более эффективный вариант

    Да нет, надо. Иногда преобразование запроса, которое вроде бы должно только добавить тормозов, уменьшает время его выполнения на порядок-другой. И даже глядя на результат не понимаешь, почему так. А ответ в плане запроса.

    Reply
  14. ivanov660

    По пункту 4.

    Решение с созданием дополнительных таблиц следует использовать если подобная операция довольно частая и вносит «определенную нагрузку».

    Reply
  15. Гость

    (9)

    незачет :).

    Во вложенной таблице выборка без фильтра.

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

    А план запроса покажет перебор большого числа записей из счет-фактур.

    Все равно плохо!

    Мой запрос работает быстрее, а тот, который вы оптимизируете (с группировками) — еще быстрее!!!

    (5) Nefertary — вы правы.

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

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

    Пример — РС НаличиеПрисоединенныхФайлов в типовой БП 3.0, это механизмы БСП.

    Хороших выходных!

    Reply
  16. Aleksey.Bochkov

    (16) true SQL DBA,

    Так проводить замеры совсем некорректно.

    Это как раз и есть решение «в-лоб».

    Вы не учли особенности динамического списка — данные выбираются порциями (к тексту запроса добавляется выборка первых записей «ПЕРВЫЕ N», в условие добавляется граничная ссылка на объект и добавляется сортировка). Т.е. фактически платформа выполняет другой запрос.

    Создайте динамические списки на основе этих запросов и снимите трассировку через MSSQL Profiler.

    Похоже надо расширить статью и показать планы запросов с пояснениями.. не хотелось перегружать на самом деле.

    Reply
  17. vandalsvq

    (13)

    На самом деле, точно такая же ситуация — если делать отбор «В ГРУППЕ», то при каждом обновлении динамического списка будет происходить выборка иерархии.

    Ты меня малость не понял, список элементов формирую как и ты писал, до передачи в отбор. Просто вместо параметра отбор «В СПИСКЕ».

    Информация обновляется оперативнее, да и сложность реализации меньшая.

    Вопрос что мы записываем туда, реквизиты из объекта, согласен, накладных копейки. А если делаем пересчет статистических показателей — лучше уж в фон. Тем более с использованием БСП проблем никаких, пишешь «правильно» процедуру, тестируешь без фона, потом просто обертку из ДлительныеОперации делаешь и все.

    к тексту запроса добавляется выборка первых записей «ПЕРВЫЕ N», в условие добавляется граничная ссылка на объект и добавляется сортировка

    Насколько я осведомлен, ПЕРВЫЕ n будут выбираться только если порционное (динамическое) чтение данных стоит, а оно будет если у списка платформа может определить ключ. В свою очередь это относится только к запросам на основании объектов метаданных.

    Оффтоп: в этом плане в 1С++ (на 7.7) динамические списки были намного шире, их можно было наполнять собственным запросом при условии установки уникального ключа. Т.е. не было большой необходимости привязываться к метаданным.

    Reply
  18. Aleksey.Bochkov

    (18) vandalsvq,

    Да, согласен по тому, что ты написал.

    Насколько я осведомлен, ПЕРВЫЕ n будут выбираться только если порционное (динамическое) чтение данных стоит, а оно будет если у списка платформа может определить ключ.

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

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

    В свою очередь это относится только к запросам на основании объектов метаданных.

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

    Reply
  19. vandalsvq

    (19)

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

    Имел в виду указывать или нет основную таблицу в форме редактирования запроса динамического списка.

    Reply
  20. Rustig

    (0) в чем преимущества использования динамических списков в сравнении с использованием АРМ разработанным на обычных неуправляемых формах?

    делали замеры производительности использования динамических списков и использования списков обычных форм?

    Reply
  21. Aleksey.Bochkov

    (21) Rustig,

    Мне кажется, что сравнивать обычные формы списков и управлямые динамические формы списков нельзя. Слишком большая разница в доступном функционале.

    При одинаковых условиях (выборка только из основной таблицы) оба механизма работают одинаково быстро.

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

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

    Reply
  22. Kabz

    (21) Rustig, НЕ всегда возможно делать обычные списки (особенно когда конфигурация написана в такси 🙂 )

    Reply
  23. sashapere

    Добрый день! можете подсказать какими инструментами делали анализ производительности запроса?

    Reply
  24. Aleksey.Bochkov

    (24) sashapere,

    SQL Server Profiler

    http://infostart.ru/public/252236/

    http://infostart.ru/public/291874/

    Скрины планов запросов для большей наглядности сделал с помощью http://www.sqlsentry.com/products/plan-explorer/

    Reply
  25. Mortiferus

    (25) последняя ссылка не работает.

    Reply
  26. Aleksey.Bochkov

    (26) Mortiferus,

    видимо, автор снял публикацию (там скрины были битые и я просил автора это поправить).

    Reply
  27. Aleksey.Bochkov

    UPD. Добавил пятый пункт про опциональные отборы.

    Reply
  28. bulpi

    (3) true SQL DBA,

    Запрос, ИМХО, неверный

    1)Использование слова РАЗЛИЧНЫЕ во вложенном запросе не оправдано

    2)Его нужно использовать в основном запросе, иначе записи не будут уникальными в случае >1 счета-фактуры на 1 реализацию.

    ВЫБРАТЬ РАЗЛИЧНЫЕ
    Р.Ссылка,
    Р.Контрагент,
    Р.Склад,
    ЕСТЬNULL(Х.Признак, ЛОЖЬ) КАК ЕстьСФ
    ИЗ
    Документ.РеализацияТоваровУслуг КАК Р
    ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
    ИСТИНА КАК Признак,
    Х.ДокументОснование КАК ДокументОснование
    ИЗ
    Документ.СчетФактураВыданный КАК Х) КАК Х
    ПО Р.Ссылка = Х.ДокументОснование
    

    Показать

    Reply
  29. nvv1970

    (29) bulpi,

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

    Поле признак избыточно, т.к. можно выводить поле (НЕ x.ДокументОснование есть NULL)

    Reply
  30. nvv1970

    Предложенный пример #1 самый непроизводительный, причем к гигантским отрывом (медленнее в несколько сотен раз) Всех прочих. Подвисания дикие… до нескольких десятков секунд! Вычисление поля в цикле…

    ВЫБРАТЬ
    Р.Ссылка,
    Р.Контрагент,
    Р.Склад,
    ВЫБОР
    КОГДА 1 В
    (ВЫБРАТЬ ПЕРВЫЕ 1
    1
    ИЗ
    Документ.СчетФактураВыданный КАК Х
    ГДЕ
    Р.Ссылка = Х.ДокументОснование)
    ТОГДА ИСТИНА
    ИНАЧЕ ЛОЖЬ
    КОНЕЦ КАК ЕстьСФ
    ИЗ
    Документ.РеализацияТоваровУслуг КАК Р

    Показать

    Reply
  31. Aleksey.Bochkov

    (31) nvv1970,

    Об этом сказано в конце примера — данный запрос хорош ТОЛЬКО для динамического списка с динамическим считыванием данных.

    Reply
  32. Sergey.Noskov
    При этом следует учесть, что в платформе есть определенное ограничение на количество параметров, передаваемых в запрос (256?).

    если больше 128 элементов, то платформа будет создавать временную таблицу, причем индексированную

    Reply
  33. Fragster

    Про пример 1. А как в таком случае будет работать отбор ДС по полю «признак»? не умрет ли он совсем по сравнению с левым соединением?

    Reply
  34. Fragster

    Про пример 3. Проводили исследования про использование В ИЕРАРХИИ через доп РС с данными для nested sets?

    Reply
  35. tank68

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

    Reply
  36. Infector

    На днях пришел к выводу, что есть ситуации, когда динамический список вообще стоит выкинуть, прицепить к форме таблицу значений и активнее использовать

    Элементы.Таб.ОтборСтрок

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

    Reply
  37. intehof

    (28) Направьте меня пожалуйста на опциональные функции компоновщика — где почитать?

    Правильно ли понимаю для Примера 5: Если параметр «Автор комментария» не заполнен и поля второй таблицы заключены в фиг скобки то левого соединения не происходит?

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

    Reply
  38. nvv1970

    (5) всегда было интересно чем, если план будет абсолютно одинаковый.

    Reply

Leave a Comment

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