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

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

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

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

Решение 1. Использование строковых функций СТРЗАМЕНИТЬ и ДЛИНАСТРОКИ (начиная с платформы 8.3.20)

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

Посмотрим на практический пример реализации такой формулы в запросе:


ВЫБРАТЬ
    Таблица.Ссылка КАК Ссылка,
    Таблица.Наименование КАК ИсходнаяСтрока,
    ВЫБОР
        КОГДА ДЛИНАСТРОКИ(&Подстрока) = 0 ТОГДА 0
        ИНАЧЕ (ДЛИНАСТРОКИ(Таблица.Наименование) - ДЛИНАСТРОКИ(СТРЗАМЕНИТЬ(Таблица.Наименование, &Подстрока, ""))) / ДЛИНАСТРОКИ(&Подстрока)
    КОНЕЦ КАК КоличествоВхождений
ИЗ
    Справочник.Номенклатура КАК Таблица
ГДЕ
    Таблица.Наименование ПОДОБНО "%" + &Подстрока + "%"

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

  1. Защита от деления на ноль: конструкция ВЫБОР обязательна, так как передача пустой строки в параметр &Подстрока приведет к ошибке выполнения запроса при делении.
  2. Регистронезависимость: в отличие от метода встроенного языка СтрЧислоВхождений(), функция запроса СТРЗАМЕНИТЬ() на уровне большинства СУБД выполняется без учета регистра символов.
  3. Предварительная фильтрация: условие ПОДОБНО в секции ГДЕ позволяет исключить строки, где фрагмент заведомо отсутствует, снижая нагрузку на процессор СУБД.

Решение 2. Соединение со вспомогательной таблицей чисел (для ранних версий платформы)

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

Разберем по шагам, как построить такой запрос:

  1. Сформируем генератор последовательности чисел (от 1 до максимально возможной длины строки) во временной таблице.
  2. Соединим таблицу исходных данных с таблицей чисел по условию, что позиция не выходит за пределы строки.
  3. Выделим срезы функцией ПОДСТРОКА() и проверим равенство искомому значению.
  4. Сгруппируем результат по исходному объекту и подсчитаем число совпадений.

Рассмотрим пример запроса, подсчитывающего количество вхождений символа-разделителя (например, точки или дефиса):


ВЫБРАТЬ 1 КАК Число ПОМЕСТИТЬ ВТ_Числа
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 10
;

ВЫБРАТЬ
    ИсходныеДанные.Ссылка КАК Ссылка,
    КОЛИЧЕСТВО(ВТ_Числа.Число) КАК КоличествоВхождений
ИЗ
    Справочник.Номенклатура КАК ИсходныеДанные
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Числа КАК ВТ_Числа
        ПО ПОДСТРОКА(ИсходныеДанные.Наименование, ВТ_Числа.Число, 1) = &ИскомыйСимвол
СГРУППИРОВАТЬ ПО
    ИсходныеДанные.Ссылка

Если длина искомой подстроки больше одного символа (например, слово из 3 букв), в третий параметр функции ПОДСТРОКА() передается длина подстроки, а в качестве правого значения сравнения указывается сам параметр &Подстрока.

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

Решение 3. Приближенная оценка кратности через оператор ПОДОБНО

Когда точное число вхождений не требуется, а нужно лишь отсечь строки, где подстрока встречается не менее определенного количества раз, эффективнее использовать конструкцию ПОДОБНО.

Посмотрим на логику проверки:


ВЫБРАТЬ
    Таблица.Ссылка КАК Ссылка,
    ВЫБОР
        КОГДА Таблица.Наименование ПОДОБНО "%" + &Подстрока + "%" + &Подстрока + "%" + &Подстрока + "%" ТОГДА 3
        КОГДА Таблица.Наименование ПОДОБНО "%" + &Подстрока + "%" + &Подстрока + "%" ТОГДА 2
        КОГДА Таблица.Наименование ПОДОБНО "%" + &Подстрока + "%" ТОГДА 1
        ИНАЧЕ 0
    КОНЕЦ КАК КратностьВхождения
ИЗ
    Справочник.ДоговорыКонтрагентов КАК Таблица

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

Решение 4. Альтернативные подходы к реализации

Если подсчет подстрок внутри запроса оказывается избыточно сложным или неэффективным, рассмотрим альтернативные механизмы платформы 1С:

  1. Вычисляемые поля в СКД: если запрос разрабатывается для отчета на базе Системы компоновки данных, вычисление можно вынести в вычисляемое поле. В выражении поля допускается обращение к экспортным функциям неглобальных общих модулей: МойОбщийМодуль.СтрЧислоВхождений(Наименование, &ИскомыйТекст). Это избавляет запрос от тяжелых конструкций.
  2. Постобработка результата на встроенном языке: если данные выгружаются в таблицу значений или обрабатываются через ВыборкаИзРезультатаЗапроса, быстрее выбрать исходные строки из базы данных, а расчет выполнить в цикле стандартной процедурой СтрЧислоВхождений().
  3. Денормализация данных: при необходимости регулярного поиска и сортировки по количеству вхождений (например, подсчет тегов или разделов) оптимальным архитектурным решением является расчет этого числа в обработчике ПередЗаписью() объекта и сохранение значения в отдельный реквизит.
← На главную