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

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

В практике разработчика 1С часто возникает задача анализа непрерывности нумерации. Например, необходимо выяснить, какие номера документов были пропущены, или, наоборот, вывести список всех непрерывных "островов" нумерации в формате "с номера X по номер Y". В классическом SQL эта задача известна как Gaps and Islands (пропуски и острова).

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

Подготовка данных: Преобразование строки в число

Первая сложность, с которой мы сталкиваемся в 1С — это то, что номера документов (поле Номер) являются строковыми. Для выполнения математических операций, таких как поиск следующего числа (n+1), нам необходимо преобразовать строку в число прямо в запросе. Поскольку в языке запросов 1С нет функции Число(), мы применим метод суммирования разрядов.

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


// Создаем таблицу весов для разрядов (16-ричная или 10-ричная система)
ВЫБРАТЬ 0 КАК С, 1 КАК П ПОМЕСТИТЬ Р10
ОБЪЕДИНИТЬ ВЫБРАТЬ 1, 10 ОБЪЕДИНИТЬ ВЫБРАТЬ 2, 100 
ОБЪЕДИНИТЬ ВЫБРАТЬ 3, 1000 ОБЪЕДИНИТЬ ВЫБРАТЬ 4, 10000;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    СУММА(Разряды.П * Значения.Цифра) КАК ЗначениеЧисло,
    Дано.Номер КАК ОригинальныйНомер
ПОМЕСТИТЬ НомераСЧислами
ИЗ
    Дано КАК Дано
    // Здесь предполагается логика выделения цифр из подстроки и умножения на веса
    // Для простоты разберем алгоритм уже на готовых числах
ГРУППИРОВАТЬ ПО
    Дано.Номер;

Важный момент: Если вы работаете с типовыми номерами, содержащими префиксы (например, "00БП-00001"), предварительно используйте функцию ПОДСТРОКА(), чтобы выделить только цифровую часть. Однако помните, что длина префикса может меняться, поэтому надежнее всего хранить числовой номер в отдельном реквизите при записи документа.

Метод поиска границ интервалов (Начало и Конец)

Разберем самый понятный алгоритм: поиск элементов, у которых нет "соседа" слева (это будет начало интервала) и элементов, у которых нет "соседа" справа (это будет конец интервала).

Проанализируем логику:

  1. Начало интервала: Число X является началом, если в таблице отсутствует число X - 1.
  2. Конец интервала: Число X является концом, если в таблице отсутствует число X + 1.

Реализуем это в запросе:


// Находим все начала интервалов
ВЫБРАТЬ
    ТЗ.Значение КАК НачалоЧисло
ПОМЕСТИТЬ ВТ_Начала
ИЗ
    ТЗ_Чисел КАК ТЗ
    ЛЕВОЕ СОЕДИНЕНИЕ ТЗ_Чисел КАК ТЗ_Минус_1
    ПО (ТЗ.Значение - 1 = ТЗ_Минус_1.Значение)
ГДЕ
    ТЗ_Минус_1.Значение ЕСТЬ NULL;

////////////////////////////////////////////////////////////////////////////////
// Находим все концы интервалов
ВЫБРАТЬ
    ТЗ.Значение КАК КонецЧисло
ПОМЕСТИТЬ ВТ_Концы
ИЗ
    ТЗ_Чисел КАК ТЗ
    ЛЕВОЕ СОЕДИНЕНИЕ ТЗ_Чисел КАК ТЗ_Плюс_1
    ПО (ТЗ.Значение + 1 = ТЗ_Плюс_1.Значение)
ГДЕ
    ТЗ_Плюс_1.Значение ЕСТЬ NULL;

Сборка интервалов в итоговый результат

Теперь, когда у нас есть две временные таблицы (ВТ_Начала и ВТ_Концы), нам нужно "сшить" их. Для каждого начала интервала концом будет минимальное из чисел в таблице концов, которое больше или равно этому началу.

Рассмотрим финальный запрос:


ВЫБРАТЬ
    ВТ_Начала.НачалоЧисло КАК НачалоИнтервала,
    МИНИМУМ(ВТ_Концы.КонецЧисло) КАК КонецИнтервала
ИЗ
    ВТ_Начала КАК ВТ_Начала
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Концы КАК ВТ_Концы
    ПО ВТ_Начала.НачалоЧисло <= ВТ_Концы.КонецЧисло
ГРУППИРОВАТЬ ПО
    ВТ_Начала.НачалоЧисло
УПОРЯДОЧИТЬ ПО
    НачалоИнтервала;

В результате мы получим таблицу, где каждая строка — это отдельный непрерывный период. Например, для последовательности 1, 2, 5, 6, 7 мы получим две строки: (1, 2) и (5, 7).

Альтернативный метод: Разность рангов

Существует еще один изящный способ решения задачи "островов", который часто работает быстрее на больших объемах данных. Он основан на использовании порядкового номера (ранга).

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

Пример:

Как мы видим, у первой группы разность всегда 9, у второй — 11. В запросе 1С порядковый номер можно получить через соединение таблицы самой с собой (количество элементов, которые меньше текущего).


ВЫБРАТЬ
    Т1.Значение КАК Значение,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т2.Значение) КАК НомерПоПорядку
ПОМЕСТИТЬ ТаблицаСРангом
ИЗ
    ТЗ_Чисел КАК Т1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТЗ_Чисел КАК Т2
    ПО Т1.Значение >= Т2.Значение
ГРУППИРОВАТЬ ПО
    Т1.Значение;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    МИНИМУМ(Значение) КАК Начало,
    МАКСИМУМ(Значение) КАК Конец
ИЗ
    ТаблицаСРангом
ГРУППИРОВАТЬ ПО
    (Значение - НомерПоПорядку);

Особенности производительности и форматирования

Проанализируем технические ограничения, которые важно учитывать при реализации данного механизма:

  1. Производительность: Методы, использующие соединения ПО Т1.Значение >= Т2.Значение или Т1.Значение = Т2.Значение + 1, имеют квадратичную зависимость от объема данных. Если в вашей выборке десятки тысяч документов, запрос может выполняться крайне медленно. В таких случаях эффективнее выгрузить отсортированные данные в ТаблицуЗначений и пройтись по ней один раз циклом на встроенном языке.
  2. Индексация: При использовании временных таблиц в запросе всегда индексируйте поле Значение. Это критически важно для скорости выполнения соединений.
  3. Форматирование строк: Если ваша цель — вывести строку вида "с 001 по 005", помните, что конкатенация строк и чисел в запросе 1С сильно ограничена. Проще всего получить таблицу начальных и конечных чисел в запросе, а окончательную "склейку" строк выполнить уже в коде 1С или в вычисляемых полях СКД.
  4. Разрезы учета: Не забывайте, что нумерация обычно ведется в разрезе организаций или типов документов. При поиске периодов обязательно добавляйте соответствующие поля в условия соединения и группировки, чтобы не "склеить" номера разных организаций.

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

← На главную