Как правильно выстраивать связи и делать группировку в запросе 1С, чтобы избежать дублей?

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

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

Суть проблемы: откуда берутся лишние строки?

Рассмотрим типичную ситуацию. У нас есть две таблицы (или временные таблицы), которые мы хотим соединить:

  1. ВТ_Начисления — здесь содержатся данные о начислениях сотрудников (например, Оклад). Предположим, по одному сотруднику здесь одна строка.
  2. ВТ_Дни — здесь содержатся данные об отработанном времени. Данные могут быть детализированы по видам времени или разбиты по периодам.

При попытке сделать ЛЕВОЕ СОЕДИНЕНИЕ первой таблицы со второй, мы ожидаем увидеть одну строку по сотруднику с присоединенными данными о днях. Однако, если во второй таблице (ВТ_Дни) по этому сотруднику найдется несколько записей (например, разные виды явок или просто разбивка по датам), то результат соединения увеличится кратно количеству этих записей.

Это классическая проблема соединения «один-ко-многим». Строка из левой таблицы дублируется для каждой подходящей строки из правой таблицы. Если после этого вы попытаетесь просуммировать начисления, итоговая сумма окажется завышенной.

Решение 1: Предварительная группировка и агрегация

Чтобы избежать дублей, нам необходимо обеспечить уникальность записей по ключевым полям связи (обычно это Сотрудник) в присоединяемой таблице. Рассмотрим, как это сделать на примере попытки развернуть данные по месяцам в колонки.

В исходной задаче использовалась конструкция ВЫБОР КОГДА... для распределения дней по колонкам, но без использования агрегатных функций. Это ошибка. Правильный подход — использовать функцию СУММА вместе с группировкой.

Давайте проанализируем, как должен выглядеть запрос для формирования правильной таблицы ВТ_Дни:


ВЫБРАТЬ
    ДанныеОбороты.Сотрудник КАК Сотрудник,
    // Агрегируем данные, чтобы получить одну строку на сотрудника
    СУММА(ВЫБОР
        КОГДА МЕСЯЦ(ДанныеОбороты.ПериодРегистрации) = МЕСЯЦ(&ДатаНачала)
        ТОГДА ЕСТЬNULL(ДанныеОбороты.ДниОборот, 0)
        ИНАЧЕ 0
    КОНЕЦ) КАК ОтработанныеДни_Месяц1,
    СУММА(ВЫБОР
        КОГДА МЕСЯЦ(ДанныеОбороты.ПериодРегистрации) = МЕСЯЦ(&ДатаОкончания)
        ТОГДА ЕСТЬNULL(ДанныеОбороты.ДниОборот, 0)
        ИНАЧЕ 0
    КОНЕЦ) КАК ОтработанныеДни_Месяц2
ПОМЕСТИТЬ ВТ_Дни
ИЗ
    РегистрНакопления.ДанныеТабельногоУчетаРабочегоВремениСотрудников.Обороты(...) КАК ДанныеОбороты

СГРУППИРОВАТЬ ПО
    ДанныеОбороты.Сотрудник

Что мы сделали:

  1. Обернули условия ВЫБОР КОГДА в функцию СУММА(). Это позволяет "схлопнуть" несколько строк с разными периодами в одну итоговую строку.
  2. Добавили секцию СГРУППИРОВАТЬ ПО. Мы группируем по полю Сотрудник. Это гарантирует, что в результате в таблице ВТ_Дни каждому сотруднику будет соответствовать ровно одна строка.

Теперь, когда мы будем делать левое соединение ВТ_Начисления с подготовленной ВТ_Дни, связь будет «один-к-одному», и дублирования сумм начислений не произойдет.

Решение 2: Оптимизация виртуальных таблиц

Второй важный аспект, который мы должны разобрать — это производительность запроса. Часто новички выбирают данные из виртуальной таблицы (например, Обороты), а затем фильтруют их с помощью условия ГДЕ.

Неправильный подход ("Красный свет"):


ВЫБРАТЬ
    ...
ИЗ
    РегистрНакопления.ДанныеТабельного.Обороты(,,,) КАК Обороты
ГДЕ
    Обороты.Сотрудник = &Сотрудник

Правильный подход ("Зеленый свет"):


ВЫБРАТЬ
    ...
ИЗ
    РегистрНакопления.ДанныеТабельного.Обороты(,,, Сотрудник = &Сотрудник) КАК Обороты

Почему это важно? Рассмотрим механику работы:

Используйте параметры виртуальных таблиц всегда, когда это возможно. Это не только ускоряет работу, но и необходимо для корректной работы механизма ограничения прав доступа (RLS).

Важные нюансы при работе с датами и NULL

При написании запросов также стоит обращать внимание на детали, которые могут привести к ошибкам в будущем.

1. Работа с функцией МЕСЯЦ()

В исходном примере использовалось условие МЕСЯЦ(Период) = МЕСЯЦ(&Дата). Это условие опасно, если ваш отчет строится на стыке лет. Например, январь 2023 и январь 2024 года вернут одинаковый номер месяца — 1. Чтобы избежать смешивания данных разных лет, лучше использовать функцию НАЧАЛОПЕРИОДА:


ВЫБОР
   КОГДА НАЧАЛОПЕРИОДА(ДанныеОбороты.ПериодРегистрации, МЕСЯЦ) = НАЧАЛОПЕРИОДА(&ДатаНачала, МЕСЯЦ)
   ...

2. Обработка NULL при соединении

Когда вы используете ЛЕВОЕ СОЕДИНЕНИЕ, данные из правой таблицы могут отсутствовать. В этом случае поля правой таблицы будут содержать значение NULL. Любая арифметическая операция с NULL (например, сложение) вернет NULL.

Поэтому в финальном запросе, где вы выбираете данные из соединенных таблиц, обязательно используйте функцию ЕСТЬNULL:


ВЫБРАТЬ
    ВТ_Начисления.Сотрудник,
    ВТ_Начисления.СуммаНачисления,
    ЕСТЬNULL(ВТ_Дни.ОтработанныеДни_Месяц1, 0) КАК ДниМесяц1
ИЗ
    ВТ_Начисления КАК ВТ_Начисления
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Дни КАК ВТ_Дни
        ПО ВТ_Начисления.Сотрудник = ВТ_Дни.Сотрудник

Подведем итоги

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

  1. Агрегация до соединения: Если одна из таблиц содержит детализацию, которая не нужна в финальной выборке, сгруппируйте её во временной таблице, используя СУММА() или МАКСИМУМ().
  2. Уникальность ключей: Убедитесь, что в присоединяемой таблице (при левом соединении) поля связи уникальны (например, только одна строка на одного Сотрудника).
  3. Оптимизация: Все отборы накладывайте внутри параметров виртуальных таблиц, а не в секции ГДЕ.
  4. Безопасность типов: Обрабатывайте результаты соединения функцией ЕСТЬNULL, чтобы избежать пустот в числовых полях.
← На главную