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