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