Что делать, если строк в документе больше 99’999?




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

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

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

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

27 Comments

  1. Merc

    Так и скажи, что не любишь динамические списки.

    Reply ↓
  2. vandalsvq

    (1) скажи на милость, чем мне динамический список поможет? Отображать данные? Ну ок. Только в этом и все, хотя для отображения тоже не выйдет, в расшифровке выводится дерево: смета — позиция/ресурс

    Reply ↓
  3. A_Max

    Рассматривали вот такой вариант?

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

    PS: Увидел, что я не первый уже после отправки комметария :о)

    Reply ↓
  4. vandalsvq

    (3) тогда бы понадобилось сразу после сбора данных записывать их в базу. А этого делать не хотелось. Пользователь не факт, что документ запишет, да и время на запись тоже необходимо. А после отказа от записи документа удалять данные. В общем это вызывает доп. расходы, а не хотелось этого.

    Поэтому при сборе получаем таблицу значений, закидываем во временное хранилище, работаем с ней. А если открыт существующий документ, то считываем либо все сразу, либо по частям и тоже в ТЗ и во временное хранилище.

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

    Reply ↓
  5. bulpi

    Если у вас строк в ТЧ более 999999 , это значит, что вы неправильно спроектировали базу. Это аксиома.

    Reply ↓
  6. Merc

    (2) Динамический список умеет в дерево

    99к + строк в данных формы это абсурд, как ссылка на вх или что-то ещё:

    1. Отобразить их невозможно, видимая часть списка это не так много строк

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

    3. Изменений больше 99к? Если да — взгляни на закрытие месяца, там тоже много изменений и ни кто не складывает их в ТЧ.

    ПС: Твоя проблема и её решение это одна большая ошибка.

    Reply ↓
  7. vandalsvq

    (5) аксиома говоришь? А теорема то кем была выдвинута? И критикуя — предлагай. Я готов обсуждать если будет что 🤗. В самом начале, постарался объяснить почему так получилось. Думаю достаточно, чтобы сделать иное предложение.

    Reply ↓
  8. vandalsvq

    (6) 1. Отображать всю таблицу никто не собирался. Отображается только часть, имеющая отношение к текущей строке материала, тем более в момент «запроса» пользователя.

    2. Изменять их напрямую пользователь не будет, он управляет материалами, а программа связанными с ними строками «истории» сбора данных.

    3. Вот поэтому в тч хранить и не стали. Это ни к чему.

    Опять повторюсь, я кажется дал с самого начала достаточно информации для создания альтернативной структуры хранения.

    Reply ↓
  9. artgen

    Разбить на несколько документов.

    Reply ↓
  10. acanta

    Какова зависимость времени записи документа от количества строк в 8ке?

    Reply ↓
  11. Vladimir Litvinenko

    Идея хорошая. Возможно будут полезны замечания.

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

    2) Перед заполнением набора записей в методе ЗаписатьДанныеВременногоХранилища блокировка данных не нужна. Вы же не выполняете предварительного чтения чтобы новые данные писались на основе старых. И вы же не ставите такую блокировку в методе ОчиститьРегистрСведенийХранениеДанныхДокумента, где точно такой же код за исключением того, что цикл заполнения набора записей заменен на метод чтения из БД.

    3) Зачем в методе ОчиститьРегистрСведенийХранениеДанныхДокумента вообще чтение из БД?

    НаборЗаписей.Прочитать();

    НаборЗаписей.Очистить();

    НаборЗаписей.Записать(Истина);

    Вроде бы как в регистре большое количество данных. Зачем их читать на сервер 1С чтобы потом сразу очистить эти данные и снова записать? Здесь ничего кроме установки неявной разделяемой управляемой блокировки не произойдет. И думаю что метод чтения из БД кучи данных вызывается вовсе не с целью установить неявную блокировку. Таким образом этот вызов является ошибкой.

    4) Регистр перезаписывается целиком для документа. Если нам нужно добавить данные к уже существующим или удалить только часть данных, то алгоритм придется менять, и в методе ЗаписатьДанныеВременногоХранилища придется все-таки добавлять предварительное чтение всего объема данных по документу из регистра. Чтобы затем поменять только часть записей. Это очень не оптимально, учитывая исходные условия задачи : большой объем данных по документу.

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

    Reply ↓
  12. Merc

    (8)

    Предложение 1:

    1. Пользователь, что-то делает с материалами

    2. Это что-то сохраняется в документ

    3. При записи/проведении рассчитываются изменения и куда-то складываются

    Предложение 2:

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

    1. Создается и записывается документ

    2. Все действия пользователя сразу пишутся в РС

    3. Пока документ не проведен, активность записей — Ложь

    Reply ↓
  13. A_Max

    (4) Именно поэтому и спросил рассматривали такой вариант или нет. Да, именно в вашем случае, когда может быть загружен большой объём и после этого велика вероятность отказа от записи, более логично времено хранить на клиенте. Но тогда мы увеличиваем требования на клиентскую часть (объём памяти и частично процессор на обработку) вместо того чтобы этим занимался сервер (вообще файл передать на сервер для загрузки). В случае с динамическим списком как минимум отпадает необходимость отработки частичного чтения. Возможность «отката» тоже реализовать на вскидку в голове пара способов крутится.

    У каждого решения есть своя сфера применимости с плюсами и минусами.

    ПС: При очистке регистра не нужно его читать. Можно сразу записывать набор после установки отбора.

    ПС2: Не надо так резко воспринимать комметарии, этим только вызываете общий негатив.

    Reply ↓
  14. vandalsvq

    (11) спасибо за замечания все по делу. Действительно полезно.

    1. Да, вы правы. Спасибо за подсказку, изменения будут внесены.

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

    3. Чтение тоже ни к чему, согласен также.

    4. Вот тут маленькая поправка. Выложил не всю информацию, сознательно ее упростив. Ваше замечание верно, и оно реализовано.

    Регистр имеет структуру: Документ / Ключ строки / Смета / Ключ позиции сметы

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

    Reply ↓
  15. vandalsvq

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

    В общем получается такая схема:

    — если нажата кнопка «Заполнить», то считанные данные помещаются во временное хранилище. Клиент получает ответ что все собрано и таблицу только уникальных позиций материалов, а вся расшифровка остается на сервере. На клиенте никоим образом отражения она не находит.

    — после записи документа,при повторном открытии, мы не сразу заполняем все данные расшифровки. Возможно пользователю она вообще не понадобится в процессе работы. Когда пользователь запрашивает необходимость расшифровки, именно эта часть считывается из регистра и дополняется в таблицу хранилища. Таким образом, мы его наполняем постепенно.

    Во всех случаях на клиенте остается только адрес хранилища и видимая часть уникального набора материалов.

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

    Пс2. видимо спал вчера мало, прошу прощения у всех

    Reply ↓
  16. Infector

    В общем-то пользуюсь подобным механизмом чтобы не добавлять нестандартные табличные части в стандартные объекты УПП. При создании на сервере формы существующего объекта читаем набор записей в реквизит формы, после записи формы — пишем содержимое этого реквизита в набор записей. Не забываем принудительно распихать ссылку объекта в соответствующий реквизит.

    Reply ↓
  17. vandalsvq

    (12) вот это другое дело ;), давай обсудим.

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

    Предложение 2 в принципе предложение рабочее. Есть один нюанс, пока не нажата кнопка «Записать» обычно пользователю считают что все изменения — это «вилами на воде писано». При обрыве соединения сохранятся изменения регистра, но данные объекта документа не изменяться. Или зависнут «неактивные» позиции и их придется как то потом подчищать.

    Reply ↓
  18. vandalsvq

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

    Reply ↓
  19. Vladimir Litvinenko

    (14)

    Если реализован регистр с измерениями Документ / Ключ строки / Смета / Ключ позиции сметы и данные действительно перезаписываются не целиком по измерению Документ, а по сочетанию измерений, то при запись данных в регистр сведений в методе ЗаписатьДанныеВременногоХранилища скорее всего идет внутри цикла, перебирающего комбинации измерений.

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

    Reply ↓
  20. Merc

    (17)

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

    Если бы знал бизнес процесс, я бы выбрал №1, но что бы это обсуждать нужен контекст, у меня его нет, посему про предложение 2.

    Почему и про нюансы:

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

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

    3. Активность как свойство записи входит в индекс и по умолчанию не попадает в срезы, очистка данных может быть не оперативной.

    Самое главное:

    1. Мы не сталкиваемся с ограничениями при масштабировании в большую сторону.

    Reply ↓
  21. Infector

    (18) в общем-то на уровне интерфейса решений может быть уйма. Динамический список с отборами тоже неплохое решение. Есть еще вариант с записью регистра с подчинением документу, при этом на форму можно просто кинуть таблицу из движений. Главное тут то, что решения с регистрами сведений неплохо работают.

    Reply ↓
  22. vandalsvq

    (20) я возможно не во все въехал сейчас, попробую проанализировать позже. Есть хорошая идея о том что «творчество можно продолжить». Это интересно может показаться, исходя из того с каким отделом мы имеем дело сейчас.

    Reply ↓
  23. vandalsvq

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

    Все таки я частенько блокировки накидываю по привычке. Долго работал над собой чтобы писать блокировки, теперь надо работать над собой чтобы не писать блокировки )))))))

    Reply ↓
  24. vandalsvq

    (21) мне лично показалось интересным заменить реквизит формы с большой таблицей, хранением этой самой таблице на сервере. Я еще отдельно проведу тесты на потери времени на получение и помещение, особенно когда речь пойдет о редактировании ее. Там получится ведь что сначала ты ее извлекаешь, меняешь, потом полностью обратно суешь. Не зная конвертирует ли 1С ТЗ в какой то внутренний формат или нет, сложно сказать накладные потери сходу.

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

    Reply ↓
  25. Merc

    (22)

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

    Документ и признак активности могут быть самостоятельными измерением, основная идея в следующем:

    1. На форме пользователь видит данные полученные объектами с поддержкой чтения по курсору (РегистрСведенийСписок, ДинамическийСписок)

    2. Изменения сразу фиксируются в базу данных

    3. Окончательное принятие к учету происходит с использованием доп. признака активности.

    Reply ↓
  26. mszsuz

    Ещё вариант — сделать ДВА регистра — один для хранения данных документа, второй — временный — вывести на форму для редактирования.

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

    Reply ↓
  27. A_Max

    (26) Излишняя сущность, т.к. можно использовать «Активность» записи у одного регистра или ещё какой-то статус добавить. Плюс от отдельного регистра может оказаться только в отсутсвии индексов для ускорения вставки.

    Reply ↓

Leave a Comment

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