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