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