В практике разработчика 1С периодически возникают ситуации, когда данные хранятся в "неправильном" виде. Классический пример — перечисление идентификаторов или номеров в одной строке через запятую. Если нам нужно построить отчет или выполнить расчет по каждому такому номеру в отдельности, возникает сложная задача: как разбить эту строку на части прямо внутри запроса, не прибегая к медленному перебору в цикле на языке 1С. Сегодня мы рассмотрим подробнее, как решить эту задачу исключительно средствами языка запросов.
Основная сложность заключается в том, что язык запросов 1С не имеет встроенной функции типа РазложитьСтрокуВМассив(). Однако, используя механизмы соединений и математические алгоритмы, мы можем обойти это ограничение. Разберем два эффективных метода решения этой задачи.
Этот метод считается наиболее универсальным. Его суть заключается в создании временной таблицы, содержащей последовательность чисел (от 0 до максимально возможной длины строки или количества элементов). Мы проанализируем ситуацию, где нам нужно найти позиции разделителей (запятых) и извлечь текст между ними.
Рассмотрим пошагово реализацию этого алгоритма:
Давайте посмотрим на пример кода, реализующего эту логику:
// Создаем базовую таблицу цифр
ВЫБРАТЬ 0 КАК Номер ПОМЕСТИТЬ Цифры ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9;
// Генерируем последовательность до 1000
ВЫБРАТЬ
Н1.Номер * 100 + Н2.Номер * 10 + Н3.Номер КАК Позиция
ПОМЕСТИТЬ ТаблицаПозиций
ИЗ
Цифры КАК Н1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Цифры КАК Н2 ПО ИСТИНА
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Цифры КАК Н3 ПО ИСТИНА;
// Основной расчет
ВЫБРАТЬ
ИсходныеДанные.Сумма / ИсходныеДанные.Количество КАК СуммаНаЕдиницу,
ПОДСТРОКА(ИсходныеДанные.СтрокаНомеров, ТПоз.Позиция,
ВЫБОР КОГДА НАЙТИ(ПОДСТРОКА(ИсходныеДанные.СтрокаНомеров, ТПоз.Позиция, 100), ",") = 0
ТОГДА 100
ИНАЧЕ НАЙТИ(ПОДСТРОКА(ИсходныеДанные.СтрокаНомеров, ТПоз.Позиция, 100), ",") - 1
КОНЕЦ) КАК Номер
ИЗ
ВТ_ИсходныеДанные КАК ИсходныеДанные
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТаблицаПозиций КАК ТПоз
ПО (ПОДСТРОКА("," + ИсходныеДанные.СтрокаНомеров, ТПоз.Позиция, 1) = ",")
И (ТПоз.Позиция <= ДЛИНА(ИсходныеДанные.СтрокаНомеров))
В данном примере мы используем ПОДСТРОКА для выделения каждого элемента. Важный нюанс: мы добавляем запятую в начало строки "," + СтрокаНомеров, чтобы корректно обработать первый элемент списка.
Если нам заранее известен диапазон возможных значений (например, номера от 1 до 100), мы можем применить более простой подход. Мы разберем по шагам, как подготовить строку так, чтобы запрос мог найти точное вхождение номера.
Проблема поиска числа в строке типа "1,10,100" заключается в том, что условие ПОДОБНО "%1%" найдет единицу во всех трех числах. Чтобы этого избежать, мы модифицируем и искомую строку, и эталонные номера, окружая их пробелами или запятыми.
Рассмотрим подробнее реализацию этого метода:
// Подготовка исходных данных: заменяем запятые на пробелы и добавляем пробелы по краям
ВЫБРАТЬ
" " + СТРЗАМЕНИТЬ(ИсходнаяТаблица.Номера, ",", " ") + " " КАК МодифицированнаяСтрока,
ИсходнаяТаблица.Сумма КАК ОбщаяСумма
ПОМЕСТИТЬ ВТ_Подготовка
ИЗ
ИсходнаяТаблица КАК ИсходнаяТаблица;
// Соединение с эталонным списком номеров
ВЫБРАТЬ
ВТ_Подготовка.ОбщаяСумма КАК Сумма,
ВТ_Номера.Номер КАК Номер
ИЗ
ВТ_Подготовка КАК ВТ_Подготовка
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номера КАК ВТ_Номера
ПО ВТ_Подготовка.МодифицированнаяСтрока ПОДОБНО "% " + ВТ_Номера.Код + " %"
Этот способ прост в реализации и отлично работает, когда у вас есть справочник или временная таблица со всеми возможными значениями кодов. Оператор ПОДОБНО с пробелами гарантирует, что мы найдем именно " 7 ", а не " 17 " или " 70 ".
При разделении строк автор вопроса столкнулся с необходимостью пропорционального деления суммы. Выясним причину, по которой обычное деление может привести к ошибкам в итоговом балансе. Если мы разделим 100 рублей на 3 части, мы получим 33.33, что в сумме даст 99.99. Одна копейка будет потеряна.
Чтобы решить эту проблему прямо в запросе, проанализируем ситуацию и применим алгоритм "остатка для последней строки":
АВТОНОМЕРЗАПИСИ или соединения).ОКР(Сумма / Количество, 2).ОбщаяСумма - Сумма(СуммПредыдущихСтрок).В запросе 1С это реализуется через левое соединение таблицы самой с собой для подсчета накопленного итога, но мы рекомендуем для таких тонких расчетов всё же использовать постобработку в коде 1С, так как запрос может стать чрезмерно громоздким и медленным.
Нужно понимать, что использование операторов ПОДСТРОКА, НАЙТИ и ПОДОБНО в условиях соединения (JOIN) крайне негативно сказывается на производительности. СУБД не может использовать индексы для таких операций, что приводит к полному сканированию таблиц (Full Scan).
Рассмотрим рекомендации по оптимизации:
СтрРазделить() и выгрузка результата во временную таблицу будет работать в десятки раз быстрее на больших объемах.Таким образом, мы выяснили, что разделить строку в запросе 1С реально. Метод с таблицей чисел (Tally Table) является мощным инструментом в арсенале программиста, позволяющим обходить ограничения платформы и решать сложные задачи трансформации данных без выхода из контекста запроса.