Дополнительные возможности параметров, передаваемых в запросе




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

    Норм. Не знал,что так можно. Типа недокументированная фича что ли ?

    Reply
  2. TMV

    (0), Интересная находка, но все же предпочитаю, чтобы конструктор запроса не выдавал ошибок о невозможности чтения текста запроса.

    Reply
  3. ShantinTD

    О_о. Прикольно. И иногда (насколько часто?) полезно. Сам тоже предпочитаю конструкторопригодный запрос, так что согласен с (2). Но на заметку возьму!

    Reply
  4. mr.Kot

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

    Reply
  5. ShantinTD

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

    Поясню: в приведенном в (0) запросе можно написать &Организация_Код, и запрос можно будет открыть редактором. Если произвести в тексте запроса замену (самым простым СтрЗаменить(…)) на &Организация.Код, то получим запрос «с хитростью». А можно и не подменять ничего, а просто передать в запрос несколько «лишних» параметров. Для новичков это должно быть проще, чем редактировать запрос «руками».

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

    Reply
  6. Yashazz

    (2) Конструктор иногда сам, например, сгенерит текст, и сам же его открыть не может. Так что в (5) правильно сказано — всякими строковыми играми можно его обмануть и выкрутиться.

    Reply
  7. asved.ru

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

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

    Reply
  8. danila_zlt
    ВЫРАЗИТЬ(&Организация как Справочник.Организации).Код

    И конструктор не будет ругаться.

    Reply
  9. МимохожийОднако

    Кто-нибудь делал замер подобной конструкции по сравнению со стандартным запросом? Подход любопытный, но как это влияет на производительность и какой результирующий запрос будет к SQL-серверу?

    Reply
  10. vde69

    рекомендую использовать (8) так как (0) грозит запросом к метаданным строк этак на 500.

    Вообще любые нетипизированые вещи в запросе — зло, именно по этому рекомендуется использовать типизированые ТЗ в качестве параметров.

    Reply
  11. 7OH

    Хм.

    А не легче ли пользоваться левым соединением, чем писать нечитаемые запросы ?

    Ну или

    Запрос = Новый Запрос;
    Запрос.Текст = «ВЫБРАТЬ РАЗРЕШЕННЫЕ
    |ИсполнительныйЛист.Организация КАК Организация,
    |ИсполнительныйЛист.Физлицо КАК Сотрудник,
    |ИсполнительныйЛист.Предел КАК СуммаВсего
    |
    |ИЗ
    |Документ.ИсполнительныйЛист КАК ИсполнительныйЛист
    |
    |ГДЕ
    |ВЫБОР КОГДА (&другаяОрганизация = &Организация01 )
    |     ТОГДА ВЫРАЗИТЬ(ИсполнительныйЛист.Организация.Код КАК СТРОКА(2)) В («01″,»02″,»53″)
    |     ИНАЧЕ
    |         ИсполнительныйЛист.Организация = &Организация
    |КОНЕЦ»;
    Запрос.УстановитьПараметр(«Организация01», Справочник.Организации.НайтиПоКоду(«01»));
    Запрос.УстановитьПараметр(«Организация», другаяОрганизация);
    

    Показать

    Reply
  12. ShantinTD

    (11) 7OH, Какова судьба параметра &другаяОрганизация ?

    Еще пример: есть справочник, у него в реквизитах хранятся 2 даты — начало и конец периода (за который предполагается провести некую оценку). Так вот предложенный в (8) приём позволит передать всего один параметр, и спокойно работать с его реквизитами.

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

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

    А по поводу Вашего «ну или»: в (0) вопрос ставился именно о том, чтобы передавать параметр и обращаться к его реквизитам. В (8) было предложено как при этом сделать запрос пригодным для редактирования конструктором (да и на глаз он сильно страдает). В предложенном Вами варианте нет обращения к реквизитам параметра. В чем «дополнительная возможность использования параметра»? В варианте с «левым соединением» аналогично не будет обращения к реквизитам параметра, да еще и усложнение запроса убавит его читаемости и добавит узких мест (в том смысле, что на лишнем соединении больше шансов накосячить). Кстати: если в том месте, где появится это самое «лишнее» соединение и без того уже есть соединение (нужное, не лишнее), то вероятность накосячить в соединениях многократно возрастает. А обращение к реквизитам параметра позволит спокойно описать все нужные условия или связи.

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

    Reply
  13. bird21

    Любопытно, не сталкивался раньше с таким.

    Reply
  14. Damian

    Книга «Язык запросов 1С:Предприятие 8» от Хрусталевой и Радченко (2013 год) в разделе про оптимизацию запросов настоятельно рекомендует воздержаться от применения каких-либо функций к передаваемым в запрос параметрам. Лучше все необходимое вычислить снаружи и передать в запрос уже константы.

    Даже банальное «НАЧАЛОПЕРИОДА(&ДатаОстатков,ДЕНЬ)» не советуют использовать в тексте запроса.

    Reply
  15. bforce

    По хорошему такие запросы нужно писать так.

    Способ 1. Получаем нужные данные в коде, а не в запросе.

    |ВЫБОР

    | КОГДА &Организация = &ОсобаяОрганизация

    | ТОГДА ИсполнительныйЛист.Организация В (&МассивОгранизаций)

    | ИНАЧЕ

    | ИсполнительныйЛист.Организация = &Организация

    |КОНЕЦ

    Запрос.УстановитьПараметр(«Организация», Документ.Огранизация);

    Запрос.УстановитьПараметр(«ОсобаяОрганизация», Справочник.Организации.НайтиПоКоду(«01»)); // А лучше здесь завести константу или обращаться к регистру сведений.

    Запрос.УстановитьПараметр(«МассивОгранизаций», СписокОгранизаций);

    Способ 2. Избавляемся от сложной конструкции в условии ГДЕ (для СУБД так будет легче).

    |ГДЕ

    | ИсполнительныйЛист.Организация В (&МассивОгранизаций)

    Запрос.УстановитьПараметр(«МассивОгранизаций», УжеПодготовленныйСписокВашихОрганизаций);

    Способ 3. Для тех, случаев, когда избежать обращения через точку не удается.

    |ВЫБОР

    | КОГДА

    | ВЫРАЗИТЬ (&Организация КАК Справочник.Организации).ИНН ПОДОБНО «68465_»

    |КОНЕЦ

    Хотя, я, наверное, излишне помешан на оптимизации. Работа такая. Приношу извинения.

    Reply
  16. asved.ru

    bforce,

    | ВЫРАЗИТЬ (&Организация КАК Справочник.Организации).ИНН ПОДОБНО «68465_»(15)

    Применение ВЫРАЗИТЬ здесь никакой смысловой нагрузки не несет, т.к. платформа определяет тип параметра, и он у нас в данном случае простой.

    ВЫРАЗИТЬ применяется к полю составного типа для ограничения типов (а следовательно, количества соединений) данных, которые требуют обращения через точку.

    Грубо говоря, если у нас есть поле составного типа Регистратор {ДокументСсылка.Реализация, ДокументСсылка.Поступление}

    и мы пишем «ВЫБРАТЬ Регистратор.Дата» то запрос будет состоять из двух соединений:

    1) Наша исходная таблица с таблицей Документ.Поступление по равенству поля Ссылка

    1) Наша исходная таблица с таблицей Документ.Реализация по равенству поля Ссылка

    А если мы напишем «ВЫБРАТЬ ВЫРАЗИТЬ(Регистратор КАК Документ.Реализация).Дата»,

    то соединение будет только одно:

    Наша исходная таблица с таблицей Документ.Реализация по равенству поля Ссылка

    второе соединение будет отфильтровано именно применением оператора ВЫРАЗИТЬ

    Reply
  17. asved.ru

    (14) Damian, это общая рекомендация. На самом деле так делать можно, если понимаешь, что делаешь и сколько раз будет выполнено вычисление значения.

    Reply
  18. bforce

    (16), ВЫРАЗИТЬ здесь имеет смысл потому, что я привожу параметр к определенному типу и гарантированно не получу ошибки в запросе при обращении к реквизиту объекта. Кроме того, запрос в конструкторе нормально открывается (автору это, кажется, было важно).

    Если вы предпочитаете такую конструкцию

    ВЫРАЗИТЬ((&Организация).Код КАК СТРОКА(2)

    ,

    то должны в полной мере понимать что это за собой может повлечь.

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

    (14), и правильно пишут, причем? не только они. Здесь, под заголовком «Эффективные планы запросов», как раз такой пример и приводится. Хотя, кого это интересует? Лучше ж «на коленке» написать и не думать о последствиях! Если у разработчика база с тремя пользователями, то такое, конечно же, прокатит, и он не заметит разницы.

    Reply
  19. qwinter

    (12) ShantinTD, учитывайте, что при каждом обращении через точку, будет еще одна (и одну ли?) временная таблица.

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

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

    Еще пример: есть справочник, у него в реквизитах хранятся 2 даты — начало и конец периода (за который предполагается провести некую оценку). Так вот предложенный в (8) приём позволит передать всего один параметр, и спокойно работать с его реквизитами.

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

    Reply
  20. omut

    Интересный момент. По кэшу: на сколько помню, получение ссылки не означает чтение реквизитов. В связи с этим, нужно смотреть контекст выполнения запроса. Если до выполнения реквизиты прочитаны, то смысла изгаляться подобным образом нет. Вред один. А вот если не прочитаны, то потенциально можно получить даже увеличение производительности. Поправьте, если не прав.

    Reply
  21. qwinter

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

    Reply

Leave a Comment

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