Как заполнить табличное поле документа из регистра сведений «Срез последних»?

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

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

Разбор логики работы виртуальной таблицы «Срез последних»

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

Рассмотрим ситуацию: в нашем регистре «ЦеныЯндексМаркета» измерениями являются Номенклатура и Ссылка (ссылка на страницу товара). Если для одного товара в регистре сохранены записи с разными ссылками, СрезПоследних вернет актуальную запись для каждой ссылки в отдельности. В результате, при попытке найти строку через НайтиСтроки() в таблице результата запроса, мы обнаружим несколько записей, и код НСтроки[0] возьмет первую попавшуюся, которая не обязательно будет самой свежей по времени.

Анализ структуры измерений и ресурсов

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

  1. Измерения: Должны содержать только те данные, которые определяют уникальность записи (например, Номенклатура, Склад, Характеристика).
  2. Ресурсы: Сюда следует выносить показатели, которые меняются во времени (Цена, КоличествоКликов).
  3. Реквизиты: Сюда выносим справочную информацию, например ту самую Ссылку на Яндекс.Маркет, если она носит информационный характер и не должна влиять на срез.

Важно: Если структуру регистра менять нельзя, то в запросе необходимо использовать вложенные запросы с группировкой по максимальному периоду, чтобы программно «схлопнуть» данные до одной записи на товар.

Правильное использование параметра «Период»

Проанализируем пример кода, где используется ТекущаяДата(). Для документов это не совсем корректно. Если пользователь решит перепровести документ за прошлый месяц, использование ТекущаяДата() подтянет в него сегодняшние цены, что исказит финансовую отчетность. Рассмотрим правильный подход:

При работе с документами всегда следует передавать МоментВремени() конкретного объекта. Это гарантирует, что запрос «увидит» данные регистра именно на ту секунду и позицию в базе, когда был создан документ.


// Пример передачи правильного периода в запрос
Запрос.УстановитьПараметр("Период", ЭтотОбъект.МоментВремени());

Оптимизация запроса: от циклов к временным таблицам

В исходном примере используется метод ВыгрузитьКолонку("Номенклатура") и передача массива в параметр В (&Номенклатура). Разберем, почему это может стать проблемой. Если в табличной части документа будет 5000 строк, формирование такого длинного условия в SQL-запросе существенно замедлит работу. Рассмотрим более профессиональный метод с использованием временных таблиц:

  1. Выгружаем табличную часть документа в ТаблицаЗначений.
  2. Передаем эту таблицу в запрос как параметр.
  3. Помещаем данные во временную таблицу (ВТ).
  4. Выполняем ЛЕВОЕ СОЕДИНЕНИЕ ВТ с виртуальной таблицей СрезПоследних.

Такой подход позволяет избежать циклов Для Каждого в коде 1С, так как вся логика сопоставления данных отрабатывает на стороне СУБД.

Реализация эффективного заполнения (Пример кода)

Посмотрим на пример кода, который объединяет в себе исправление ошибок и оптимизацию производительности. Мы будем использовать виртуальную таблицу с отбором и соединение.


Запрос = Новый Запрос;
// Используем временную таблицу для ускорения работы с большим количеством строк
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

// Шаг 1: Помещаем номенклатуру из документа во временную таблицу
ТаблицаТоваров = ТаблицаЦен.Выгрузить( , "Номенклатура");
Запрос.Текст = 
"ВЫБРАТЬ
|	ТЗ.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
|	&ТаблицаТоваров КАК ТЗ
|ИНДЕКСИРОВАТЬ ПО
|	Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ВТ_Товары.Номенклатура КАК Номенклатура,
|	ЕСТЬNULL(Срез.Цена, 0) КАК Цена,
|	ЕСТЬNULL(Срез.Ссылка, """") КАК СсылкаНаМаркет,
|	ЕСТЬNULL(Срез.Количество_кликов, 0) КАК КоличествоКликов
|ИЗ
|	ВТ_Товары КАК ВТ_Товары
|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныЯндексМаркета.СрезПоследних(&Период, 
|			НоменклатураСправочник В (ВЫБРАТЬ ВТ_Товары.Номенклатура ИЗ ВТ_Товары)) КАК Срез
|		ПО ВТ_Товары.Номенклатура = Срез.НоменклатураСправочник";

Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
// Если заполняем в документе, используем МоментВремени, иначе - ТекущаяДата
Запрос.УстановитьПараметр("Период", ТекущаяДата()); 

РезультатЗапроса = Запрос.Выполнить();
ТаблицаРезультата = РезультатЗапроса.Выгрузить();

// Шаг 2: Эффективное заполнение строк документа
Для Каждого СтрДокумента Из ТаблицаЦен Цикл
    СтруктураПоиска = Новый Структура("Номенклатура", СтрДокумента.Номенклатура);
    НайденныеСтроки = ТаблицаРезультата.НайтиСтроки(СтруктураПоиска);
    
    Если НайденныеСтроки.Количество() > 0 Тогда
        ЗаполнитьЗначенияСвойств(СтрДокумента, НайденныеСтроки[0]);
    КонецЕсли;
КонецЦикла;

Подведение итогов и важные замечания

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

Также стоит помнить о функции ЕСТЬNULL(). При левом соединении, если по какой-то номенклатуре в регистре нет данных, запрос вернет NULL. Без обработки это может привести к ошибкам при записи данных в колонки с числовыми или строковыми типами. Всегда подставляйте значения по умолчанию.

Таким образом, для решения проблемы «первых попавшихся» значений необходимо:

← На главную