При внедрении поштучного учета маркированных товаров (КИЗ, кодов маркировки DataMatrix систем «Честный Знак», МДЛП и др.) в устаревших, но все еще активно используемых конфигурациях на платформе 1С:Предприятие 7.7, разработчики часто совершают архитектурную ошибку. Они создают новый регистр остатков и добавляют в него измерение с типом Строка (например, длиной 100 или более символов) для хранения кодов маркировки.
В процессе эксплуатации такого решения программисты неизбежно сталкиваются с проблемами поиска, производительности СУБД и даже физической порчи данных. В этой статье мы подробно разберем, почему строковые измерения в регистрах платформы 1С 7.7 на MS SQL являются «миной замедленного действия», проанализируем технические причины сбоев и внедрим правильное архитектурное решение.
Рассмотрим ситуацию. В регистре накопления создано строковое измерение для хранения КИЗ. При попытке оприходовать два схожих кода маркировки, которые отличаются только регистром одного символа (например, 0104660077310172210z00NnY и 0104660077310172210z00Nny), система начинает вести себя непредсказуемо. При попытке получить остатки или отфильтровать движения по второму коду, платформа всегда возвращает данные по первому или выдает нулевой итог.
Проанализируем код, который вызывает ошибку:
РегКИЗ = СоздатьОбъект("Регистр.КИЗ");
РегКИЗ.УстановитьФильтр("0104660077310172210z00Nny");
РегКИЗ.ВыбратьИтоги();
РегКИЗ.ПолучитьИтог(); // Результат всегда возвращает 0 или остаток по коду с "Y" на конце
Выясним причину такого поведения. По умолчанию при установке платформы 1С 7.7 на MS SQL Server база данных создается со стандартным сопоставлением (Collation) — обычно это Cyrillic_General_CI_AS. Параметр CI (Case Insensitive) означает, что СУБД при операциях сравнения строк и поиске по индексам игнорирует регистр символов. Для базы данных строки "NnY" и "Nny" абсолютно идентичны.
Когда платформа 1С 7.7 пытается выбрать итоги из таблицы RG* (таблица остатков регистров), SQL Server возвращает первую попавшуюся запись, удовлетворяющую условию без учета регистра. В результате логика закрытия регистров и списания остатков полностью ломается.
Некоторые администраторы баз данных и разработчики пытаются решить проблему «в лоб» — изменяют Collation конкретной колонки в таблице движений (RA*) и итогов (RG*) регистра через SQL Enterprise Manager на регистрозависимый (например, Cyrillic_General_CS_AS, где CS — Case Sensitive), после чего запускают тестирование и исправление (ТиИ) со сдвигом ТА и пересчетом итогов.
Делать это категорически запрещено по трем ключевым причинам:
JOIN), MS SQL Server выдаст фатальную ошибку: Cannot resolve collation conflict for equal to operation. Это происходит из-за того, что сопоставление вашей измененной колонки не совпадает с сопоставлением временных таблиц или других справочников базы данных.Даже если бы проблемы с регистрозависимостью не существовало, использование длинных строк в качестве измерений регистров — это грубая архитектурная ошибка. Давайте разберем физическую структуру хранения данных регистра в SQL.
Для каждой таблицы движений (RA*) и остатков (RG*) СУБД строит кластерные и некластерные индексы. В эти индексы в обязательном порядке входят все измерения регистра. Если одним из измерений является строка длиной 100 символов, мы получаем следующие критические проблемы:
RA*) начинает требовать перестроения огромных строковых индексов, что драматически снижает скорость проведения документов.Справочник в регистр пишется компактный внутренний идентификатор 1С (ID объекта, состоящий всего из 9 символов в 36-ричной системе счисления). Хранение 100-символьной строки вместо 9-символьного ID увеличивает физический объем таблиц регистров более чем в 10 раз!Коды маркировки формата GS1 DataMatrix содержат обязательные управляющие символы-разделители групп данных — символ GS (Group Separator), имеющий ASCII-код 29 (Симв(29)).
Штатная утилита тестирования и исправления 1С 7.7 («Проверка физической целостности») при сканировании строковых реквизитов считает любые управляющие ASCII-символы с кодами меньше 32 (за исключением перевода строки и табуляции) признаком повреждения структуры данных (мусором в таблице DBF/SQL). При первом же запуске ТиИ система очистит или обрежет ваши строки в местах нахождения символа Симв(29), сделав коды маркировки полностью нечитаемыми для контролирующих органов и кассового ПО.
Единственным надежным, стабильным и производительным решением является отказ от строковых измерений в регистрах и перенос КИЗ в подчиненный справочник.
Рассмотрим по шагам, как правильно реализовать эту архитектуру:
Создадим новый справочник в конфигураторе. Назовем его КИЗ (или КодыМаркировки).
ПолныйКод с типом Строка неограниченной длины (или фиксированной длины, например, 150-240 символов, так как некоторые КИЗ с криптохвостом могут превышать 100 символов).В регистре остатков ОстаткиКИЗ изменим тип измерения КИЗ со Строка 100 на Справочник.КИЗ. Теперь в физических таблицах SQL вместо длинных, чувствительных к регистру строк будет храниться компактный и надежный 9-символьный идентификатор ссылки.
При получении КИЗ (из файла, ТСД или ЭДО) мы сначала ищем элемент в справочнике КИЗ. Если элемент не найден — создаем его. При поиске мы должны учитывать регистрозависимость на уровне кода 1С, чтобы избежать коллизий на SQL. Посмотрим на пример функции получения ссылки на КИЗ:
Функция ПолучитьСсылкаНаКИЗ(Знач ИсходныйКод) Экспорт
Перем СпрКИЗ, Ссылка;
// Очищаем код от управляющего символа GS перед сохранением,
// либо заменяем его на безопасный печатный символ (например, "|")
БезопасныйКод = СтрЗаменить(ИсходныйКод, Симв(29), "|");
СпрКИЗ = СоздатьОбъект("Справочник.КИЗ");
// Выполняем поиск по реквизиту.
// Поскольку SQL найдет код без учета регистра, мы должны
// программно перепроверить соответствие регистра символов в 1С.
СпрКИЗ.ВыбратьЭлементыПоРеквизиту("ПолныйКод", БезопасныйКод, 1, 0);
Пока СпрКИЗ.ПолучитьЭлемент() = 1 Цикл
// Сравниваем строки с учетом регистра на стороне клиента 1С
Если СпрКИЗ.ПолныйКод = БезопасныйКод Тогда
Возврат СпрКИЗ.ТекущийЭлемент();
КонецЕсли;
КонецЦикла;
// Если точное совпадение не найдено, создаем новый элемент
СпрКИЗ.Новый();
СпрКИЗ.ПолныйКод = БезопасныйКод;
СпрКИЗ.Записать();
Возврат СпрКИЗ.ТекущийЭлемент();
КонецФункции
Теперь при проведении документов прихода и расхода мы передаем в движение регистра не саму строку КИЗ, а полученную ссылку на элемент справочника. Проанализируем пример проведения прихода:
// ... код модуля проведения документа ...
Рег = Регистр.ОстаткиКИЗ;
Рег.НоваяДвижение();
Рег.КИЗ = ПолучитьСсылкаНаКИЗ(ТекущийКодМаркировки);
Рег.Количество = 1;
Рег.ДвижениеВыполнить();
// ...
Для извлечения реального текстового кода маркировки (например, для выгрузки в ОФД или Честный Знак) мы просто обращаемся к реквизиту справочника, заменяя безопасный разделитель обратно на управляющий символ:
СтрокаДляПечати = СтрЗаменить(ВыбранныйКИЗ.ПолныйКод, "|", Симв(29));
Если по каким-то причинам вы категорически не можете отказаться от строковых измерений (что крайне маловероятно при грамотном подходе к проектированию базы данных), существует альтернативный способ обойти ограничение регистронезависимости SQL — кодирование КИЗ в формат Base64 или Hex (шестнадцатеричный вид) перед записью в регистр.
Разберем этот механизм:
Однако этот метод решает только проблему чувствительности к регистру, оставляя нерешенными проблемы гигантского размера индексов, медленной работы СУБД и раздувания физического размера файлов вашей базы данных. Поэтому хранение КИЗ через специализированный справочник остается единственным профессиональным стандартом разработки.
Мы проанализировали архитектурные особенности хранения маркированных товаров в 1С 7.7. Перевод КИЗ в справочник позволяет:
Симв(29).