При разработке алгоритмов расчета премий или анализа выполненных работ программисты часто сталкиваются с ситуацией, когда запрос к регистру накопления возвращает лишь малую часть ожидаемых данных или вовсе пустой результат, хотя в интерфейсе программы записи видны. Разберем эту проблему на примере задачи суммирования итогов по регистру ЗаданияНаРаботу с фильтрацией по связанным событиям.
Рассмотрим типовой сценарий: необходимо выбрать записи из регистра накопления за определенный период и присвоить каждой записи числовой коэффициент (сумму) в зависимости от параметров связанного документа. В исходном коде автора используется конструкция ВЫБОР, которая проверяет такие поля, как ЗаданияНаРаботу.Регистратор.Событие.Тема.Наименование.
Проблема заключается в том, что в консоли запросов или при выполнении кода в документе подхватывается лишь 1-2 записи из множества существующих. Давайте проанализируем, почему это происходит и как правильно организовать получение данных из связанных объектов.
Самая частая ошибка — использование цепочки Регистратор.Событие. В языке запросов 1С обращение через точку работает как неявное ЛЕВОЕ СОЕДИНЕНИЕ. Однако оно успешно отрабатывает только в том случае, если в документе, который является регистратором, физически существует реквизит Событие, и он заполнен ссылкой на конкретный объект.
Выясним причину отсутствия данных: если в вашей конфигурации связь между «Заданием на работу» и «Событием» реализована через механизм «На основании» (то есть в документе Событие есть поле Основание, указывающее на задание), то в самом задании ссылки на событие может не быть. В этом случае путь Регистратор.Событие вернет NULL, и любые сравнения (например, по наименованию темы) отсекут эти записи из итогового результата.
Проанализируем фрагмент кода, отвечающий за расчет суммы:
ВЫБОР
КОГДА ЗаданияНаРаботу.ВидРабот.Наименование = "Расчет"
И ЗаданияНаРаботу.Регистратор.Событие.ТипСобытия = ЗНАЧЕНИЕ(Перечисление.ТипыСобытий.Прочее)
И ЗаданияНаРаботу.Регистратор.Событие.Тема.Наименование = "Расчет"
ТОГДА 200
КОГДА ЗаданияНаРаботу.Регистратор.Событие.ТипСобытия = ЗНАЧЕНИЕ(Перечисление.ТипыСобытий.Прочее)
И ЗаданияНаРаботу.Регистратор.Событие.Тема.Наименование = "Расчет"
И ЗаданияНаРаботу.Регистратор.Событие.Состояние = ЗНАЧЕНИЕ(Справочник.СостоянияСобытий.Завершено)
ТОГДА 50
...
ИНАЧЕ 0
КОНЕЦ КАК Сумма
Важно помнить, что в блоке ВЫБОР (аналог CASE в SQL) условия проверяются последовательно. Если первое условие истинно, выполнение блока прекращается. В данном примере второе условие (где добавляется проверка на состояние "Завершено") является более строгим подмножеством первого. Это означает, что если событие имеет тему "Расчет", оно всегда попадет в первую ветку и вернет 200, а до проверки состояния "Завершено" дело никогда не дойдет. Правило: всегда располагайте более специфичные и подробные условия выше общих.
Использование Наименование = "Расчет" — это критическая ошибка для надежности системы. Если пользователь случайно поставит лишний пробел в названии справочника или переименует его, ваш код перестанет работать без каких-либо предупреждений. Кроме того, сравнение строк в базе данных выполняется медленнее, чем сравнение ссылочных типов.
Для того чтобы учесть все связанные события, даже если их несколько на одно задание, нам необходимо отказаться от обращения через точку и использовать ЛЕВОЕ СОЕДИНЕНИЕ с таблицей документа Событие. Рассмотрим, как должен выглядеть исправленный запрос.
ВЫБРАТЬ
ВЫБОР
КОГДА ДокСобытие.Состояние = ЗНАЧЕНИЕ(Справочник.СостоянияСобытий.Завершено)
И ДокСобытие.Тема.Наименование = "Расчет"
ТОГДА 50
КОГДА ДокСобытие.Тема.Наименование = "Расчет"
ТОГДА 200
КОГДА ЗаданияНаРаботу.ВидРабот.Наименование = "Замер"
И ЗаданияНаРаботу.Регистратор.Состояние = ЗНАЧЕНИЕ(Справочник.СостоянияСобытий.Завершено)
ТОГДА 100
ИНАЧЕ 0
КОНЕЦ КАК Сумма,
ЗаданияНаРаботу.Сотрудник КАК Сотрудник,
ЗНАЧЕНИЕ(Справочник.ВидыНачисленийИУдержаний.ПремияЗаВыполненныеЗаказы) КАК ВидНачисленияУдержания
ИЗ
РегистрНакопления.ЗаданияНаРаботу КАК ЗаданияНаРаботу
ЛЕВОЕ СОЕДИНЕНИЕ Документ.Событие КАК ДокСобытие
ПО ЗаданияНаРаботу.Регистратор = ДокСобытие.Основание
ГДЕ
ЗаданияНаРаботу.ВремяОкончания МЕЖДУ &ДатаНачала И &ДатаОкончания
Документ.Событие. Условие связи ПО ЗаданияНаРаботу.Регистратор = ДокСобытие.Основание позволяет найти все события, которые были созданы на базе нашего задания.Завершено стоит первой, что гарантирует правильное начисление 50 единиц вместо 200 для закрытых задач.При работе с большими объемами данных в регистре накопления ЗаданияНаРаботу, следует обратить внимание на индексацию поля ВремяОкончания. Если это обычный реквизит, а не стандартный период регистра, запрос может выполняться медленно. В таких случаях лучше использовать стандартный реквизит Период или убедиться, что для ВремяОкончания в конфигураторе установлено свойство "Индексировать".
Также, если вы используете ОБЪЕДИНИТЬ ВСЕ для объединения данных из разных регистров (как в исходном коде), следите за тем, чтобы типы полей в соответствующих позициях выборок совпадали. Например, если в первой части вы выбираете Регистратор, а во второй — NULL, это допустимо, но при группировке поле Регистратор придется исключить, иначе записи не схлопнутся по сотруднику.
Резюме: Если ваш запрос "не видит" записи, первым делом проверьте связи в консоли запросов, заменив обращения через точку на явные ЛЕВОЕ СОЕДИНЕНИЕ. Это позволит увидеть, на каком этапе данные превращаются в NULL и выпадают из условий фильтрации.