Как в запросе 1С разделить строку на несколько строк и распределить сумму по номерам?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Основная сложность заключается в том, что язык запросов 1С не имеет встроенной функции типа РазложитьСтрокуВМассив(). Однако, используя механизмы соединений и математические алгоритмы, мы можем обойти это ограничение. Разберем два эффективных метода решения этой задачи.

Метод 1: Использование вспомогательной таблицы чисел (Tally Table)

Этот метод считается наиболее универсальным. Его суть заключается в создании временной таблицы, содержащей последовательность чисел (от 0 до максимально возможной длины строки или количества элементов). Мы проанализируем ситуацию, где нам нужно найти позиции разделителей (запятых) и извлечь текст между ними.

Рассмотрим пошагово реализацию этого алгоритма:

  1. Генерируем таблицу цифр от 0 до 9.
  2. Путем самосоединения этой таблицы получаем таблицу чисел (например, от 0 до 999).
  3. Соединяем нашу исходную таблицу с таблицей чисел по условию нахождения разделителя.

Давайте посмотрим на пример кода, реализующего эту логику:


// Создаем базовую таблицу цифр
ВЫБРАТЬ 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) = ",")
    И (ТПоз.Позиция <= ДЛИНА(ИсходныеДанные.СтрокаНомеров))

В данном примере мы используем ПОДСТРОКА для выделения каждого элемента. Важный нюанс: мы добавляем запятую в начало строки "," + СтрокаНомеров, чтобы корректно обработать первый элемент списка.

Метод 2: Соединение по шаблону ПОДОБНО

Если нам заранее известен диапазон возможных значений (например, номера от 1 до 100), мы можем применить более простой подход. Мы разберем по шагам, как подготовить строку так, чтобы запрос мог найти точное вхождение номера.

Проблема поиска числа в строке типа "1,10,100" заключается в том, что условие ПОДОБНО "%1%" найдет единицу во всех трех числах. Чтобы этого избежать, мы модифицируем и искомую строку, и эталонные номера, окружая их пробелами или запятыми.

Рассмотрим подробнее реализацию этого метода:


// Подготовка исходных данных: заменяем запятые на пробелы и добавляем пробелы по краям
ВЫБРАТЬ
    " " + СТРЗАМЕНИТЬ(ИсходнаяТаблица.Номера, ",", " ") + " " КАК МодифицированнаяСтрока,
    ИсходнаяТаблица.Сумма КАК ОбщаяСумма
ПОМЕСТИТЬ ВТ_Подготовка
ИЗ
    ИсходнаяТаблица КАК ИсходнаяТаблица;

// Соединение с эталонным списком номеров
ВЫБРАТЬ
    ВТ_Подготовка.ОбщаяСумма КАК Сумма,
    ВТ_Номера.Номер КАК Номер
ИЗ
    ВТ_Подготовка КАК ВТ_Подготовка
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номера КАК ВТ_Номера
    ПО ВТ_Подготовка.МодифицированнаяСтрока ПОДОБНО "% " + ВТ_Номера.Код + " %"

Этот способ прост в реализации и отлично работает, когда у вас есть справочник или временная таблица со всеми возможными значениями кодов. Оператор ПОДОБНО с пробелами гарантирует, что мы найдем именно " 7 ", а не " 17 " или " 70 ".

Проблема распределения суммы и "лишней копейки"

При разделении строк автор вопроса столкнулся с необходимостью пропорционального деления суммы. Выясним причину, по которой обычное деление может привести к ошибкам в итоговом балансе. Если мы разделим 100 рублей на 3 части, мы получим 33.33, что в сумме даст 99.99. Одна копейка будет потеряна.

Чтобы решить эту проблему прямо в запросе, проанализируем ситуацию и применим алгоритм "остатка для последней строки":

  1. Для каждой строки рассчитываем ее порядковый номер в рамках одной группы (используя АВТОНОМЕРЗАПИСИ или соединения).
  2. Считаем количество строк в группе.
  3. Для всех строк, кроме последней, применяем обычное округление: ОКР(Сумма / Количество, 2).
  4. Для последней строки вычисляем значение как ОбщаяСумма - Сумма(СуммПредыдущихСтрок).

В запросе 1С это реализуется через левое соединение таблицы самой с собой для подсчета накопленного итога, но мы рекомендуем для таких тонких расчетов всё же использовать постобработку в коде 1С, так как запрос может стать чрезмерно громоздким и медленным.

Производительность и ограничения

Нужно понимать, что использование операторов ПОДСТРОКА, НАЙТИ и ПОДОБНО в условиях соединения (JOIN) крайне негативно сказывается на производительности. СУБД не может использовать индексы для таких операций, что приводит к полному сканированию таблиц (Full Scan).

Рассмотрим рекомендации по оптимизации:

Таким образом, мы выяснили, что разделить строку в запросе 1С реально. Метод с таблицей чисел (Tally Table) является мощным инструментом в арсенале программиста, позволяющим обходить ограничения платформы и решать сложные задачи трансформации данных без выхода из контекста запроса.

← На главную