При разработке и доработке алгоритмов в 1С (особенно в конфигурациях для расчета зарплаты, таких как ЗУП 3.1, ERP или Комплексная автоматизация) программисты часто сталкиваются с задачей получения данных из документа и связанного с ним регистра. Частая проблема при этом — «слипание» строк, когда в результате запроса все сотрудники выводятся в одной строке, либо каждому сотруднику присваиваются показатели (например, проценты премии) всех остальных сотрудников.
Рассмотрим эту проблему подробнее, проанализируем причины возникновения дублей (декартова произведения) и разберем по шагам, как правильно соединять табличные части с регистрами сведений или накопления.
Представим ситуацию: нам необходимо получить список сотрудников из документа «Премия» и соответствующие им значения показателей из регистра сведений. Изначально разработчик пишет следующий запрос:
ВЫБРАТЬ
Премия.Ссылка КАК Ссылка,
Премия.Начисления.Сотрудник КАК Сотрудник,
ЗначенияПоказателейНачислений.Регистратор.Ссылка КАК РегистраторСсылка,
ЗначенияПоказателейНачислений.Значение КАК Значение,
ЗначенияПоказателейНачислений.Показатель КАК Показатель
ИЗ
Документ.Премия КАК Премия
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрСведений.ЗначенияПоказателейНачислений КАК ЗначенияПоказателейНачислений
ПО
(Премия.Ссылка = ЗначенияПоказателейНачислений.Регистратор.Ссылка)
При выполнении этого кода мы видим, что данные искажаются: происходит присвоение процента премии от одних сотрудников другим. Выясним причину такого поведения платформы.
Премия.Начисления.Сотрудник. В языке запросов 1С это приводит к неявному левому соединению основной таблицы документа с ее табличной частью.Премия.Ссылка = ЗначенияПоказателейНачислений.Регистратор). Система берет документ, неявно присоединяет к нему все строки табличной части «Начисления», а затем к каждой этой строке присоединяет все записи регистра по этому документу. Возникает классическое декартово произведение таблиц.В современных архитектурах конфигураций 1С данные одного начисления разделены. Факт начисления и привязка к сотруднику хранятся в табличной части Начисления, а вот значения показателей для расчета формулы лежат в отдельном регистре сведений (например, ЗначенияПоказателейНачислений).
Чтобы платформа понимала, к какой именно строке табличной части относится конкретная запись в регистре, используется специальное связующее поле. В табличной части документа оно обычно называется ИдентификаторСтрокиВидаРасчета, а в регистре — ИдентификаторСтроки.
Посмотрим на пример правильного соединения таблиц. Использование идентификатора гарантирует строгое соответствие «один к одному» (или «один ко многим», если у одного начисления несколько разных показателей).
Чтобы избавиться от дублей, нам необходимо выполнить два шага:
Документ.Премия.Начисления).ПО) добавить связь не только по регистратору, но и по идентификатору строки.Разберем исправленный вариант кода:
ВЫБРАТЬ
ПремияНачисления.Сотрудник КАК Сотрудник,
ПремияНачисления.Ссылка КАК Ссылка,
ЗначенияПоказателейНачислений.Регистратор КАК РегистраторСсылка,
ЗначенияПоказателейНачислений.Значение КАК Значение,
ЗначенияПоказателейНачислений.Показатель КАК Показатель
ИЗ
Документ.Премия.Начисления КАК ПремияНачисления
ВНУТРЕННЕЕ СОЕДИНЕНИЕ
РегистрСведений.ЗначенияПоказателейНачислений КАК ЗначенияПоказателейНачислений
ПО
(ПремияНачисления.Ссылка = ЗначенияПоказателейНачислений.Регистратор)
И (ПремияНачисления.ИдентификаторСтрокиВидаРасчета = ЗначенияПоказателейНачислений.ИдентификаторСтроки)
В этом варианте мы напрямую читаем данные из Документ.Премия.Начисления. Это значительно повышает читаемость кода и его производительность. А добавление условия связи по идентификатору полностью решает проблему перепутанных процентов и дублей.
В приведенном выше правильном примере используется ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Однако, разберем важный нюанс, который необходимо учитывать при выборе типа соединения в 1С.
Если в документе есть сотрудники, для которых формула начисления не требует ввода показателей (или они были оставлены пустыми), то в регистре ЗначенияПоказателейНачислений записей по ним не будет. При использовании внутреннего соединения строки с такими сотрудниками полностью исчезнут из результата запроса, так как для них не найдется пары в правой таблице.
Если наша задача — вывести абсолютно всех сотрудников из табличной части документа (даже тех, у кого нет показателей), нам следует использовать ЛЕВОЕ СОЕДИНЕНИЕ:
ВЫБРАТЬ
ПремияНачисления.Сотрудник КАК Сотрудник,
ПремияНачисления.Ссылка КАК Ссылка,
ЗначенияПоказателейНачислений.Значение КАК Значение,
ЗначенияПоказателейНачислений.Показатель КАК Показатель
ИЗ
Документ.Премия.Начисления КАК ПремияНачисления
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрСведений.ЗначенияПоказателейНачислений КАК ЗначенияПоказателейНачислений
ПО
(ПремияНачисления.Ссылка = ЗначенияПоказателейНачислений.Регистратор)
И (ПремияНачисления.ИдентификаторСтрокиВидаРасчета = ЗначенияПоказателейНачислений.ИдентификаторСтроки)
Иногда программисты пытаются исправить запрос, добавляя в условие связи поле Сотрудник вместо идентификатора строки. Разберем, почему это является ошибкой.
Один и тот же сотрудник может фигурировать в табличной части документа несколько раз. Например, ему могут назначить разные виды премий в одном документе, либо начисления по разным подразделениям. Если мы свяжем таблицы только по сотруднику и регистратору, то система снова размножит строки: каждая запись из регистра для этого сотрудника соединится со всеми строками этого же сотрудника в табличной части документа. Именно поэтому уникальный технический ключ (идентификатор строки) является единственным надежным способом связать табличную часть и движения.
Подведем итог и выделим основные правила, которые мы рекомендуем соблюдать:
Документ.ИмяДокумента.ИмяТабличнойЧасти.ИдентификаторСтроки, КлючСвязи, КлючСтроки и т.д.).