Неразрывный пробел - это вам не пробел

25.09.26

Интеграция - Перенос данных 1C

Продолжение истории про символ 160, который в прошлый раз сорвал перенос базы на PostgreSQL. Там неразрывный пробел приезжал в данные копипастом и мешал построить уникальный индекс. Здесь он приезжает сам: штатное преобразование числа в строку форматирует его для человека и вставляет разделитель разрядов. Такая строка идёт в ключ сопоставления, и совпадений больше нет - молча, без единой ошибки в журнале. Интереснее масштаба другое: почему дефект годами никого не беспокоил, хотя работал с первого дня. Ответ оказался неприятным - обе стороны сопоставления портились одинаково и потому сходились между собой. Отсюда следствие, о котором лучше знать до правки: починка одной стороны рвёт связи, которые сейчас работают. В конце - порядок действий и честный список того, чего в разборе не замерили.

В таблице сопоставления было 1 449 ключей. Битыми оказались 1 370 из них, 94,55 процента.

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

Самое неприятное в этой истории даже не масштаб. Дефект годами не проявлялся, хотя работал с первого дня. И последнее, о чём лучше подумать заранее: починка одной стороны сопоставления ломает то, что до этого работало. Про это в конце.

 

Сразу оговорюсь: это вторая часть

Про тот же символ я уже писал - "Как неразрывный пробел мешает миграции MS SQL → PostgreSQL на Windows Server". Читать её необязательно, здесь всё самодостаточно, но если попадалась, полезно понимать, чем эта история отличается.

Там неразрывный пробел приехал в базу снаружи: копипастом из внешних таблиц и с сайтов поставщиков. И ломал он построение уникального индекса при переносе на PostgreSQL. Библиотека сравнения строк ICU (International Components for Unicode), через которую PostgreSQL сравнивает значения, считает неразрывный пробел равным обычному: два разных значения схлопываются в одно, и уникальный индекс отказывается строиться. Там символ делал разные строки одинаковыми.

Здесь всё зеркально. Символ вставляет сама платформа, руками его никто не заносил, и эффект обратный: два визуально одинаковых значения перестают быть равными. Первая история была про лишнее равенство, эта - про пропавшее.

Тема тоже сместилась. Первая часть отвечала на вопрос "что это за символ и как его поймать". Эта - на два других: почему испорченные данные годами вели себя как здоровые и сколько стоит их вылечить. Символ здесь скорее повод; интересно то, что вокруг него выросло.

 

Что произошло с ключами

Ключ сопоставления собирался из числового кода. Ожидалось значение вида шести цифр подряд. Фактически в базе лежала строка из семи символов: шесть цифр и один разделитель между третьей и четвёртой.

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

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

И вот почему это худший из возможных сценариев. Упавшая обработка сама сообщает, что с ней что-то не так, и её чинят в тот же день. Обработка, которая молча возвращает пустоту, живёт до тех пор, пока кто-то не сверит результат руками. А сверять результат руками никто не будет: механизм ведь работает.

1 370 ключей из 1 449 были в таком состоянии. Уцелело 79, это 5,45 процента, и почему уцелели именно они - разберём отдельно, потому что это и есть доказательство механизма.

 

Откуда берётся лишний символ и как его убрать

Число превращается в строку не само по себе. Речь про самое обычное Строка(Значение) - то, что пишут не задумываясь. Эта функция форматирует число так, как принято показывать человеку: с разделителем групп разрядов.

То есть она делает ровно то, что должна. Она про представление числа человеку; цифры из неё доставать никто не обещал. Шестизначное число она честно превращает в семизначную строку.

Ошибка не в функции. Ошибка в том, что её результат пошёл в идентификатор, хотя рассчитан был на надпись на экране.

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

Почему разделитель именно этот

Разделителем групп разрядов работает неразрывный пробел, код 160. Обычный пробел - код 32.

Отсюда все дальнейшие неприятности с поиском.

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

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

Надёжный способ один: смотреть коды символов, а не символы. Развернуть строку посимвольно и посмотреть на числа. Ноль двусмысленности, ноль зависимости от шрифта и редактора.

Длина растёт нелинейно, и на этом ломается самая очевидная проверка

Тут есть тонкость, которая портит первую же самодельную проверку.

Разделитель ставится между группами по три цифры. У шестизначного числа группы две, разделитель один, строка вырастает с 6 символов до 7. У семизначного групп уже три, разделителей два, и длина становится 9. То есть прирост длины зависит от разрядности исходного числа.

Отсюда простое следствие. Любая проверка вида "длина ключа отличается от ожидаемой ровно на один символ" ловит один диапазон значений и пропускает соседний. На той же таблице она честно найдёт шестизначные и тихо пройдёт мимо семизначных - и вы получите отчёт "всё исправлено", в котором исправлено не всё.

Правильный порог для проверки по длине один: "длина отличается от ожидаемой", без указания, на сколько именно. А ещё лучше вообще не опираться на длину, об этом ниже.

Как это чинится

Лечится одной строкой: при преобразовании явно указать нулевое число групп разрядов. Вместо Строка(Значение) - Формат(Значение, "ЧГ=0"). Функция форматирования это умеет с самого начала, просто по умолчанию ведёт себя иначе, и умолчание тут работает против вас.

Для буквенно-цифровых значений исправление безопасно: то, что не число, возвращается как есть. Это важно, потому что в одном и том же поле часто лежит вперемешку и числовой код, и артикул с буквами, и разбирать их по типам перед сборкой ключа никто не будет.

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

Ищутся такие места по коду, признак у них общий: преобразование числа в строку внутри построения ключа. Одно правило поиска, результат сразу.

Чем чинить нельзя

Два способа, которые напрашиваются и оба вредны.

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

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

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

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

Тут же вылезает вторая ловушка, симметричная первой. Проверка "есть ли в значении пробел" даёт ложные срабатывания в обе стороны сразу: обычный пробел не находится там, где сидит символ 160, и находится там, где он законный - внутри буквенно-цифрового артикула. Такая проверка врёт и про битые значения, и про целые.

 

Почему дефект жил годами

Вот главный вопрос всей истории. Механизм работал с первого дня, а сломалось всё в какой-то момент.

Ответ: обе стороны сопоставления портились одинаково.

Ключ формировался тем же кодом и там, и там. Слева получалась строка с неразрывным пробелом, справа - точно такая же строка с неразрывным пробелом. Они совпадали. Сопоставление работало.

Дефект был, а последствий не было, потому что ошибка была симметричной. Причём система в этот момент вела себя абсолютно правильно: испорченный ключ равен испорченному ключу, равенство сохраняется. Ошибки нет ни на одном уровне - платформа сравнила строки честно, сравнение честно вернуло истину. Ловить тут было нечего, и никакой мониторинг не помог бы.

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

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

И этот день назначаете не вы. Его назначает подключение нового поставщика, переписанный кем-то модуль или обычный ручной ввод. Для того, кто в этот день дежурит, всё выглядит как "сломалось от новой интеграции", хотя интеграция ни при чём: она первой сделала ключ правильно.

Проверить систему на такое можно единственным способом: брать значение по отдельности и мерить его об ожидаемый формат. Сравнение половин между собой тут бесполезно по построению. Сверка "наши ключи против их ключей" проходит успешно и ничего не показывает - обе стороны испорчены одинаково.

 

Проверка объяснения: почему уцелели 79

Механизм даёт предсказание, и оно проверяемо.

Разделитель групп разрядов появляется только тогда, когда группы есть, то есть начиная с четырёхзначных чисел. У числа меньше тысячи разделять нечего, строка получается чистой.

Значит, уцелевшие 79 ключей обязаны быть либо короткими числами, либо буквенно-цифровыми значениями, которые преобразование не трогает.

Скажу честно: эту проверку я привожу как предсказание, а не как выполненный замер. Запрос, который разложил бы уцелевшие по длине, стоит одного захода, и в разборе его не сделали. Если среди 79 найдётся четырёхзначное числовое значение - механизм описан неполно, и об этом надо будет знать.

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

 

Риск, который создаёт само исправление

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

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

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

Из этого следует порядок действий, и он важнее самой правки:

Сначала посчитайте обе стороны. Сколько значений с посторонним символом слева, сколько справа. Если испорчены обе - правьте обе одновременно, одним заходом.

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

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

Разбор, на котором основана статья, до этой части не дошёл: что стало после исправления, сколько ключей поднялось и не сломалось ли что-то - не зафиксировано. Риск здесь выведен из механизма. Замера под ним нет, так его и читайте.

 

Как проверить это у себя за один заход

Порядок, который я бы взял, если бы лез в чужую базу с нуля.

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

Шаг второй, коды. Возьмите из подозрительной группы несколько значений и разверните их посимвольно через КодСимвола(). Символ 160 виден мгновенно, и разговор "может, это у нас шрифт такой" на этом заканчивается.

Шаг третий, код конфигурации. Глобальным поиском по Строка( найдите все места, где ключ собирается из числа. Именно все, не первое попавшееся. Пока не найдены все, править нельзя: получится наполовину чистая система, а это состояние хуже исходного, потому что часть связей рвётся, а объяснение неочевидно.

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

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

 

Границы применимости

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

Симметричная порча - условие невидимости. Если у вас ключи формируются в одном месте и сравниваются с внешним источником, дефект вылезет в первый же день. Годами он живёт только там, где обе стороны портит один и тот же код.

Не проверено, сколько ещё мест в коде формируют ключи тем же способом. Один найденный участок почти наверняка не единственный: приём "преобразовать число в строку" пишут по привычке, а привычка одна на всю команду.

Замера "после" нет. Ни числа поднявшихся ключей, ни числа сломавшихся связей.

И честно про метод поиска. Версий тут не перебирали: дефект нашёлся сразу, как только посмотрели на коды символов вместо самих символов. Отклонённых гипотез в этой истории не было, и придумывать их ради красивой структуры я не стану.

 

Открытый вопрос

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

Симметричную порчу ключей внутри базы обработка не поймает, это я сказал выше честно. А вот несимметричную, ту самую, которая валит построение уникального индекса на переносе, ищет Проверка базы перед миграцией на PostgreSQL: она проходит по реквизитам и показывает значения, которые ICU схлопнет в одно, вместе со списком индексов, встающих на этом.

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

Вступайте в нашу телеграмм-группу Инфостарт

неразрывный пробел символ 160 разделитель разрядов Формат СокрЛП ключ сопоставления загрузка номенклатуры артикул поставщика несовпадение строк преобразование числа в строку коды символов 1С обмен

Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».

  • 0% комиссии — оплата напрямую исполнителю;
  • Исполнители любого масштаба — от отдельных специалистов до команд под проект;
  • Прямой обмен контактами между заказчиком и исполнителем;
  • Безопасная сделка — при необходимости;
  • Рейтинги, кейсы и прозрачная система откликов.

См. также

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Разработчик 1С:Предприятие 8 1С:Комплексная автоматизация 1.х 1С:Управление производственным предприятием 1С:Бухгалтерия 3.0 Россия Бухгалтерский учет Платные (руб)

Перенос данных из 1С:Управление производственным предприятием 1.3 в 1С:Бухгалтерия предприятия 3.0 с помощью правил обмена | Можно выполнить переход с УПП на БП 3 или запускать выгрузку данных за выбранный период времени | Переносятся документы, начальные остатки и вся справочная информация | Есть фильтр по организации и множество других параметров выгрузки | Поддерживается несколько сценариев работы: как первичный полный перенос, так и перенос только новых документов | Перенос данных возможен в "1С: Бухгалтерия 3.0" версии ПРОФ, КОРП или базовую | Переход с "1С: УПП1.3" / "1С:КА 1.1" на "1С:БП3.0" с помощью правил конвертации будет максимально комфортным! | Можно бесплатно проверить перенос на вашем сервере!

50050 руб.

25.02.2015    191578    376    295    

432

Перенос данных 1C Разработчик 1С:Предприятие 8 1С:Управление производственным предприятием 1С:ERP Управление предприятием 2 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х Россия Платные (руб)

Перенос документов, начальных остатков и справочной информации из УПП 1.3 в ERP 2 | из УПП 1.3 в УТ 11 | из УПП в КА 2 | Правила конвертации (КД 2) | Более 360 предприятий выполнили переход с использованием этого продукта! | Сэкономьте время - используйте готовое решение для перехода! | Позволяет перенести из УПП 1.3 в ERP / УТ 11 / КА 2 всю возможную информацию | В переносе есть фильтр по организации и множество других опциональных параметров выгрузки | Есть несколько алгоритмов выгрузки остатков на выбор

58000 руб.

04.08.2015    193535    468    309    

466

SALE! 15%

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Разработчик 1С:Предприятие 8 1С:Розница 2 1С:Управление нашей фирмой 1.6 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 1С:Розница 3.0 Россия Платные (руб)

Правила в универсальном формате обмена для ERP 2.5, КА 2.5, УТ 11.5, БП 3.0, Розница, УНФ, для последних версий конфигураций. Ссылки на другие конфигурации в описании публикации. Правила совместимы со всеми другими версиями конфигураций новыми и старыми, поддерживающими обмен и синхронизацию в формате EnterpriseData. Не требуется синхронного обновления правил после обновления другой конфигурации, участвующей в обмене. Типовой обмен через планы обмена кнопкой Синхронизация вручную или автоматически по расписанию, или вручную обработкой.

27633 руб.

12.06.2017    163994    997    329    

488

Перенос данных 1C Взаиморасчеты Оптовая торговля Логистика, склад и ТМЦ Файловый обмен (TXT, XML, DBF), FTP Системный администратор Разработчик 1С:Предприятие 8 1С:Управление торговлей 10 1С:ERP Управление предприятием 2 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х Россия Управленческий учет Платные (руб)

Можно проверить до покупки, оставьте заявку! Воспользовались более 268 компаний! Перенос данных из УТ 10.3 в УТ 11 | из УТ 10.3 в КА 2 | из УТ 10.3 в ERP. Решение для перехода с УТ 10.3. Можно перенести начальные остатки, нормативно-справочную информацию и все возможные документы. При выгрузке можно установить отбор по периоду, организациям и складам.

50200 руб.

24.04.2015    210202    182    253    

300

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Разработчик 1С:Предприятие 8 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х Россия Платные (руб)

Перенос данных из ERP в БП 3 | из КА 2 в БП 3 | из УТ 11 в БП 3 | из ЕРП в БП 3 | Сэкономьте время - используйте готовое решение для перехода! | Перенос разработан в формате КД 2 (правила конвертации данных) | Переносятся все возможные виды документов, начальных остатков и нормативно-справочная информация| Можно опционально выгружать каждую пару "номенклатура+характеристика" как отдельную номенклатуру | Есть выгрузка настроек счетов учета и зарплатных данных из ERP / КА 2 | Можно проверить на вашем сервере перед покупкой

58000 руб.

15.04.2019    86819    233    182    

169

SALE! 10%

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Разработчик 1С:Предприятие 8 1С:Управление производственным предприятием 1С:Бухгалтерия 3.0 Россия Бухгалтерский учет Управленческий учет Платные (руб)

Переносите справочную информацию, остатки и документы из УПП 1.3 в Бухгалтерию 3.0 с помощью готовых правил. Переносится более 50 видов документов. Простой интерфейс и понятные настройки.

42000 37800 руб.

15.12.2021    36314    265    68    

202

Файловый обмен (TXT, XML, DBF), FTP Перенос данных 1C Системный администратор Разработчик Бухгалтер 1С:Предприятие 8 1С:Бухгалтерия 3.0 Россия Платные (руб)

Обработка не только формирует начальные остатки по всем счетам на нужную дату (экономя время на свёртке базы БП 3), но и полностью переносит справочные данные и документы за заданный период. Гибкая настройка включает фильтр по организациям и множество параметров выгрузки. Работайте в удобном формате: выполните однократный полный переход или настройте регулярную догрузку только новых документов из БП 3 в БП 3.0. Интеграция правил конвертации в план обмена гарантирует точную выгрузку исключительно зарегистрированных объектов.

70760 руб.

10.04.2026    1278    3    8    

2

Рабочее место Производство готовой продукции (работ, услуг) Перенос данных 1C Пользователь 1С:Предприятие 8 1С:Управление производственным предприятием 1С:Документооборот 1С:Комплексная автоматизация 2.х 1С:КА 1С:ДО Платные (руб)

Продукт "Интеграция с 1С:Документооборот" позволяет использовать функции программы "1С:Документооборот 8" напрямую из учетной системы (1С:УПП; 1С:КА, 1С:УТ 10.3, 1С:БГУ 1.0, 1С:ЗБУ 1.0, 1С:УПП для Казахстана и отраслевых решений, разработанных на их основе) на платформе "1С:Предприятие 8": выполнять и ставить задачи, просматривать документы, скан-копии и прочие файлы, штрих-кодировать документы отправлять письма, вести учет рабочего времени - не входя в "1С:Документооборот 8", работая в одной программе, что значительно сокращает время и делает работу более комфортной и эффективной. Продукт прошел сертификацию 1С-Совместимо

135530 руб.

11.06.2015    63652    39    20    

51
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. RocKeR_13 1485 25.09.26 09:33 Сейчас в теме
Прошу прощения, но удивительно, сколько из банальной невнимательности можно выжать строк для статьи) Или опять ИИ?
chuevsf; SerVer1C; +2 – Ответить
2. ixijixi 2167 25.09.26 10:40 Сейчас в теме
Горшочек, не вари
RocKeR_13; 0x00; +2 – Ответить
Для отправки сообщения требуется регистрация/авторизация