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

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

В практике разработки на платформе 1С часто возникает задача поиска дублей или агрегации данных по части строкового поля. Типичный пример — шифр документа, где последние символы обозначают версию или индекс подразделения, а основная часть строки идентифицирует сам объект. Попытка использовать привычные функции работы со строками непосредственно в блоке СГРУППИРОВАТЬ ПО часто приводит к ошибкам. Разберем подробно, почему так происходит и какие существуют способы решения этой задачи.

Анализ проблемы: почему стандартный подход не работает

Рассмотрим ситуацию: нам нужно найти дубликаты документов, у которых совпадает ШифрДокумента (длиной 35 символов) без учета последних 4 знаков. Начинающий разработчик может попытаться написать запрос следующим образом:


ВЫБРАТЬ
    КОЛИЧЕСТВО(Реестр.Ссылка) КАК КоличествоДублей,
    ЛЕВ(Реестр.ШифрДокумента, ДЛИНАСТРОКИ(Реестр.ШифрДокумента) - 4) КАК ШифрБезВерсии
ИЗ
    Документ.РеестрЗаданий КАК Реестр
СГРУППИРОВАТЬ ПО
    ЛЕВ(Реестр.ШифрДокумента, ДЛИНАСТРОКИ(Реестр.ШифрДокумента) - 4)
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(Реестр.Ссылка) > 1

При выполнении такого запроса система выдаст ошибку: "Недопустимое поле для группировки". Проанализируем причину. До версии платформы 8.3.20 функции ЛЕВ(), ПРАВ() и ДЛИНАСТРОКИ() вообще не поддерживались в языке запросов. Но даже в современных версиях основной камень преткновения — динамическое вычисление длины. Функция ПОДСТРОКА(), которая является классическим инструментом, требует, чтобы параметры смещения и длины были литералами (конкретными числами), а не результатами других функций.

Решение 1: Использование функции ПОДСТРОКА для фиксированной длины

Если мы точно знаем, сколько символов нам нужно оставить с начала строки, самым надежным и универсальным способом будет использование функции ПОДСТРОКА(). Рассмотрим ситуацию, когда общая длина шифра — 35 символов, и нам нужно отсечь последние 4. Значит, нам нужно взять первые 31 символ.

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


ВЫБРАТЬ
    ПОДСТРОКА(Реестр.ШифрДокумента, 1, 31) КАК ШифрБезВерсии,
    КОЛИЧЕСТВО(Реестр.Ссылка) КАК КоличествоДублей
ИЗ
    Документ.РеестрЗаданий КАК Реестр
ГДЕ
    НЕ Реестр.ПометкаУдаления
СГРУППИРОВАТЬ ПО
    ПОДСТРОКА(Реестр.ШифрДокумента, 1, 31)
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(Реестр.Ссылка) > 1

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

Решение 2: Использование ВЫРАЗИТЬ для ограничения длины

Как отмечают опытные разработчики, приведение типа через ВЫРАЗИТЬ может работать быстрее, чем ПОДСТРОКА, так как на уровне СУБД это преобразуется в простую операцию изменения типа данных. Если нам нужно оставить первые N символов, мы можем принудительно ограничить длину строки.


ВЫБРАТЬ
    ВЫРАЗИТЬ(Реестр.ШифрДокумента КАК СТРОКА(31)) КАК ШифрСокращенный,
    КОЛИЧЕСТВО(Реестр.Ссылка) КАК КоличествоДублей
ИЗ
    Документ.РеестрЗаданий КАК Реестр
СГРУППИРОВАТЬ ПО
    ВЫРАЗИТЬ(Реестр.ШифрДокумента КАК СТРОКА(31))

Помните, что ВЫРАЗИТЬ просто "обрезает" строку до указанной длины. Это работает только для строк фиксированной длины или в случаях, когда логика "отрезать последние 4" эквивалентна "оставить первые 31".

Решение 3: Новые возможности платформы 8.3.20+

Начиная с версии 8.3.20, в языке запросов появились долгожданные функции ЛЕВ(), ПРАВ(), СТРЗАМЕНИТЬ() и другие. Это значительно упрощает синтаксис. Однако здесь есть критически важное условие: режим совместимости конфигурации должен быть установлен не ниже 8.3.20.

Если условия соблюдены, мы можем использовать более изящный код:


ВЫБРАТЬ
    ЛЕВ(Реестр.ШифрДокумента, 31) КАК ШифрСокращенный
ИЗ
    Документ.РеестрЗаданий КАК Реестр
СГРУППИРОВАТЬ ПО
    ЛЕВ(Реестр.ШифрДокумента, 31)

Продвинутый подход: Группировка через временные таблицы

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

Разберем этот алгоритм по шагам:

  1. В первом запросе выбираем все необходимые данные и вычисляем строковое поле, по которому будем группировать.
  2. Помещаем результат во временную таблицу.
  3. Во втором запросе выбираем данные из временной таблицы и выполняем СГРУППИРОВАТЬ ПО уже готовому полю.

Пример реализации:


// Шаг 1: Подготовка данных
ВЫБРАТЬ
    Реестр.Ссылка КАК Ссылка,
    ПОДСТРОКА(Реестр.ШифрДокумента, 1, 31) КАК ЧистыйШифр
ПОМЕСТИТЬ ВТ_Данные
ИЗ
    Документ.РеестрЗаданий КАК Реестр ;

// Шаг 2: Группировка
ВЫБРАТЬ
    ВТ.ЧистыйШифр,
    КОЛИЧЕСТВО(ВТ.Ссылка) КАК КолВо
ИЗ
    ВТ_Данные КАК ВТ
СГРУППИРОВАТЬ ПО
    ВТ.ЧистыйШифр
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(ВТ.Ссылка) > 1

Проблема производительности (SARGability)

Проанализируем ситуацию с точки зрения нагрузки на сервер. Использование любых функций (ПОДСТРОКА, ЛЕВ, ВЫРАЗИТЬ) в секции ГДЕ или СГРУППИРОВАТЬ ПО делает запрос не-SARGable. Это означает, что СУБД (SQL Server или PostgreSQL) не сможет использовать индексы по полю ШифрДокумента. Система будет вынуждена прочитать абсолютно все записи таблицы (Full Table Scan), вычислить для каждой результат функции и только потом выполнить группировку.

Рекомендация для высоконагруженных систем: Если поиск дублей по части шифра требуется выполнять часто, правильнее будет создать в документе дополнительный реквизит ШифрДляПоиска. При записи документа программно записывайте в него нужную часть строки. Индексация этого реквизита позволит запросу работать мгновенно даже на миллионах записей.

Резюме

Выясним основные правила работы с группировкой по строкам:

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

← На главную