Типичные ошибки, некоторые вопросы качества и эффективности работы при разработке в 1С




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

40 Comments

  1. TODD22

    п.8 Какой то не очень удачный пример.

    используйте условия проверки РезультатЗапроса.Пустой() или Выборка.Следующий().

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

    Reply
  2. baton_pk
    … Если НЕ Результат=Неопределено ИЛИ Результат=Истина Тогда … // правильно
    

    ААА! Можно мне развидеть это?!

    Reply
  3. peterxx

    IRL не все, и не так уж очевидно.

    Например:

    п. 11. Сами разработчики 1С зачастую забивают на это правило. Кроме того если речь идет об обработке, которая запускается раз в месяц, не имеет значение будет ли она выполняться 30 секунд или 10 минут.

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

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

    Reply
  4. TODD22

    (2) baton_pk,

    ААА! Можно мне развидеть это?!

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

    Reply
  5. ivanov660

    (4) TODD22, мы не старались описать все возможные варианты и привести решения, на самом деле показан один из примеров реализации. Для случая когда результат не только булево решение такое но более элегантное:

    …
    Если НЕ Результат=Неопределено // не забыть перенос на другую строку :-)))
    И Результат Тогда
    …
    КонецЕсли;
    …
    
    Reply
  6. ivanov660

    (1) TODD22, Можно привести совсем идеальный правильный вариант:

    …
    Результат = Запрос.Выполнить();
    Если НЕ Результат.Пустой() Тогда
    Выборка = Результат.Выбрать();
    Пока Выборка.Следующий() Цикл
    …
    КонецЦикла;
    КонецЕсли;
    …
    

    Показать

    Reply
  7. ivanov660

    (3) peterxx,

    Сами разработчики 1С зачастую забивают на это правило.

    Давайте стремиться к лучшему, а то получается какая-то «круговая порука».

    БСП тоже не панацея. 

    Никто не говорит, что она идеальна, но очень много функций уже в ней есть.

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

    А вот тут надо стараться хотя бы разрабатывать функционал сверху вниз, а не снизу вверх, тогда избежать такой ситуации гораздо проще. Хотя не всегда подобное получается.

    Reply
  8. TODD22

    (7)

    Никто не говорит, что она идеальна, но очень много функций уже в ней есть.

    Сталкивался пару раз с тем что внезапно менялись функции и приходилось устранять возникающие при этом ошибки. Это было в БСП.

    Так что теперь стараюсь всё выносить максимально в свои модули.

    Reply
  9. baton_pk

    (4) (5)

    Зачем эти ДВА условия?!

    Если Результат = Истина Тогда // Этого вполне достаточно
    Если Результат = ДокументОснование Тогда // Зачем тут ещё одно сравнение с неопределено?
    

    К тому же возмутился я тем, что условие отработает неверно в случае когда Результат=Ложь.

    Reply
  10. ivanov660

    (9) baton_pk,

    Согласен поспешил с ответом. Поправил комментарий, теперь условие отражает суть ответа )

    Reply
  11. baton_pk

    (10)

    Опять же, зачем

    Если НЕ Результат=Неопределено // не забыть перенос на другую строку :-)))
    И Результат Тогда
    …
    КонецЕсли;
    

    Если можно просто

    Если Результат = Истина Тогда // …
    
    Reply
  12. viramen
    6. Формат строки. (ошибки, код)

    Не забывайте про формат строки, т.к. 1С число преобразует в строку с пробелом между разрядами.

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

    Reply
  13. bonv

    5. В первую очередь надо избегать таких случаев. Либо метод возвращает булево, либо надо использовать объект (структуру) для возврата.

    21. >

    НЕ Контрагент=Значение(Справочник.Контрагенты.ПустаяСсылка)

    В 1С есть знак «не равно».

    Контрагент <> Значение(Справочник.Контрагенты.ПустаяСсылка)
    Reply
  14. baton_pk

    (13) bonv,

    для «не равно» надо раскладку переключать простым смертным (у кого нет снегопатов и шаблонов). так что «не .. = ..» имеет право на существование 🙂

    Reply
  15. bonv

    (14) baton_pk, сильный аргумент 🙂

    Но с другой стороны это же запрос, а в конструкторе мышкой «не равно» проще выбрать, чем вписать «НЕ .. = ..» 😉

    Reply
  16. BigClock

    Хотелось бы узнать, корректно ли использовать конструкцию вида

    Если НЕ Результат=Неопределено

    Ведь если Результат равен Неопределено, то выражение Не Результат при вычислении дает ошибку «{(1)}: Преобразование значения к типу Булево не может быть выполнено»

    Аналогичную ошибку дает код

    Стр = ТабЗнч.Найти(«Значение», «Колонка»);
    Если Не Стр = Неопределено Тогда
    ………..
    КонецЕсли
    Reply
  17. baton_pk

    (16) BigClock,

    А = Неопределено;
    Если Не А = Неопределено Тогда
    
    КонецЕсли;
    

    Отрабатывает без ошибок (8.2.19.83).

    В любом случае, лучше использовать скобки 🙂

    Reply
  18. baton_pk

    (15) bonv,

    а в конструкторе мышкой

    Открою маленький секрет: далеко не всегда люди пользуются конструктором запросов. 😉

    Reply
  19. jeyrico

    (14) Чтобы не переключаться хорошо помогает это специальная раскладка клавиатуры с сайта Павла Чистова http://1c.chistov.pro/2012/11/1.html

    Где-то, вроде даже, на сайте ИТС или Евгения Гилева, была статья о том, что с <> нужно пользоваться осторожно, и рекомендуют использовать именно конструкцию (Не … = …)

    Reply
  20. baton_pk

    (19) jeyrico,

    «<>» от лукавого. Не понимаю, почему 1С до сих пор не сделали «!=».

    Reply
  21. jeyrico

    (20) Может потому, что это будет знатный холивар насчет было стало.

    Reply
  22. bonv

    (18) baton_pk,

    Открою маленький секрет: далеко не всегда люди пользуются конструктором запросов. 😉

    Чорт, правда что ли 🙂

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

    Хотя мне тоже более привычен «!=».

    Reply
  23. friend0

    Начиналось все хорошо, но последние пункты весьма спорные. Использование «В» и «Объединить» в запросах лично меня напрягают. Так же как «ИЛИ». В свое время оптимизировал тормозной запрос, пробовал разные варианты и в итоге соединение оказалось самым быстродейственным вариантом замены «ИЛИ».

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

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

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

    Я бы тут порекоммендовал следующие правила (общий шаблон поведения с возможными отклонениями на конкретике):

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

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

    3. Не запихивать во временную таблицу слишком много данных.

    4. Для группировки данных не класть исходные данные во временную таблицу (если эти данные не понадобятся в другом запросе пакета) — вложенный запрос работает на ура.

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

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

    Reply
  24. Yashazz

    Особенно позабавил пункт 14. Это всё равно, что написать: «включайте мозги, когда проектируете и кодите» )))

    Reply
  25. jobkostya1c8

    Бесполезно про ошибки писать. Все равно только практика подскажет.

    Reply
  26. ivanov660

    (25) kostyaomsk, лучше учиться на чужих ошибках чем на своих.

    Reply
  27. ivanov660

    (24) Yashazz, как это не удивительно звучит, но по результатам работы с франчами, начинающими и даже некоторыми опытными спецами (возможно невнимательность или еще что-то) этот пункт достаточно актуален. Все приведенные тут примеры взяты из практики.

    Reply
  28. ivanov660

    (20) baton_pk, в также можно задать вопрос: почему еще нет классов, наследования, инкапсуляции и др.? Думаю, что этого не будет и в 8.4, хотя эклипс вроде обещали.

    Reply
  29. demrak

    (13) bonv, в какой-то момент знак <> начинает наоборот отвлекать, уже года как 3 использую в сравнениях только НЕ = и в запросах и в коде. Тренирует логическое мышление, как мне кажется.

    Reply
  30. pro1c@inbox.ru

    Стараюсь придерживаться правильному подходу, принятому, но:

    1. Видел правильный хорошо выверенный код, но не решающий задачу.

    2. Видел «спагетти» кода, но работает как часы. Разобраться сложно но можно.

    Так что все относительно.

    Тут недавно показывали код некоторых модулей исходников Windows, так там используют даже GOTO!!!!

    и ничего! спор шел по-поводу целесообразности выхода из цикла ‘Break’ или ‘GoTo Метка’?

    Так вот сошлись на том, что Break, что GoTo сути не меняет, но для некоторых понятнее куда переходим из цикла при прерывании, а не просто прерываем цикл (break) и следуем далее по ходу выполнения после тела цикла!

    Как-то так…

    Reply
  31. Painted
    15. Не изобретайте велосипед

    Где бы еще найти описание функций общих модулей. Бывает и нередко, что действительно изобретаю велосипед.

    Reply
  32. SirYozha
    21. Испольуйте предопределенные значения в запросах. (совет, запросы)

    С пунктом 21 не согласен.

    Так как использование функций (ЗНАЧЕНИЕ() и т.п.) в запросе ведет к дополнительным преобразованиям на стороне СУБД. Соответственно, может увеличиться время исполнения запроса.

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

    p.s. кстати, буква «з» пропущена в слове ИспольЗуйте

    Reply
  33. ivanov660

    (32) SirYozha,

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

    2. Быстродействие зависит от платформы к платформе и оптимизатора 1С

    Reply
  34. ivanov660

    (31) Painted, на ИТС есть документация библиотеки стандартных подсистем от фирмы 1С — описание использования набора универсальных функциональных подсистем, предназначенных для использования в прикладных решениях на платформе «1С:Предприятие» Стандартные библиотеки. Документация..

    Reply
  35. ivanov660

    (23) friend0,

    1. на самом деле подобные вещи про оптимизацию запросов собирались из разных источников: настольная книга 1с: эксперта по технологическим вопросам; в интернете — на текущем сайте, других формах; про особенности работы MS SQL оптимизацию на technet.microsoft.com; в некоторых случаях проводились практические замеры.

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

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

    3. Вопрос: про неиспользование виртуальных таблиц — при создании запроса по реальной таблице Вы сразу вспомнили и подумали про поле активность?

    Reply
  36. andrey3d

    (11) baton_pk,

    Можно еще проще

    Если Результат Тогда // …
    Reply
  37. ivanov660

    (36) andrey3d, если Вы про пункт 5, то нельзя (читайте внимательнее).

    Reply
  38. Denis S

    (14) baton_pk,

    Можно еще использовать команды:

    < — ALT + 60;

    > — ALT + 62.

    Сам только так и делаю.

    Еще пример:

    & — ALT + 38

    Reply
  39. baton_pk

    (38) Denis S,

    раз уж рука дотянулась до альта, то лично мне проще дважды раскладку переключить: alt+shift [] alt+shift

    цифрами я только | (alt+124) набираю и # (alt+35).

    к тому же альт+… плохо работает при работе через TeamViewer, особенно под Убунту. а если ещё ноутбук без нумпада — это вообще в ад превращается 🙂

    Reply
  40. Prisian

    По поводу пункта 22.1. На больших объемах данных + MS SQL очень быстро увеличиваются логи. Так как временная таблица создает физическую таблицу в базе, потом удаляет и все это пишется в логи. В случае использования вложенных таблиц будет использоватся только оперативная память. Тут уж лучше исходить из задачи.

    Reply

Leave a Comment

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