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