Почему запрос в 1С не видит все записи регистра накопления при обращении к связанным документам через точку?

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

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

Анализ исходной ситуации

Рассмотрим типовой сценарий: необходимо выбрать записи из регистра накопления за определенный период и присвоить каждой записи числовой коэффициент (сумму) в зависимости от параметров связанного документа. В исходном коде автора используется конструкция ВЫБОР, которая проверяет такие поля, как ЗаданияНаРаботу.Регистратор.Событие.Тема.Наименование.

Проблема заключается в том, что в консоли запросов или при выполнении кода в документе подхватывается лишь 1-2 записи из множества существующих. Давайте проанализируем, почему это происходит и как правильно организовать получение данных из связанных объектов.

Причина №1: Обращение через точку к отсутствующему реквизиту

Самая частая ошибка — использование цепочки Регистратор.Событие. В языке запросов 1С обращение через точку работает как неявное ЛЕВОЕ СОЕДИНЕНИЕ. Однако оно успешно отрабатывает только в том случае, если в документе, который является регистратором, физически существует реквизит Событие, и он заполнен ссылкой на конкретный объект.

Выясним причину отсутствия данных: если в вашей конфигурации связь между «Заданием на работу» и «Событием» реализована через механизм «На основании» (то есть в документе Событие есть поле Основание, указывающее на задание), то в самом задании ссылки на событие может не быть. В этом случае путь Регистратор.Событие вернет NULL, и любые сравнения (например, по наименованию темы) отсекут эти записи из итогового результата.

Причина №2: Нарушение логики в конструкции ВЫБОР

Проанализируем фрагмент кода, отвечающий за расчет суммы:


ВЫБОР 
    КОГДА ЗаданияНаРаботу.ВидРабот.Наименование = "Расчет" 
        И ЗаданияНаРаботу.Регистратор.Событие.ТипСобытия = ЗНАЧЕНИЕ(Перечисление.ТипыСобытий.Прочее) 
        И ЗаданияНаРаботу.Регистратор.Событие.Тема.Наименование = "Расчет" 
        ТОГДА 200 
    КОГДА ЗаданияНаРаботу.Регистратор.Событие.ТипСобытия = ЗНАЧЕНИЕ(Перечисление.ТипыСобытий.Прочее) 
        И ЗаданияНаРаботу.Регистратор.Событие.Тема.Наименование = "Расчет" 
        И ЗаданияНаРаботу.Регистратор.Событие.Состояние = ЗНАЧЕНИЕ(Справочник.СостоянияСобытий.Завершено) 
        ТОГДА 50 
    ...
ИНАЧЕ 0 
КОНЕЦ КАК Сумма

Важно помнить, что в блоке ВЫБОР (аналог CASE в SQL) условия проверяются последовательно. Если первое условие истинно, выполнение блока прекращается. В данном примере второе условие (где добавляется проверка на состояние "Завершено") является более строгим подмножеством первого. Это означает, что если событие имеет тему "Расчет", оно всегда попадет в первую ветку и вернет 200, а до проверки состояния "Завершено" дело никогда не дойдет. Правило: всегда располагайте более специфичные и подробные условия выше общих.

Причина №3: Сравнение по наименованию вместо ссылок

Использование Наименование = "Расчет" — это критическая ошибка для надежности системы. Если пользователь случайно поставит лишний пробел в названии справочника или переименует его, ваш код перестанет работать без каких-либо предупреждений. Кроме того, сравнение строк в базе данных выполняется медленнее, чем сравнение ссылочных типов.

Правильное решение: использование явного соединения таблиц

Для того чтобы учесть все связанные события, даже если их несколько на одно задание, нам необходимо отказаться от обращения через точку и использовать ЛЕВОЕ СОЕДИНЕНИЕ с таблицей документа Событие. Рассмотрим, как должен выглядеть исправленный запрос.


ВЫБРАТЬ
    ВЫБОР
        КОГДА ДокСобытие.Состояние = ЗНАЧЕНИЕ(Справочник.СостоянияСобытий.Завершено)
            И ДокСобытие.Тема.Наименование = "Расчет"
            ТОГДА 50
        КОГДА ДокСобытие.Тема.Наименование = "Расчет"
            ТОГДА 200
        КОГДА ЗаданияНаРаботу.ВидРабот.Наименование = "Замер"
            И ЗаданияНаРаботу.Регистратор.Состояние = ЗНАЧЕНИЕ(Справочник.СостоянияСобытий.Завершено)
            ТОГДА 100
        ИНАЧЕ 0
    КОНЕЦ КАК Сумма,
    ЗаданияНаРаботу.Сотрудник КАК Сотрудник,
    ЗНАЧЕНИЕ(Справочник.ВидыНачисленийИУдержаний.ПремияЗаВыполненныеЗаказы) КАК ВидНачисленияУдержания
ИЗ
    РегистрНакопления.ЗаданияНаРаботу КАК ЗаданияНаРаботу
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.Событие КАК ДокСобытие
        ПО ЗаданияНаРаботу.Регистратор = ДокСобытие.Основание
ГДЕ
    ЗаданияНаРаботу.ВремяОкончания МЕЖДУ &ДатаНачала И &ДатаОкончания

Разберем по шагам внесенные изменения:

  1. Левое соединение: Мы явно присоединяем таблицу Документ.Событие. Условие связи ПО ЗаданияНаРаботу.Регистратор = ДокСобытие.Основание позволяет найти все события, которые были созданы на базе нашего задания.
  2. Обработка связи «Один ко многим»: Если к одному заданию привязано три события, после соединения в выборке появится три строки. Это позволит нам корректно просуммировать бонусы за каждое действие в последующей группировке.
  3. Изменение порядка условий: Теперь проверка состояния Завершено стоит первой, что гарантирует правильное начисление 50 единиц вместо 200 для закрытых задач.
  4. Работа с итогами: Чтобы итоговая сумма в документе начисления была верной, результат этого запроса необходимо сгруппировать по сотруднику и виду начисления.

Рекомендации по оптимизации запроса

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

Также, если вы используете ОБЪЕДИНИТЬ ВСЕ для объединения данных из разных регистров (как в исходном коде), следите за тем, чтобы типы полей в соответствующих позициях выборок совпадали. Например, если в первой части вы выбираете Регистратор, а во второй — NULL, это допустимо, но при группировке поле Регистратор придется исключить, иначе записи не схлопнутся по сотруднику.

Резюме: Если ваш запрос "не видит" записи, первым делом проверьте связи в консоли запросов, заменив обращения через точку на явные ЛЕВОЕ СОЕДИНЕНИЕ. Это позволит увидеть, на каком этапе данные превращаются в NULL и выпадают из условий фильтрации.

← На главную