Как в 1С найти и пометить на удаление номенклатуру без остатков

Программист 1С v8.3 (Обычные формы) Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При оптимизации и очистке справочников в 1С (в частности, в конфигурации «Управление производственным предприятием») часто возникает задача: найти все позиции номенклатуры, по которым в текущий момент отсутствуют остатки на складах и счетах бухгалтерского учета, и установить на них пометку на удаление. Разберем по шагам, как правильно составить запрос к базе данных и какие архитектурные нюансы необходимо учесть при реализации такой обработки.

Анализ задачи и типичные ошибки выборки

Частой ошибкой начинающих разработчиков является попытка выбрать список номенклатуры напрямую из регистра остатков (например, РегистрНакопления.ТоварыНаСкладах.Остатки). В виртуальных таблицах остатков содержатся только те позиции, по которым есть ненулевой количественный или суммовой остаток на указанный момент времени. Если остаток товара равен нулю или движений по нему никогда не было, его просто не окажется в таблице остатков.

Поэтому исходной базой для выборки всегда должен выступать справочник Справочник.Номенклатура, к которому присоединяются данные из регистров остатков. Также важно учесть следующие архитектурные особенности конфигурации:

  1. Режим учета затрат (РАУЗ против партионного учета): Если в информационной базе включен режим расширенной аналитики учета затрат (РАУЗ), регистры партий (ПартииТоваровНаСкладах) не заполняются. В этом случае остатки себестоимости хранятся в регистрах УчетЗатрат и УчетЗатратРегл.
  2. Разнообразие счетов учета: Номенклатура используется не только на счете 41 («Товары»), но и на счетах 10 («Материалы»), 21 («Полуфабрикаты»), 43 («Готовая продукция»), а также забалансовых счетах переработки и ответственного хранения.
  3. Услуги и наборы: Позиции с признаком «Услуга» не имеют физических остатков. Если не исключить их из выборки, они будут ошибочно помечены на удаление.

Вариант 1. Быстрый запрос через вложенное условие

Рассмотрим базовый вариант запроса, который позволяет получить элементы справочника, отсутствующие в виртуальной таблице остатков склада. Этот способ подходит для небольших баз данных и оперативной проверки.


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    НЕ Номенклатура.ЭтоГруппа
    И НЕ Номенклатура.ПометкаУдаления
    И НЕ Номенклатура.Предопределенный
    И НЕ Номенклатура.Услуга
    И НЕ Номенклатура.Ссылка В
        (ВЫБРАТЬ
            Остатки.Номенклатура КАК Номенклатура
        ИЗ
            РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК Остатки
        ГДЕ
            Остатки.КоличествоОстаток > 0)

Важно: Конструкция ГДЕ НЕ Ссылка В (ВЫБРАТЬ ...) при работе с большими объемами данных (десятки и сотни тысяч позиций) создает существенную нагрузку на СУБД, так как подзапрос может приводить к неоптимальному плану выполнения.

Вариант 2. Оптимальный запрос с левым соединением и временными таблицами

Проанализируем более надежный и масштабируемый вариант. Мы соберем во временную таблицу все ненулевые остатки из складских регистров и плана счетов бухгалтерского учета, после чего свяжем ее со справочником через ЛЕВОЕ СОЕДИНЕНИЕ.


ВЫБРАТЬ
    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ТоварыНаСкладахОстатки
ГДЕ
    ТоварыНаСкладахОстатки.КоличествоОстаток > 0

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ХозрасчетныйОстатки.Субконто1 КАК Номенклатура
ИЗ
    РегистрБухгалтерии.Хозрасчетный.Остатки(
        &Период,
        Счет В ИЕРАРХИИ (&СчетаТМЦ),
        &ВидыСубконто,
        ) КАК ХозрасчетныйОстатки
ГДЕ
    ХозрасчетныйОстатки.КоличествоОстаток > 0
    ИЛИ ХозрасчетныйОстатки.СуммаОстаток > 0
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ВТ_Остатки.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_УникальныеОстатки
ИЗ
    ВТ_Остатки КАК ВТ_Остатки
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Номенклатура.Ссылка КАК НоменклатураСсылка
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_УникальныеОстатки КАК ВТ_УникальныеОстатки
        ПО Номенклатура.Ссылка = ВТ_УникальныеОстатки.Номенклатура
ГДЕ
    ВТ_УникальныеОстатки.Номенклатура ЕСТЬ NULL
    И НЕ Номенклатура.ЭтоГруппа
    И НЕ Номенклатура.ПометкаУдаления
    И НЕ Номенклатура.Предопределенный
    И НЕ Номенклатура.Услуга

Программная обработка и контроль ссылочной целостности

Посмотрим, как безопасно выполнить установку пометки на удаление в цикле. При массовой обработке данных рекомендуется выполнять запись порциями в транзакциях, чтобы не вызывать длительных блокировок таблиц базы данных.

Также важно помнить о контроле ссылочной целостности: если товар участвовал в движениях прошлых периодов, стандартная процедура удаления помеченных объектов не позволит физически удалить его из базы, пока на него ссылаются проведенные документы.


Запрос = Новый Запрос;
// Устанавливаем текст запроса из Варианта 2
Запрос.Текст = ПолучитьТекстЗапроса();
Запрос.УстановитьПараметр("Период", ТекущаяДата());

СчетаТМЦ = Новый Массив;
СчетаТМЦ.Добавить(ПланыСчетов.Хозрасчетный.Материалы);
СчетаТМЦ.Добавить(ПланыСчетов.Хозрасчетный.Товары);
СчетаТМЦ.Добавить(ПланыСчетов.Хозрасчетный.ГотоваяПродукция);
Запрос.УстановитьПараметр("СчетаТМЦ", СчетаТМЦ);

ВидыСубконто = Новый Массив;
ВидыСубконто.Добавить(ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Номенклатура);
Запрос.УстановитьПараметр("ВидыСубконто", ВидыСубконто);

Выборка = Запрос.Выполнить().Выбрать();

Счетчик = 0;
НачатьТранзакцию();

Пока Выборка.Следующий() Цикл
    Попытка
        ОбъектНоменклатура = Выборка.НоменклатураСсылка.ПолучитьОбъект();
        Если ОбъектНоменклатура <> Неопределено Тогда
            ОбъектНоменклатура.УстановитьПометкуУдаления(Истина);
            Счетчик = Счетчик + 1;
        КонецЕсли;
    Исключение
        ОтменитьТранзакцию();
        ЗаписьЖурналаРегистрации("ПометкаНоменклатуры", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
        Возврат;
    КонецПопытки;
    
    // Фиксируем порции по 500 элементов
    Если Счетчик % 500 = 0 Тогда
        ЗафиксироватьТранзакцию();
        НачатьТранзакцию();
    КонецЕсли;
КонецЦикла;

Если Счетчик % 500 <> 0 Тогда
    ЗафиксироватьТранзакцию();
КонецЕсли;

Если требуется скрыть неиспользуемую номенклатуру от пользователей, но сохранить возможность построения отчетов за прошлые периоды без конфликтов контроля ссылок, альтернативным решением является перенос найденных позиций в отдельную группу (например, «_Архив») с ограничением видимости в формах выбора.

← На главную