В процессе разработки складских систем, рабочих мест кассиров (РМК) или обработок заполнения документов перед программистом 1С часто возникает задача: найти номенклатуру и ее характеристики по известным штрихкодам. Источником данных при этом обычно выступает табличная часть на форме, куда штрихкоды были считаны сканером или загружены из терминала сбора данных (ТСД).
В этой статье мы подробно разберем, как правильно реализовать этот алгоритм без потери производительности базы данных. Мы рассмотрим два основных сценария: простой поиск по одномерному списку штрихкодов и сложный поиск с передачей полноценной таблицы значений в запрос.
Прежде чем перейти к правильным алгоритмам, разберем типичную ошибку начинающих разработчиков. Иногда на практике встречается код, в котором разработчик обходит строки таблицы на форме в цикле и на каждой итерации выполняет отдельный запрос к базе данных. Рассмотрим, почему так делать категорически нельзя.
Каждое обращение к базе данных из цикла (так называемая проблема «N+1 запрос») порождает сетевой сеанс между сервером приложений 1С и СУБД (MS SQL, PostgreSQL и др.). Если в вашей таблице будет 1000 строк, система выполнит 1000 отдельных запросов. Это приведет к колоссальным задержкам, блокировкам таблиц и зависанию интерфейса у пользователя. Запомните золотое правило разработки в среде 1С: запрос должен быть один, а все необходимые данные нужно передавать в него пакетом.
Если перед нами стоит задача просто найти в базе данных все характеристики и номенклатуру, для которых в регистре сведений ШтрихкодыНоменклатуры зарегистрированы штрихкоды из нашего списка, оптимально использовать объект СписокЗначений.
Проанализируем этот метод по шагам:
В.Рассмотрим пример реализации этого алгоритма на встроенном языке:
// Шаг 1. Получаем уникальные штрихкоды из таблицы на форме
МассивШтрихкодов = ТаблицаНаФорме.ВыгрузитьКолонку("Штрихкод");
// Исключаем дубликаты штрихкодов для оптимизации запроса
ФункцияСвернутьМассив(МассивШтрихкодов);
// Шаг 2. Создаем и настраиваем запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РегШтрихкоды.Штрихкод КАК Штрихкод,
| РегШтрихкоды.Номенклатура КАК Номенклатура,
| РегШтрихкоды.Характеристика КАК Характеристика
|ИЗ
| РегистрСведений.ШтрихкодыНоменклатуры КАК РегШтрихкоды
|ГДЕ
| РегШтрихкоды.Штрихкод В(&СписокШтрихкодов)
|УПОРЯДОЧИТЬ ПО
| Номенклатура,
| Характеристика";
Запрос.УстановитьПараметр("СписокШтрихкодов", МассивШтрихкодов);
// Шаг 3. Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Здесь выполняем обработку полученных данных
КонецЦикла;
Этот способ прост в реализации и отлично подходит, когда нам нужно просто получить список сопоставленных объектов. Однако у него есть существенный недостаток: если в исходной таблице на форме у нас были какие-то дополнительные связанные данные (например, уникальный идентификатор строки ID, количество или цена), мы потеряем связь между исходной строкой и найденным товаром.
Выясним, как решить более сложную и жизненную задачу. Предположим, на форме есть таблица, содержащая две колонки: Штрихкод и ID (уникальный идентификатор строки или связанного объекта). Нам нужно сопоставить штрихкоды с регистром и вернуть выборку, в которой для каждого ID будет указан найденный товар и его характеристика.
Для реализации этого сценария нам потребуется передать всю таблицу значений в запрос как параметр и связать ее с физической таблицей регистра сведений с помощью механизма временных таблиц.
Важное архитектурное требование: Платформа 1С:Предприятие не позволяет передавать в запрос таблицы значений с произвольными типами данных. Если вы попытаетесь передать пустую или нетипизированную колонку, система вызовет ошибку: «Тип не может быть выбран в запросе». Поэтому мы должны применить строгое типизирование колонок исходной таблицы значений.
Разберем готовый шаблон кода, который решает эту задачу:
// Шаг 1. Создаем структуру таблицы значений с явным указанием типов данных
ТЗ_Исходная = Новый ТаблицаЗначений;
// Настраиваем тип для колонки "Штрихкод" (Строка, длина 200)
МассивСтрока = Новый Массив;
МассивСтрока.Добавить(Тип("Строка"));
КвалификаторСтроки = Новый КвалификаторыСтроки(200);
ТипШтрихкод = Новый ОписаниеТипов(МассивСтрока, , КвалификаторСтроки);
ТЗ_Исходная.Колонки.Добавить("Штрихкод", ТипШтрихкод);
// Настраиваем тип для колонки "ID" (Число, 10 знаков, без дробной части)
МассивЧисло = Новый Массив;
МассивЧисло.Добавить(Тип("Число"));
КвалификаторЧисла = Новый КвалификаторыЧисел(10, 0);
ТипID = Новый ОписаниеТипов(МассивЧисло, , КвалификаторЧисла);
ТЗ_Исходная.Колонки.Добавить("ID", ТипID);
// Шаг 2. Заполняем типизированную таблицу данными из нашей формы
Для Каждого СтрокаФормы Из ТаблицаНаФорме Цикл
НоваяСтрока = ТЗ_Исходная.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаФормы);
КонецЦикла;
// Делаем проверку: если таблица пуста, выполнять запрос нет смысла
Если ТЗ_Исходная.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Шаг 3. Инициализируем запрос и описываем пакетную логику
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИсходныеДанные.Штрихкод КАК Штрихкод,
| ИсходныеДанные.ID КАК ID
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
| &ТаблицаДанных КАК ИсходныеДанные
|
|ИНДЕКСИРОВАТЬ ПО
| Штрихкод
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.ID КАК ID,
| ВТ.Штрихкод КАК Штрихкод,
| ЕСТЬNULL(РегШтрихкоды.Номенклатура, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Номенклатура,
| ЕСТЬNULL(РегШтрихкоды.Характеристика, ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)) КАК Характеристика
|ИЗ
| ВТ_ИсходныеДанные КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ШтрихкодыНоменклатуры КАК РегШтрихкоды
| ПО ВТ.Штрихкод = РегШтрихкоды.Штрихкод";
// Передаем типизированную таблицу в качестве параметра
Запрос.УстановитьПараметр("ТаблицаДанных", ТЗ_Исходная);
// Шаг 4. Выполняем и обрабатываем результат
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Теперь у нас есть и исходный ID, и найденная номенклатура с характеристикой!
// Если товар по штрихкоду не найден, поля вернут пустую ссылку.
КонецЦикла;
Рассмотрим несколько критически важных архитектурных нюансов, о которых часто забывают разработчики при реализации подобных механизмов связи.
1. Индексирование временных таблиц. Обратите внимание на инструкцию ИНДЕКСИРОВАТЬ ПО Штрихкод в первом запросе пакета. Создание индекса по полю связи во временной таблице критически важно для СУБД при левом соединении с физической таблицей регистра. Без индекса при объемах исходной таблицы более 100 строк производительность СУБД может ощутимо снизиться.
2. Выбор правильного типа соединения (Левое против Внутреннего). В нашем примере используется ключевое слово ЛЕВОЕ СОЕДИНЕНИЕ. Это гарантирует, что в итоговый результат запроса попадут абсолютно все строки из исходной таблицы, даже если для некоторых штрихкодов не будет найдено соответствие в базе данных. В этом случае поля Номенклатура и Характеристика примут значение NULL, которое мы красиво преобразуем в пустую ссылку с помощью функции ЕСТЬNULL. Если вам нужно отсечь ненайденные штрихкоды еще на уровне запроса, используйте ВНУТРЕННЕЕ СОЕДИНЕНИЕ.
3. Проблема дублирования записей (множение строк). Помните, что штрихкод в системе 1С не всегда является абсолютно уникальным полем. В некоторых конфигурациях (например, при некорректно настроенных правилах ведения базы данных) один и тот же штрихкод может быть назначен двум разным карточкам товаров или разным характеристикам одного товара. В случае левого соединения это приведет к тому, что одна строка из вашей таблицы на форме превратится в две строки в результате запроса. Если такое поведение нежелательно, перед соединением таблиц сгруппируйте данные по штрихкоду или используйте ключевое слово РАЗЛИЧНЫЕ.