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