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