В практике разработки на платформе 1С часто возникает задача поиска дублей или агрегации данных по части строкового поля. Типичный пример — шифр документа, где последние символы обозначают версию или индекс подразделения, а основная часть строки идентифицирует сам объект. Попытка использовать привычные функции работы со строками непосредственно в блоке СГРУППИРОВАТЬ ПО часто приводит к ошибкам. Разберем подробно, почему так происходит и какие существуют способы решения этой задачи.
Рассмотрим ситуацию: нам нужно найти дубликаты документов, у которых совпадает ШифрДокумента (длиной 35 символов) без учета последних 4 знаков. Начинающий разработчик может попытаться написать запрос следующим образом:
ВЫБРАТЬ
КОЛИЧЕСТВО(Реестр.Ссылка) КАК КоличествоДублей,
ЛЕВ(Реестр.ШифрДокумента, ДЛИНАСТРОКИ(Реестр.ШифрДокумента) - 4) КАК ШифрБезВерсии
ИЗ
Документ.РеестрЗаданий КАК Реестр
СГРУППИРОВАТЬ ПО
ЛЕВ(Реестр.ШифрДокумента, ДЛИНАСТРОКИ(Реестр.ШифрДокумента) - 4)
ИМЕЮЩИЕ
КОЛИЧЕСТВО(Реестр.Ссылка) > 1
При выполнении такого запроса система выдаст ошибку: "Недопустимое поле для группировки". Проанализируем причину. До версии платформы 8.3.20 функции ЛЕВ(), ПРАВ() и ДЛИНАСТРОКИ() вообще не поддерживались в языке запросов. Но даже в современных версиях основной камень преткновения — динамическое вычисление длины. Функция ПОДСТРОКА(), которая является классическим инструментом, требует, чтобы параметры смещения и длины были литералами (конкретными числами), а не результатами других функций.
Если мы точно знаем, сколько символов нам нужно оставить с начала строки, самым надежным и универсальным способом будет использование функции ПОДСТРОКА(). Рассмотрим ситуацию, когда общая длина шифра — 35 символов, и нам нужно отсечь последние 4. Значит, нам нужно взять первые 31 символ.
Проанализируем пример кода:
ВЫБРАТЬ
ПОДСТРОКА(Реестр.ШифрДокумента, 1, 31) КАК ШифрБезВерсии,
КОЛИЧЕСТВО(Реестр.Ссылка) КАК КоличествоДублей
ИЗ
Документ.РеестрЗаданий КАК Реестр
ГДЕ
НЕ Реестр.ПометкаУдаления
СГРУППИРОВАТЬ ПО
ПОДСТРОКА(Реестр.ШифрДокумента, 1, 31)
ИМЕЮЩИЕ
КОЛИЧЕСТВО(Реестр.Ссылка) > 1
Важный нюанс: Этот метод идеально работает, если длина строки всегда постоянна. Если длина "хвоста" фиксирована, а общая длина строки меняется, этот способ в чистом виде не подойдет, так как 1С не позволит написать ПОДСТРОКА(Поле, 1, ДЛИНАСТРОКИ(Поле) - 4).
Как отмечают опытные разработчики, приведение типа через ВЫРАЗИТЬ может работать быстрее, чем ПОДСТРОКА, так как на уровне СУБД это преобразуется в простую операцию изменения типа данных. Если нам нужно оставить первые N символов, мы можем принудительно ограничить длину строки.
ВЫБРАТЬ
ВЫРАЗИТЬ(Реестр.ШифрДокумента КАК СТРОКА(31)) КАК ШифрСокращенный,
КОЛИЧЕСТВО(Реестр.Ссылка) КАК КоличествоДублей
ИЗ
Документ.РеестрЗаданий КАК Реестр
СГРУППИРОВАТЬ ПО
ВЫРАЗИТЬ(Реестр.ШифрДокумента КАК СТРОКА(31))
Помните, что ВЫРАЗИТЬ просто "обрезает" строку до указанной длины. Это работает только для строк фиксированной длины или в случаях, когда логика "отрезать последние 4" эквивалентна "оставить первые 31".
Начиная с версии 8.3.20, в языке запросов появились долгожданные функции ЛЕВ(), ПРАВ(), СТРЗАМЕНИТЬ() и другие. Это значительно упрощает синтаксис. Однако здесь есть критически важное условие: режим совместимости конфигурации должен быть установлен не ниже 8.3.20.
Если условия соблюдены, мы можем использовать более изящный код:
ВЫБРАТЬ
ЛЕВ(Реестр.ШифрДокумента, 31) КАК ШифрСокращенный
ИЗ
Документ.РеестрЗаданий КАК Реестр
СГРУППИРОВАТЬ ПО
ЛЕВ(Реестр.ШифрДокумента, 31)
Если задача стоит сложнее — например, длина строки переменная, и нам действительно нужно вычислять количество символов динамически — лучше всего разделить запрос на два этапа с использованием временной таблицы. Это позволит избежать ошибок при группировке сложных выражений.
Разберем этот алгоритм по шагам:
СГРУППИРОВАТЬ ПО уже готовому полю.Пример реализации:
// Шаг 1: Подготовка данных
ВЫБРАТЬ
Реестр.Ссылка КАК Ссылка,
ПОДСТРОКА(Реестр.ШифрДокумента, 1, 31) КАК ЧистыйШифр
ПОМЕСТИТЬ ВТ_Данные
ИЗ
Документ.РеестрЗаданий КАК Реестр ;
// Шаг 2: Группировка
ВЫБРАТЬ
ВТ.ЧистыйШифр,
КОЛИЧЕСТВО(ВТ.Ссылка) КАК КолВо
ИЗ
ВТ_Данные КАК ВТ
СГРУППИРОВАТЬ ПО
ВТ.ЧистыйШифр
ИМЕЮЩИЕ
КОЛИЧЕСТВО(ВТ.Ссылка) > 1
Проанализируем ситуацию с точки зрения нагрузки на сервер. Использование любых функций (ПОДСТРОКА, ЛЕВ, ВЫРАЗИТЬ) в секции ГДЕ или СГРУППИРОВАТЬ ПО делает запрос не-SARGable. Это означает, что СУБД (SQL Server или PostgreSQL) не сможет использовать индексы по полю ШифрДокумента. Система будет вынуждена прочитать абсолютно все записи таблицы (Full Table Scan), вычислить для каждой результат функции и только потом выполнить группировку.
Рекомендация для высоконагруженных систем: Если поиск дублей по части шифра требуется выполнять часто, правильнее будет создать в документе дополнительный реквизит ШифрДляПоиска. При записи документа программно записывайте в него нужную часть строки. Индексация этого реквизита позволит запросу работать мгновенно даже на миллионах записей.
Выясним основные правила работы с группировкой по строкам:
ПОДСТРОКА(Поле, 1, Длина).ВЫРАЗИТЬ(Поле КАК СТРОКА(N)).ДЛИНАСТРОКИ).Рассмотренные методы позволяют гибко манипулировать строковыми данными в запросах, обеспечивая корректную работу механизмов агрегации и поиска дублей.