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

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

При разработке и доработке алгоритмов в 1С (особенно в конфигурациях для расчета зарплаты, таких как ЗУП 3.1, ERP или Комплексная автоматизация) программисты часто сталкиваются с задачей получения данных из документа и связанного с ним регистра. Частая проблема при этом — «слипание» строк, когда в результате запроса все сотрудники выводятся в одной строке, либо каждому сотруднику присваиваются показатели (например, проценты премии) всех остальных сотрудников.

Рассмотрим эту проблему подробнее, проанализируем причины возникновения дублей (декартова произведения) и разберем по шагам, как правильно соединять табличные части с регистрами сведений или накопления.

Проанализируем исходную ошибку

Представим ситуацию: нам необходимо получить список сотрудников из документа «Премия» и соответствующие им значения показателей из регистра сведений. Изначально разработчик пишет следующий запрос:


ВЫБРАТЬ 
    Премия.Ссылка КАК Ссылка, 
    Премия.Начисления.Сотрудник КАК Сотрудник, 
    ЗначенияПоказателейНачислений.Регистратор.Ссылка КАК РегистраторСсылка, 
    ЗначенияПоказателейНачислений.Значение КАК Значение, 
    ЗначенияПоказателейНачислений.Показатель КАК Показатель 
ИЗ 
    Документ.Премия КАК Премия 
ЛЕВОЕ СОЕДИНЕНИЕ 
    РегистрСведений.ЗначенияПоказателейНачислений КАК ЗначенияПоказателейНачислений 
ПО 
    (Премия.Ссылка = ЗначенияПоказателейНачислений.Регистратор.Ссылка)

При выполнении этого кода мы видим, что данные искажаются: происходит присвоение процента премии от одних сотрудников другим. Выясним причину такого поведения платформы.

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

Зачем нужен Идентификатор строки?

В современных архитектурах конфигураций 1С данные одного начисления разделены. Факт начисления и привязка к сотруднику хранятся в табличной части Начисления, а вот значения показателей для расчета формулы лежат в отдельном регистре сведений (например, ЗначенияПоказателейНачислений).

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

Посмотрим на пример правильного соединения таблиц. Использование идентификатора гарантирует строгое соответствие «один к одному» (или «один ко многим», если у одного начисления несколько разных показателей).

Рассмотрим правильное решение задачи

Чтобы избавиться от дублей, нам необходимо выполнить два шага:

  1. Обращаться не к таблице самого документа, а к физической таблице его табличной части (Документ.Премия.Начисления).
  2. В условии соединения (секция ПО) добавить связь не только по регистратору, но и по идентификатору строки.

Разберем исправленный вариант кода:


ВЫБРАТЬ 
    ПремияНачисления.Сотрудник КАК Сотрудник, 
    ПремияНачисления.Ссылка КАК Ссылка, 
    ЗначенияПоказателейНачислений.Регистратор КАК РегистраторСсылка, 
    ЗначенияПоказателейНачислений.Значение КАК Значение, 
    ЗначенияПоказателейНачислений.Показатель КАК Показатель 
ИЗ 
    Документ.Премия.Начисления КАК ПремияНачисления 
ВНУТРЕННЕЕ СОЕДИНЕНИЕ 
    РегистрСведений.ЗначенияПоказателейНачислений КАК ЗначенияПоказателейНачислений 
ПО 
    (ПремияНачисления.Ссылка = ЗначенияПоказателейНачислений.Регистратор)
    И (ПремияНачисления.ИдентификаторСтрокиВидаРасчета = ЗначенияПоказателейНачислений.ИдентификаторСтроки)

В этом варианте мы напрямую читаем данные из Документ.Премия.Начисления. Это значительно повышает читаемость кода и его производительность. А добавление условия связи по идентификатору полностью решает проблему перепутанных процентов и дублей.

Внутреннее или Левое соединение: что выбрать?

В приведенном выше правильном примере используется ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Однако, разберем важный нюанс, который необходимо учитывать при выборе типа соединения в 1С.

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

Если наша задача — вывести абсолютно всех сотрудников из табличной части документа (даже тех, у кого нет показателей), нам следует использовать ЛЕВОЕ СОЕДИНЕНИЕ:


ВЫБРАТЬ 
    ПремияНачисления.Сотрудник КАК Сотрудник, 
    ПремияНачисления.Ссылка КАК Ссылка, 
    ЗначенияПоказателейНачислений.Значение КАК Значение, 
    ЗначенияПоказателейНачислений.Показатель КАК Показатель 
ИЗ 
    Документ.Премия.Начисления КАК ПремияНачисления 
ЛЕВОЕ СОЕДИНЕНИЕ 
    РегистрСведений.ЗначенияПоказателейНачислений КАК ЗначенияПоказателейНачислений 
ПО 
    (ПремияНачисления.Ссылка = ЗначенияПоказателейНачислений.Регистратор)
    И (ПремияНачисления.ИдентификаторСтрокиВидаРасчета = ЗначенияПоказателейНачислений.ИдентификаторСтроки)

Почему недостаточно связать только по Сотруднику?

Иногда программисты пытаются исправить запрос, добавляя в условие связи поле Сотрудник вместо идентификатора строки. Разберем, почему это является ошибкой.

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

Правила хорошего тона при написании таких запросов

Подведем итог и выделим основные правила, которые мы рекомендуем соблюдать:

  1. Избегайте обращений через точку к табличным частям: Если вам нужны данные из строк, делайте выборку непосредственно из таблицы Документ.ИмяДокумента.ИмяТабличнойЧасти.
  2. Связывайте данные правильно: При соединении табличных частей с регистрами всегда ищите поля-идентификаторы (ИдентификаторСтроки, КлючСвязи, КлючСтроки и т.д.).
  3. Осознанно выбирайте тип соединения: Помните про разницу между левым и внутренним соединением. Если вы хотите сохранить базовые строки при отсутствии движений — применяйте левое соединение.
← На главную