При разработке обработок в среде 1С:Предприятие программисты часто сталкиваются с необходимостью не просто сделать выборку данных из базы, но и произвести над ними определенные логические или строковые операции, сохранить промежуточный результат для последующего анализа (например, для поиска дубликатов) и наглядно вывести его пользователю. В этой статье мы подробно разберем, как правильно организовать такой процесс, исправим логические ошибки в алгоритмах обработки строк, научимся работать с коллекцией ТаблицаЗначений и рассмотрим современные способы вывода информации как для обычного, так и для управляемого приложения.
Для начала давайте проанализируем исходный код, предложенный в начале обсуждения. Автор пытается обойти выборку запроса и удалить ведущие нули из кода номенклатуры с помощью следующей конструкции:
Пока Найти(Номер, Лев(Номер, 1)) = 0 Цикл
Номер = Сред(Номер, 2);
КонецЦикла;
Этот алгоритм содержит критическую логическую ошибку и работать не будет. Давайте выясним причину. Функция Лев(Номер, 1) возвращает крайний левый символ строки (например, символ "0", если код равен "000123"). Далее функция Найти("000123", "0") ищет первое вхождение этого символа в строке. Естественно, она находит его на самой первой позиции и возвращает значение 1. Поскольку единица не равна нулю, условие цикла 1 = 0 сразу же возвращает значение Ложь, и тело цикла не выполняется ни разу. Код так и остается с ведущими нулями.
Для решения этой задачи мы можем применить несколько гораздо более простых, надежных и быстрых подходов.
Рассмотрим три наиболее эффективных варианта удаления лидирующих нулей в языке 1С.
Вариант А. Простой и понятный цикл проверки символов
Мы можем посимвольно проверять крайний левый символ строки в цикле и отсекать его, если он является нулем:
Номер = СокрЛП(ВыборкаДетальныеЗаписи.Код);
Пока Лев(Номер, 1) = "0" Цикл
Номер = Сред(Номер, 2);
КонецЦикла;
Этот способ работает безотказно, однако при обработке десятков тысяч позиций номенклатуры частый вызов строковых функций в цикле может замедлить работу системы.
Вариант Б. Быстрый метод через замену символов (без использования циклов)
Если мы уверены, что внутри кода номенклатуры нет пробелов (а в стандартных кодах 1С их, как правило, нет), можно использовать изящную комбинацию встроенных функций:
НомерБезНулей = СтрЗаменить(СокрЛ(СтрЗаменить(Код, "0", " ")), " ", "0");
Разберем по шагам, как работает этот алгоритм:
СтрЗаменить(Код, "0", " ") временно заменяет все нули в коде на пробелы. Код "000102" превращается в " 1 2".СокрЛ удаляет все пробелы только с левой стороны строки. Наша строка превращается в "1 2".СтрЗаменить возвращает сохраненные внутренние пробелы обратно в нули. Строка принимает вид "102". Задача успешно решена за одну строку кода без медленных циклов.Вариант В. Использование стандартных функций БСП
Если вы работаете в любой современной типовой конфигурации, в ней уже установлена Библиотека Стандартных Подсистем (БСП). В составе БСП есть оптимизированная общая функция, которую рекомендуется использовать в профессиональной разработке:
НомерБезНулей = ПрефиксацияОбъектовКлиентСервер.УдалитьЛидирующиеНулиИзНомераОбъекта(Номер);
Теперь решим вторую проблему автора: как сохранить результаты обработки на каждой итерации цикла обхода запроса, чтобы затем сравнить их и выявить дубликаты. Выводить результат сразу на экран в цикле крайне неэффективно. Нам понадобится универсальная коллекция данных — ТаблицаЗначений.
Давайте разберем по шагам, как программно создать таблицу значений, задать структуру колонок с определением типов и заполнить ее в цикле:
// Шаг 1. Инициализируем новую Таблицу Значений
ТЗ_Результат = Новый ТаблицаЗначений;
// Шаг 2. Описываем колонки таблицы и задаем им явные типы данных для повышения стабильности кода
ТЗ_Результат.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка"));
ТЗ_Результат.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
// Шаг 3. Выполняем запрос к базе данных
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
// Шаг 4. Начинаем обход результатов запроса в цикле
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Очищаем код от нулей с помощью быстрого бессимвольного метода
ИсходныйКод = СокрЛП(ВыборкаДетальныеЗаписи.Код);
КодБезНулей = СтрЗаменить(СокрЛ(СтрЗаменить(ИсходныйКод, "0", " ")), " ", "0");
// Добавляем новую строку в нашу Таблицу Значений
НоваяСтрока = ТЗ_Результат.Добавить();
НоваяСтрока.Код = КодБезНулей;
НоваяСтрока.Наименование = ВыборкаДетальныеЗаписи.Наименование;
КонецЦикла;
Поскольку цель автора — «сравнить одинаковые значения уже без нулей и вывести эти одинаковые значения», нам необходимо проанализировать полученную таблицу значений на наличие дубликатов по колонке Код. Рассмотрим два профессиональных метода решения этой задачи.
Метод А. Сворачивание таблицы значений (быстрый способ)
Мы можем добавить в нашу таблицу вспомогательную числовую колонку «Количество», заполнить её единицами, свернуть таблицу по кодам и удалить все строки, где количество равно единице. Оставшиеся строки будут являться дубликатами:
// Добавляем колонку для подсчета количества
ТЗ_Результат.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ТЗ_Результат.ЗаполнитьЗначения(1, "Количество");
// Сворачиваем таблицу по полям "Код" и "Наименование", суммируя "Количество"
ТЗ_Результат.Свернуть("Код, Наименование", "Количество");
// Удаляем уникальные строки с конца таблицы (чтобы не сбивались индексы при удалении)
Индекс = ТЗ_Результат.Количество() - 1;
Пока Индекс >= 0 Цикл
Если ТЗ_Результат[Индекс].Количество = 1 Тогда
ТЗ_Результат.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Метод Б. Запрос к Таблице Значений (гибкий и производительный способ)
Для поиска дубликатов на больших объемах данных правильнее использовать механизм временных таблиц в запросе. Мы можем передать заполненную ТаблицаЗначений в новый запрос в качестве параметра:
ЗапросДубли = Новый Запрос;
ЗапросДубли.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Передаем нашу таблицу значений в качестве параметра
ЗапросДубли.УстановитьПараметр("ВнешняяТЗ", ТЗ_Результат);
// Формируем текст запроса с группировкой по коду и условием ИМЕЮЩИЕ
ЗапросДубли.Текст =
"ВЫБРАТЬ
| ВнешняяТЗ.Код КАК Код,
| ВнешняяТЗ.Наименование КАК Наименование
|ПОМЕСТИТЬ ВТ_ДанныеНоменклатуры
|ИЗ
| &ВнешняяТЗ КАК ВнешняяТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ДанныеНоменклатуры.Код КАК Код,
| КОЛИЧЕСТВО(ВТ_ДанныеНоменклатуры.Наименование) КАК КоличествоДублей
|ИЗ
| ВТ_ДанныеНоменклатуры КАК ВТ_ДанныеНоменклатуры
|СГРУППИРОВАТЬ ПО
| ВТ_ДанныеНоменклатуры.Код
|ИМЕЮЩИЕ
| КОЛИЧЕСТВО(ВТ_ДанныеНоменклатуры.Наименование) > 1";
ТаблицаДубликатов = ЗапросДубли.Выполнить().Выгрузить();
Способ вывода полученных данных зависит от того, какой тип интерфейса используется в вашей конфигурации.
Вариант для Обычного приложения (Обычные формы):
Для быстрого анализа в режиме разработчика можно использовать встроенную функцию ОткрытьЗначение(ТаблицаДубликатов). Однако для конечного пользователя лучше использовать связку с объектом ПостроительОтчета и вывести данные в ТабличныйДокумент:
Построитель = Новый ПостроительОтчета;
// Передаем таблицу в качестве источника данных
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаДубликатов);
// Выводим результат в табличный документ на экран
ТД = Новый ТабличныйДокумент;
Построитель.Вывести(ТД);
ТД.Показать("Результаты поиска дубликатов кодов");
Вариант для Управляемого приложения (Управляемые формы):
В управляемом приложении ПостроительОтчета и интерактивный метод ОткрытьЗначение на клиенте недоступны. В этом случае мы должны передать полученную таблицу на форму. Предположим, что на форме у нас создан реквизит с именем "ТаблицаРезультат" (тип данных — Таблица значений), отображенный в виде элемента управления "Таблица":
&НаСервере
Процедура НайтиИВывестиДубликатыНаСервере()
// ... здесь выполняется код создания ТЗ_Результат и поиска дубликатов (как в Способе 3) ...
// Пусть результат поиска записан в переменную "ТаблицаДубликатов"
// Заполняем реквизит формы данными нашей таблицы
ЗначениеВРеквизитФормы(ТаблицаДубликатов, "ТаблицаРезультат");
КонецПроцедуры
После вызова этого серверного метода платформа автоматически перерисует интерфейс и отобразит красивую заполненную таблицу со всеми найденными дубликатами кодов прямо на форме вашей обработки.
Если в вашем справочнике "Номенклатура" содержатся сотни тысяч или миллионы элементов, выгрузка всей таблицы в оперативную память для построчной обработки в цикле может привести к перерасходу памяти сервера 1С. В таких случаях рекомендуется: