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

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

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

В этой статье мы подробно разберем, как реализовать такую проверку в платформе «1С:Предприятие». Мы проанализируем ошибки, возникающие при работе со вложенными табличными частями в запросах, рассмотрим правильные методы обхода выборки и изучим лучшие практики оптимизации этого процесса с точки зрения производительности СУБД и архитектуры 1С.

Анализ исходной проблемы и разбор ошибок в коде

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


ВЫБРАТЬ
    ВедомостьРаздачиПищи.Список.(
        ФИОСотрудника,
        КолОбед
    ),
    ВедомостьРаздачиПищи.Ссылка КАК Документ
ИЗ
    Документ.ВедомостьРаздачиПищи КАК ВедомостьРаздачиПищи

Такой синтаксис запроса — ВедомостьРаздачиПищи.Список.(...) — возвращает вложенную таблицу (тип РезультатЗапроса внутри колонки основной выборки). При попытке обойти такой результат обычным циклом возникает популярная ошибка:

«Метод объекта не обнаружен (Следующий)»

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

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


Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    // Получаем выборку по вложенной табличной части "Список"
    ВыборкаСписок = Выборка.Список.Выбрать();
    Пока ВыборкаСписок.Следующий() Цикл
        Если ВыборкаСписок.КолОбед > 0 Тогда
            // Здесь обрабатываем результат
            Предупреждение("Сотрудник уже обедал!");
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

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

Решение №1: Переход к «плоской» структуре запроса (Рекомендуемый способ)

Вместо того чтобы выбирать документ и вложенную в него таблицу, мы можем обратиться напрямую к табличной части документа как к самостоятельной таблице базы данных: Документ.ВедомостьРаздачиПищи.Список. Это разворачивает результат в плоскую структуру, где каждая строка — это отдельная запись, содержащая и данные сотрудника, и ссылку на сам документ.

Давайте перепишем наш запрос и процедуру проверки по шагам:

  1. Обращаемся напрямую к табличной части Документ.ВедомостьРаздачиПищи.Список.
  2. Связываем её с реквизитами шапки документа (такими как Проведен и МестоХранения) через точку от поля Ссылка.
  3. Группируем результат, чтобы свернуть возможные дубли внутри одного документа.

Посмотрим на пример реализации такого запроса:


Процедура СписокКолПриИзменении(Элемент)
    СтрокаТаблицы = ЭлементыФормы.Список.ТекущиеДанные;
    Если СтрокаТаблицы = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ВедомостьСписок.Ссылка КАК Документ,
        |	ВедомостьСписок.Ссылка.МестоХранения КАК Склад,
        |	СУММА(ВедомостьСписок.КолОбед) КАК КолОбед,
        |	СУММА(ВедомостьСписок.КолУжин) КАК КолУжин,
        |	СУММА(ВедомостьСписок.КолСобойка) КАК КолСобойка
        |ИЗ
        |	Документ.ВедомостьРаздачиПищи.Список КАК ВедомостьСписок
        |ГДЕ
        |	ВедомостьСписок.Ссылка.Дата МЕЖДУ &НачалоДня И &КонецДня
        |	И ВедомостьСписок.ФИОСотрудника = &ФИОСотрудника
        |	И ВедомостьСписок.Ссылка.Проведен = ИСТИНА
        |	И ВедомостьСписок.Ссылка <> &ТекущийДокумент
        |
        |СГРУППИРОВАТЬ ПО
        |	ВедомостьСписок.Ссылка,
        |	ВедомостьСписок.Ссылка.МестоХранения";

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

    Результат = Запрос.Выполнить();
    Если Не Результат.Пустой() Тогда
        Выборка = Результат.Выбрать();
        Пока Выборка.Следующий() Цикл
            Если Выборка.КолОбед > 0 Тогда
                ВызватьИсключение "Сотрудник уже получал обед сегодня в документе " 
                    + Выборка.Документ + " на складе/раздаче: " + Выборка.Склад;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    
    // Если проверок нет — рассчитываем стоимость
    РассчитатьСтрокуПитания(СтрокаТаблицы);
КонецПроцедуры

Важные нюансы оптимизации, о которых нельзя забывать

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

  1. Исключение текущего документа из проверки: Условие ВедомостьСписок.Ссылка <> &ТекущийДокумент жизненно важно. Если ваш документ уже сохранен или проведен, то при его повторном открытии и изменении строки запрос без этого условия найдет этот же самый документ. Произойдет ложное срабатывание — система решит, что сотрудник уже обедал, хотя это запись из текущего документа.
  2. Оптимизация работы с датами (Индексы СУБД): Конструкция КОНЕЦПЕРИОДА(Дата, ДЕНЬ) = КОНЕЦПЕРИОДА(&Дата, ДЕНЬ) — это серьезная ошибка производительности. СУБД не сможет использовать индексы по полю Дата, так как поле обернуто в функцию. Вместо этого мы применили конструкцию МЕЖДУ &НачалоДня И &КонецДня. Параметры формируются в коде функциями НачалоДня() и КонецДня(). Это позволяет SQL-серверу мгновенно находить нужные записи в индексе.
  3. Тип данных поля «Сотрудник»: Если реквизит ФИОСотрудника в вашей конфигурации имеет тип «Строка», то система уязвима к опечаткам. Например, «Иванов И.И.» и «Иванов И.И.» (с лишним пробелом) СУБД посчитает разными людьми. Мы настоятельно рекомендуем использовать ссылку на справочник ФизическиеЛица или Сотрудники.

Решение №2: Проектирование архитектуры через Регистры сведений

Запросы напрямую к табличным частям документов для оперативного контроля лимитов и дублей — это архитектурный антипаттерн платформы «1С:Предприятие». Документы предназначены для регистрации факта хозяйственной операции, но не для быстрого извлечения агрегированных данных.

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

Разберем этот паттерн по шагам:

  1. Создадим регистр сведений ПредоставленноеПитание с режимом записи «Подчинение регистратору».
  2. Определим измерения регистра: Период (День), Сотрудник (СправочникСсылка.ФизическиеЛица), ВидПитания (ПеречислениеСсылка.ВидыПитания: Обед, Ужин, Собойка).
  3. Определим ресурс регистра: Количество (Число).
  4. При проведении документа ВедомостьРаздачиПищи запишем движения по этому регистру.
  5. При проверке на форме или в транзакции проведения мы будем обращаться к виртуальной таблице этого регистра, что работает мгновенно даже на миллионах записей.

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ПредоставленноеПитаниеОбороты.Сотрудник,
    |	ПредоставленноеПитаниеОбороты.ВидПитания,
    |	ПредоставленноеПитаниеОбороты.КоличествоОборот КАК Кол
    |ИЗ
    |	РегистрНакопления.ПредоставленноеПитание.Обороты(&НачалоДня, &КонецДня, , Сотрудник = &Сотрудник) КАК ПредоставленноеПитаниеОбороты
    |ГДЕ
    |	ПредоставленноеПитаниеОбороты.КоличествоОборот > 0";

Такой подход полностью исключает необходимость «читать» тяжелые таблицы документов и гарантирует максимальное быстродействие вашей системы.

← На главную