В практике разработчика на платформе 1С:Предприятие 7.7 часто возникает задача построения отчета «от обратного». Например, когда необходимо вывести список товаров, которые ни разу не продавались за период или вообще не фигурировали в определенных типах документов. Стандартные механизмы запросов 7.7 заточены на поиск существующих данных, поэтому задача «найти то, чего нет» требует особого подхода. Рассмотрим подробно, как реализовать такой алгоритм, используя различные методы: от штатных запросов до прямой работы с базой данных.
Это наиболее распространенный и понятный способ, который подходит для большинства конфигураций. Суть метода заключается в двух этапах: сначала мы собираем всю «использованную» номенклатуру, а затем исключаем ее из общего списка при формировании отчета. Проанализируем этот процесс пошагово.
Для начала нам необходимо собрать все уникальные ссылки на товары, которые присутствуют в табличных частях нужного нам вида документа. Мы воспользуемся объектом Запрос, так как он работает быстрее, чем простой перебор документов в цикле.
Рассмотрим пример кода для сбора данных:
// Создаем список для хранения найденных товаров
СписокИспользованныхТоваров = СоздатьОбъект("СписокЗначений");
ТекстЗапроса = "
|Период с ВыбДатаНач по ВыбДатаКон;
|Товар = Документ.Реализация.Товар;
|Группировка Товар без групп;
|";
Запрос = СоздатьОбъект("Запрос");
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
Пока Запрос.Группировка(1) = 1 Цикл
// Добавляем только конкретные элементы номенклатуры
СписокИспользованныхТоваров.ДобавитьЗначение(Запрос.Товар);
КонецЦикла;
КонецЕсли;
После того как мы получили СписокИспользованныхТоваров, перейдем ко второму этапу — получению списка всей номенклатуры (или выбранной группы), из которой нужно исключить найденные позиции. Разберем, как применить условие отрицания в новом запросе:
ТекстЗапросаОстатки = "
|Товар = Справочник.Номенклатура.ТекущийЭлемент;
|Группировка Товар без групп;
|Условие (НЕ(Товар В СписокИспользованныхТоваров));
|Условие (Товар.ЭтоГруппа = 0);
|";
// Если пользователь выбрал конкретную группу на форме, добавим условие вхождения
Если ВыбГруппа.Выбран() = 1 Тогда
ТекстЗапросаОстатки = ТекстЗапросаОстатки + "Условие (Товар В ВыбГруппа);";
КонецЕсли;
Важный нюанс: в 1С 7.7 оператор В внутри условия запроса отлично работает со списками значений. Однако стоит помнить, что если список будет содержать десятки тысяч элементов, производительность «черного запроса» может существенно снизиться.
Если документов очень много и СписокЗначений получается избыточно большим, стандартный запрос с условием НЕ (Товар В Списке) может работать крайне медленно. В этом случае мы можем применить программный фильтр с использованием объекта ТаблицаЗначений. Выясним, почему этот способ иногда эффективнее.
Алгоритм действий будет следующим:
Свернуть, чтобы остались только уникальные идентификаторы товаров.НайтиЗначение. Если значение найдено — удаляем строку из ТЗ1.Проанализируем пример реализации этого метода:
// 1. Получаем всю номенклатуру
ТЗ_Все = СоздатьОбъект("ТаблицаЗначений");
СпрН = СоздатьОбъект("Справочник.Номенклатура");
СпрН.ИспользоватьРодителя(ВыбГруппа);
СпрН.ВыбратьЭлементы();
Пока СпрН.ПолучитьЭлемент() = 1 Цикл
Если СпрН.ЭтоГруппа = 1 Тогда Продолжить; КонецЕсли;
ТЗ_Все.НоваяСтрока();
ТЗ_Все.Товар = СпрН.ТекущийЭлемент();
КонецЦикла;
// 2. Получаем использованную номенклатуру (из запроса первого метода)
// Предположим, ТЗ_Использовано уже заполнена и свернута.
// 3. Фильтруем
Кол = ТЗ_Все.КоличествоСтрок();
Пока Кол > 0 Цикл
ТЗ_Все.ПолучитьСтрокуПоНомеру(Кол);
Если ТЗ_Использовано.НайтиЗначение(ТЗ_Все.Товар, , "Товар") > 0 Тогда
ТЗ_Все.УдалитьСтроку(Кол);
КонецЕсли;
Кол = Кол - 1;
КонецЦикла;
Этот метод работает быстрее на файловых базах за счет того, что НайтиЗначение в оперативной памяти выполняется мгновенно по сравнению с тяжелыми SQL-подобными фильтрами в штатном движке запросов 7.7.
Рассмотрим более продвинутый уровень, упомянутый опытными разработчиками. В файловой версии 1С 7.7 данные хранятся в формате DBF. Если нам нужно обработать колоссальный объем документов, штатный объект Документ или Запрос накладными расходами на создание объектов может замедлить процесс. Мы можем обратиться к файлу табличной части напрямую.
Посмотрим на последовательность действий:
1cv7.dd в корне каталога базы данных.DT[номер].dbf).SP123).XBase для быстрого перебора этого файла и сбора ID товаров.Такой подход позволяет прочитать миллионы строк за считанные секунды, так как 1С не тратит ресурсы на контроль прав, проверку ссылочной целостности и создание контекста объектов.
Если ваша база работает на Microsoft SQL Server, лучшим решением будет использование внешней компоненты 1С++. Это позволяет выполнить операцию исключения на стороне сервера. Разберем, как выглядит логика запроса на языке SQL.
Вместо перебора данных в 1С, мы просим SQL Server вернуть нам разницу между двумя наборами данных, используя оператор EXCEPT или конструкцию NOT IN (или NOT EXISTS).
// Пример логики SQL запроса (упрощенно)
ТекстЗапроса = "
|SELECT id as [Товар $Справочник.Номенклатура]
|FROM $Справочник.Номенклатура
|WHERE isfolder = 0
|EXCEPT
|SELECT $ДокС.Товар
|FROM $ДокументСтроки.Реализация as ДокС
|";
Использование прямого SQL-запроса — это «золотой стандарт» для крупных систем на базе 7.7. Весь расчет происходит на сервере, и в 1С возвращается уже готовый, отфильтрованный список номенклатуры.
При любом из выбранных методов необходимо учитывать несколько критических моментов, чтобы отчет выдавал корректные данные:
Условие(Запрос.Документ.Проведен() = 1).ЭтоГруппа = 0 или проверку на уровень иерархии.Товар = Документ.Реализация.Товар.Библиотека.Номенклатура.Подводя итог, можно сказать, что для небольших баз идеально подходит Метод 1 с использованием СписокЗначений. Для средних и высоконагруженных систем лучше ориентироваться на программную обработку Таблиц Значений или прямые SQL-запросы. Выбор конкретного инструмента зависит от ваших требований к скорости и технических особенностей инсталляции 1С.