Как составить запрос для поиска неликвидов (товаров без продаж) в 1С?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

Контроль залежавшихся остатков и своевременное выявление неликвидных товаров — одна из ключевых задач складского и финансового учета в любой торговой компании. На практике программистам 1С часто поступает задача: «Вывести список товаров, которые поступили на склад в определенный период (например, в прошлом году), но с тех пор не продавались и продолжают лежать мертвым грузом».

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

Критический разбор ошибок и упущенных нюансов в теме форума

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

1. Использование неподходящего регистра накопления
В первом сообщении автор пытается построить запрос по регистру СвободныеОстатки. Однако далее (в сообщении 13) он уточняет, что ему необходимы данные не только по количеству, но и по себестоимости (цене прихода). Ресурсы регистра СвободныеОстатки содержат исключительно количественные данные. В нем физически нет информации о стоимости товаров. Для получения стоимостной оценки остатков необходимо использовать другие регистры, например ПартииТоваровНаСкладах (в старых конфигурациях) или СебестоимостьТоваров (в современных ERP-системах).

2. Логическое противоречие при указании периодов
Участники форума пытаются ограничить виртуальную таблицу остатков и оборотов единым периодом &НачалоПериода и &КонецПериода. Если установить этот период равным первому кварталу прошлого года, запрос выберет товары, которые не продавались внутри этого же первого квартала. Но этот товар мог быть успешно распродан позже! Чтобы корректно выявить неликвид, нам необходимы два разных периода: период закупки (когда товар пришел на склад) и период анализа продаж (который должен длиться с момента закупки по сегодняшний день).

3. Попытка исключить продажи только по одному типу документа
Рекомендация использовать фильтр по регистратору РеализацияТоваровУслуг ошибочна. Списание и продажа товаров со склада могут осуществляться множеством других документов: ОтчетОРозничныхПродажах, СписаниеТоваров, ПередачаТоваровМеждуОрганизациями (в схемах Интеркампани) и т.д. Если исключить только один документ реализации, в отчет попадут товары, которые на самом деле были проданы через розницу или списаны по другим причинам.

4. Использование тяжелых конструкций с оператором НЕ В
В сообщениях 16 и 18 предлагается использовать подзапрос в параметрах виртуальной таблицы с оператором НЕ В. С точки зрения производительности СУБД (особенно на крупных базах данных MS SQL Server или PostgreSQL), использование НЕ В с подзапросом, содержащим периодичность Регистратор, — это критическая ошибка. Такой запрос приведет к полному сканированию таблиц (Table Scan), вызовет избыточную нагрузку на сервер и может привести к зависанию системы.

Оптимальный архитектурный паттерн: Временные таблицы и Левое соединение

Для быстрого и корректного решения этой задачи мы применим проверенную методику пакетирования запросов с использованием временных таблиц. Наш алгоритм будет состоять из трех последовательных шагов:

  1. Получение остатков и приходов за целевой период. Мы выберем товары, по которым были поступления в интересующий нас период и которые до сих пор физически лежат на складе (конечный остаток больше нуля). Результат поместим во временную таблицу.
  2. Получение списка всех проданных товаров. Мы соберем всю номенклатуру, по которой были зафиксированы продажи за весь анализируемый период (с даты закупки по текущий момент). Этот список мы также сохраним во временной таблице.
  3. Соединение таблиц и отбор неликвидов. Мы выполним левое соединение таблицы остатков с таблицей продаж и установим условие, что данные о продажах отсутствуют (значение поля равно NULL). Это позволит нам мгновенно отсечь все позиции, по которым было хоть какое-то движение.

Рассмотрим подробнее практическую реализацию этого алгоритма на примере регистра накопления ПартииТоваровНаСкладах.


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

////////////////////////////////////////////////////////////////////////////////
// ШАГ 2: Собираем список всех проданных товаров за весь период анализа
ВЫБРАТЬ РАЗЛИЧНЫЕ
	ПродажиОбороты.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_ПроданныеТовары
ИЗ
	РегистрНакопления.Продажи.Обороты(&НачалоПериодаЗакупки, &ТекущаяДата, , ) КАК ПродажиОбороты
;

////////////////////////////////////////////////////////////////////////////////
// ШАГ 3: Левым соединением находим товары без продаж и рассчитываем цену
ВЫБРАТЬ
	ВТ_ОстаткиИПриход.Номенклатура КАК Номенклатура,
	ВТ_ОстаткиИПриход.ОстатокКоличество КАК ОстатокКоличество,
	ВТ_ОстаткиИПриход.ОстатокСебестоимость КАК ОстатокСебестоимость,
	ВЫБОР
		КОГДА ВТ_ОстаткиИПриход.ОстатокКоличество > 0
			ТОГДА ВТ_ОстаткиИПриход.ОстатокСебестоимость / ВТ_ОстаткиИПриход.ОстатокКоличество
		ИНАЧЕ 0
	КОНЕЦ КАК ЦенаЗакупки
ИЗ
	ВТ_ОстаткиИПриход КАК ВТ_ОстаткиИПриход
		ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПроданныеТовары КАК ВТ_ПроданныеТовары
		ПО ВТ_ОстаткиИПриход.Номенклатура = ВТ_ПроданныеТовары.Номенклатура
ГДЕ
	ВТ_ПроданныеТовары.Номенклатура ЕСТЬ NULL

Разберем, почему данный подход эффективен:

Альтернативный вариант: Использование вложенного запроса в параметрах виртуальной таблицы

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


ВЫБРАТЬ РАЗЛИЧНЫЕ
	ПартииОбороты.Номенклатура КАК Номенклатура
ИЗ
	РегистрНакопления.ПартииТоваровНаСкладах.ОстаткиИОбороты(
		&НачалоПериодаЗакупки, 
		&КонецПериодаЗакупки, 
		, 
		, 
		НЕ Номенклатура В (
			ВЫБРАТЬ РАЗЛИЧНЫЕ 
				Продажи.Номенклатура 
			ИЗ 
				РегистрНакопления.Продажи.Обороты(&НачалоПериодаЗакупки, &КонецПериодаЗакупки) КАК Продажи
		)
	) КАК ПартииОбороты

Обратим внимание на особенности этого решения:

Бизнес-логика: как неликвиды анализируются в реальной практике

В реальном бизнесе жесткий критерий «вообще не было движений» используется редко, так как он не дает полной картины затоваренности. Профессиональные аналитики и категорийные менеджеры применяют более гибкие показатели:

1. Расчет показателя «Дней без движения»
Вместо жесткой привязки к дате закупки, для каждого товара на складе определяется дата его последней продажи. Количество дней, прошедших с даты последней продажи до текущего дня, и есть показатель залежалости. Это позволяет ранжировать товары по степени их «зависания».

2. Оценка оборачиваемости запасов в днях
Классический показатель, который рассчитывается как отношение среднего остатка за период к объему продаж за этот же период, умноженное на количество дней. Если оборачиваемость позиции превышает норматив (например, 90 или 120 дней), товар признается неликвидным, даже если редкие продажи по нему все-таки происходят.

3. Использование типовых механизмов 1С
Прежде чем приступать к написанию собственного сложного запроса, проверьте возможности вашей конфигурации. В современных решениях на платформе 1С:Предприятие 8 (таких как 1С:Управление торговлей ред. 11, 1С:ERP или 1С:Комплексная автоматизация) уже присутствуют мощные встроенные инструменты:

← На главную