Почему не работает цикл группировки в запросе 1С 7.7 и как это исправить?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Разработчики, работающие с платформой 1С:Предприятие 7.7 (в обиходе — «семерка» или «клюшки»), часто сталкиваются со специфическим поведением так называемых «черных» запросов. Типичная ситуация: синтаксический анализатор не выдает ошибок, метод Запрос.Выполнить() успешно возвращает единицу (истина), однако программа полностью игнорирует тело цикла Пока Запрос.Группировка() = 1 Цикл. Отладчик показывает, что метод Группировка() сразу возвращает 0, хотя программист абсолютно уверен, что данные, соответствующие условиям поиска, в базе присутствуют.

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

Причина 1: Некорректное динамическое формирование типа документа

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


|ТекДокумент = Документ." + ТипДок + ".ТекущийДокумент;
|Идентификатор = Документ." + ТипДок + ".Идентификатор;

Если в переменной ТипДок содержится имя документа с концевыми пробелами (например, полученное из внешнего файла или текстового реквизита без предварительной обработки) или если строка пуста, то запрос сформируется некорректно. На уровне СУБД (особенно в SQL-версии) такой запрос выполнится без ошибок инициализации, но вернет пустой результат.

Решение: Перед сборкой текста запроса всегда применяйте функцию очистки от пробелов СокрЛП() и проверяйте существование объекта в метаданных конфигурации:


ИмяДока = СокрЛП(ТипДок);
Если Метаданные.Документ(ИмяДока).Выбран() = 0 Тогда
    Предупреждение("Неверно указан тип документа: " + ИмяДока);
    Возврат;
КонецЕсли;

ТекстЗапроса = "
|Период с НачПериод по КонПериод;
|ОбрабатыватьДокументы все;
|ТекДокумент = Документ." + ИмяДока + ".ТекущийДокумент;
|Идентификатор = Документ." + ИмяДока + ".Идентификатор;
|Группировка ТекДокумент;
|Условие(Идентификатор = ИдентификаторПоиск);
|";

Причина 2: Отсутствие или неявное указание конечной даты периода

В исходном запросе автора на форуме использовалась конструкция:

|Период с НачПериод;

Хотя синтаксис языка запросов 1С 7.7 теоретически допускает опускать конечную дату периода (в этом случае система подставляет рабочую дату или конец рассчитанного периода бухгалтерских итогов), на практике это часто приводит к сбоям. При работе с таблицами документов на SQL-версиях платформы неуказанная конечная дата может сместить диапазон выборки или ограничить его некорректно.

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


// Явное указание периода с использованием скобок для переменных
ТекстЗапроса = "
|Период с (НачПериод) по (КонПериод);
|// ... остальной текст запроса
|";

В качестве конечной даты (КонПериод) можно передавать переменную, содержащую ТекущаяДата(), РабочаяДата() или явную дату конца интервала.

Причина 3: Сравнение строк фиксированной длины и концевые пробелы

В обсуждаемом примере реквизит Идентификатор имеет строковый тип с длиной 36 символов (стандартный размер для GUID / UUID). Если в базе данных (в формате DBF или MS SQL) длина этого реквизита объявлена больше (например, 50 символов), система дополняет строку концевыми пробелами до максимальной длины. При прямом сравнении:

|Условие(Идентификатор = ИдентификаторПоиск);

Значение Идентификатор из базы (длиной 50 символов с пробелами на конце) никогда не совпадет с точным 36-символьным значением ИдентификаторПоиск. В результате условие отсечет все записи, и цикл группировки окажется пустым.

Решение: Отрезайте концевые пробелы с обеих сторон сравнения с помощью функции СокрЛП() прямо внутри секции Условие:

|Условие(СокрЛП(Идентификатор) = СокрЛП(ИдентификаторПоиск));

Важное предупреждение: Использование функций обработки строк (таких как СокрЛП) в секции Условие приводит к тому, что СУБД не сможет использовать индексы при поиске (произойдет так называемый Full Table Scan). На больших базах данных это вызовет колоссальное падение производительности. Безопаснее дополнять поисковую строку пробелами до длины реквизита в коде перед выполнением запроса либо использовать альтернативные методы поиска.

Причина 4: Специфика директивы "Без итогов" и обхода группировок

В тексте запроса была указана директива |Без итогов;. Она сообщает оптимизатору платформы, что дерево итогов строить не нужно, а результат должен быть возвращен в виде плоской таблицы. При включенной директиве Без итогов действуют следующие правила:

  1. Метод обхода группировок Запрос.Группировка() должен вызываться строго без параметров (без указания номера или имени группировки). Вызов вида Запрос.Группировка(1) или Запрос.Группировка("ТекДокумент") при плоской выборке гарантированно вернет 0 на некоторых релизах движка 7.7.
  2. Для стабильного построения выборки документов в SQL-версиях иногда полезно добавлять в тело запроса хотя бы одну фиктивную функцию-счетчик, чтобы гарантировать корректное формирование временных таблиц:

|Функция ВсегоДокументов = Счетчик(); ---

Альтернативные и более быстрые методы поиска документов

Штатные "черные" запросы в 1С 7.7 к таблицам документов работают крайне медленно, так как платформа сначала выполняет тяжелый SQL-запрос во временные таблицы на сервере, а затем выкачивает их локально на клиентский компьютер для последующей фильтрации. Если вам нужно найти конкретные документы по уникальному идентификатору, рекомендуется отказаться от запросов в пользу более эффективных инструментов.

Вариант А: Использование объектного метода ВыбратьДокументы()

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


Док = СоздатьОбъект("Документ." + СокрЛП(ТипДок));
Если Док.ВыбратьДокументы(НачПериод, КонПериод) = 1 Тогда
    Пока Док.ПолучитьДокумент() = 1 Цикл
        // Сравниваем очищенные от пробелов строки
        Если СокрЛП(Док.Идентификатор) = СокрЛП(ИдентификаторПоиск) Тогда
            // Документ успешно найден!
            Сообщить("Найден документ: " + Док.ТекущийДокумент());
            Прервать;
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Вариант Б: Быстрый отбор по индексированному реквизиту (ВыбратьПоЗначению)

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

  1. Откройте конфигуратор 1С 7.7.
  2. Найдите ваш реквизит Идентификатор (он должен быть либо Общим реквизитом документов, либо реквизитом конкретного документа).
  3. В свойствах реквизита на вкладке "Дополнительно" установите флаг "Сортировка". Это заставит систему создать индекс по данному полю в базе данных (в таблице _1SJOURN или таблице конкретного документа).
  4. Используйте следующий код для мгновенного поиска (работает доли секунды даже на терабайтных базах данных):

// Используем универсальный объект "Документ" для поиска по общему реквизиту
Док = СоздатьОбъект("Документ");
ИскомыйВидДока = СокрЛП(ТипДок);

Если Док.ВыбратьПоЗначению(НачПериод, КонПериод, "Идентификатор", СокрЛП(ИдентификаторПоиск)) = 1 Тогда
    Пока Док.ПолучитьДокумент() = 1 Цикл
        // Проверяем тип найденного документа, если поиск идет среди всех видов
        Если Док.Вид() = ИскомыйВидДока Тогда
            Сообщить("Документ найден по индексу: " + Док.ТекущийДокумент());
            // Выполняем логику...
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Вариант В: Прямой SQL-запрос через внешнюю компоненту "1С++"

Если ваша база данных развернута на Microsoft SQL Server, использование штатных средств 1С 7.7 для сложных выборок неэффективно. С помощью популярной библиотеки 1С++ (класс ODBCRecordset) можно написать прямой SQL-запрос, который обратится непосредственно к таблицам СУБД, минуя программный слой 1С:


// Пример выполнения прямого SQL-запроса через 1С++
Recordset = СоздатьОбъект("ODBCRecordset");

ТекстSQL = "
|SELECT 
|    Doc.ID as [Док $Документ." + СокрЛП(ТипДок) + "]
|FROM 
|    $Документ." + СокрЛП(ТипДок) + " as Doc WITH (NOLOCK)
|INNER JOIN
|    _1SJOURN as Journ WITH (NOLOCK) ON Journ.IDDoc = Doc.IDDoc
|WHERE 
|    $Doc.Идентификатор = :ИскомыйИД
|    AND Journ.Date_Time_IDDoc BETWEEN :НачДата AND :КонДата
|";

Recordset.УстановитьТекстовыйПараметр("ИскомыйИД", СокрЛП(ИдентификаторПоиск));
Recordset.УстановитьТекстовыйПараметр("НачДата", НачПериод);
Recordset.УстановитьТекстовыйПараметр("КонДата", КонПериод);

ТЗ = Recordset.ВыполнитьИнструкцию(ТекстSQL);
// На выходе получаем плоскую Таблицу Значений со списком найденных документов
ТЗ.ВыбратьСтроку();

Такой подход полностью снимает проблемы с неверно сопоставленными типами данных, нечувствителен к ошибкам парсинга штатного движка запросов 1С 7.7 и выполняется со скоростью работы SQL-сервера.

← На главную