При разработке отчетов по истории начислений в 1С программисты часто сталкиваются с проблемой "размножения" строк. Это происходит, когда текущая запись регистра сведений соединяется не с одной следующей по хронологии записью, а со всеми последующими записями сразу. В результате вместо четкой цепочки изменений (например, было 10 000, стало 15 000) мы получаем избыточный набор данных, где первая зарплата сопоставляется со всеми изменениями за несколько лет.
В этой статье мы подробно разберем механизм построения корректного запроса, который позволит сопоставить текущее состояние начисления с непосредственно следующим за ним, исключив повторения и сохранив строгую хронологию.
Проанализируем ситуацию на примере кода из обсуждения. Типичная ошибка заключается в использовании условия соединения ВТПланТекущая.Период < ПлановыеНачисленияРаботников.Период. Без дополнительных ограничений это условие заставляет систему соединять запись от 2017 года с записями за 2018, 2019 и все последующие года. Чтобы получить только одно, следующее изменение, нам нужно найти минимальную дату среди всех дат, которые больше текущей.
Рассмотрим самый надежный и универсальный способ решения этой задачи, который работает на любых версиях платформы 1С 8. Этот метод заключается в последовательном формировании структуры данных.
Шаг 1: Подготовка исходных данных. Сначала мы выбираем все значимые записи из регистра РегистрСведений.ПлановыеНачисленияРаботников во временную таблицу. Это позволит нам обращаться к данным несколько раз без повторного сканирования всего регистра.
ВЫБРАТЬ
ПлановыеНачисления.Период КАК Период,
ПлановыеНачисления.ФизЛицо КАК ФизЛицо,
ПлановыеНачисления.ВидРасчета КАК ВидРасчета,
ПлановыеНачисления.Показатель1 КАК Результат,
ПлановыеНачисления.Регистратор КАК Документ
ПОМЕСТИТЬ ВТ_ВсеЗаписи
ИЗ
РегистрСведений.ПлановыеНачисленияРаботников КАК ПлановыеНачисления
ГДЕ
ПлановыеНачисления.Период МЕЖДУ &НачалоПериода И &КонецПериода
;
Шаг 2: Поиск ближайшего периода изменения. Теперь проанализируем ситуацию и найдем для каждой записи дату "следующего" события. Для этого мы соединяем таблицу саму с собой и используем агрегатную функцию МИНИМУМ.
ВЫБРАТЬ
Т1.ФизЛицо,
Т1.ВидРасчета,
Т1.Период КАК ПериодСтарый,
МИНИМУМ(Т2.Период) КАК ПериодНовый
ПОМЕСТИТЬ ВТ_СтыкПериодов
ИЗ
ВТ_ВсеЗаписи КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ВсеЗаписи КАК Т2
ПО Т1.ФизЛицо = Т2.ФизЛицо
И Т1.ВидРасчета = Т2.ВидРасчета
И Т1.Период < Т2.Период
И Т1.Результат <> Т2.Результат
СГРУППИРОВАТЬ ПО
Т1.ФизЛицо,
Т1.ВидРасчета,
Т1.Период
;
Важный нюанс: условие Т1.Результат <> Т2.Результат позволяет нам игнорировать кадровые документы, которые не изменили сумму начисления (например, перевод в другой отдел без смены оклада), если это требуется по условию задачи.
Шаг 3: Соединение данных для итогового результата. Выясним окончательные значения, соединив найденные пары периодов с исходной таблицей записей дважды: один раз для получения данных "До", второй — для данных "После".
ВЫБРАТЬ
Стык.ФизЛицо,
Стык.ВидРасчета,
Стык.ПериодСтарый,
ДанныеСтарые.Документ КАК ДокументСтарый,
ДанныеСтарые.Результат КАК РезультатСтарый,
Стык.ПериодНовый,
ДанныеНовые.Документ КАК ДокументНовый,
ДанныеНовые.Результат КАК РезультатНовый
ИЗ
ВТ_СтыкПериодов КАК Стык
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ВсеЗаписи КАК ДанныеСтарые
ПО Стык.ФизЛицо = ДанныеСтарые.ФизЛицо
И Стык.ВидРасчета = ДанныеСтарые.ВидРасчета
И Стык.ПериодСтарый = ДанныеСтарые.Период
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ВсеЗаписи КАК ДанныеНовые
ПО Стык.ФизЛицо = ДанныеНовые.ФизЛицо
И Стык.ВидРасчета = ДанныеНовые.ВидРасчета
И Стык.ПериодНовый = ДанныеНовые.Период
Если ваша база работает на современной версии платформы (8.3.20 и выше), мы можем значительно упростить запрос, используя оконную функцию LEAD. Она позволяет "заглянуть" в следующую строку результата без сложных соединений.
Рассмотрим пример такого подхода:
ВЫБРАТЬ
ПлановыеНачисления.ФизЛицо,
ПлановыеНачисления.ВидРасчета,
ПлановыеНачисления.Период КАК ПериодСтарый,
ПлановыеНачисления.Показатель1 КАК РезультатСтарый,
LEAD(ПлановыеНачисления.Период) ОБРАБОТКА ПОРЯДКА (ПлановыеНачисления.Период) КАК ПериодНовый,
LEAD(ПлановыеНачисления.Показатель1) ОБРАБОТКА ПОРЯДКА (ПлановыеНачисления.Период) КАК РезультатНовый
ИЗ
РегистрСведений.ПлановыеНачисленияРаботников КАК ПлановыеНачисления
ГДЕ
ПлановыеНачисления.Период МЕЖДУ &НачалоПериода И &КонецПериода
Этот метод работает в разы быстрее на больших объемах данных, так как СУБД выполняет обход таблицы один раз, сразу вычисляя значения для следующего периода.
При разработке подобных запросов в 1С:ЗУП следует учитывать несколько важных моментов:
Периоду может выдать неверный результат. Проанализируем ситуацию: в таких случаях лучше использовать МоментВремени или добавлять в условия соединения сравнение по ссылке Регистратор.ЛЕВОЕ СОЕДИНЕНИЕ при поиске нового значения.СрезПоследних с условием на период, но для построения полной истории за большой интервал времени (как в нашем случае) описанный выше метод с временными таблицами остается стандартом.Таким образом, для корректного решения задачи мы должны сначала четко определить пары периодов (текущий и ближайший следующий), и только после этого "навешивать" на них суммы и документы. Это гарантирует отсутствие дублей и правильную последовательность строк в вашем отчете.