При построении аналитических отчетов в 1С 7.7 с использованием прямых запросов (SQL) часто возникает задача подсчитать, сколько дней конкретный товар фактически находился на складе за определенный период. Типичная ошибка в таких запросах приводит к тому, что дни, когда товар поступил и был списан в один и тот же день, выпадают из расчета (получается 0 дней), что искажает итоговую статистику. Разберем, как правильно составить запрос, чтобы избежать этой проблемы.
Рассмотрим ситуацию, с которой столкнулся автор темы. В его запросе использовалась проверка только начального остатка товара на конкретную дату. Проанализируем фрагмент исходного кода:
CASE WHEN ВлЗапрос.НачОст > 0 THEN
-- ... логика расчета интервала ...
ELSE
CASE WHEN ВлЗапрос.КонОст > 0 THEN
DATEDIFF(day, ВлЗапрос.Период, convert(datetime, :КонДата, 112)) + 1
END
END
В чем заключается ошибка? Если товар поступил утром (НачальныйОстаток = 0) и был продан вечером того же дня (КонечныйОстаток = 0), то условие ВлЗапрос.НачОст > 0 вернет ложь. Программа не увидит, что в течение этого дня товар фактически присутствовал на складе. Выясним, как изменить это условие, чтобы охватить все движения товара.
Самый простой и эффективный способ исправить логику — анализировать не только остаток на начало дня, но и факт прихода товара. Если на начало дня товара не было, но в течение дня был приход (КоличествоПриход), этот день должен быть засчитан как день нахождения на складе.
Рассмотрим правильное условие для функции SUM. Вместо сложных вложенных конструкций CASE и DATEDIFF, если мы используем виртуальную таблицу $РегистрОстаткиОбороты с периодичностью День, нам достаточно проверить сумму начального остатка и прихода:
SELECT
ВлЗапрос.Номенклатура,
SUM(CASE WHEN (ВлЗапрос.НачОст + ВлЗапрос.Приход) > 0 THEN 1 ELSE 0 END) as ДнейНаСкладе
FROM (
SELECT
Остатки.Номенклатура,
Остатки.Период as Период,
Остатки.КоличествоНачальныйОстаток НачОст,
Остатки.КоличествоПриход Приход
FROM $РегистрОстаткиОбороты.ОстаткиТМЦ(:НачДата, :КонДата~, День, ДвиженияИГраницыПериода, , ,) AS Остатки
) AS ВлЗапрос
GROUP BY ВлЗапрос.Номенклатура
В данном примере мы суммируем единицы за каждый день, когда условие (НачОст + Приход) > 0 выполняется. Это гарантирует, что даже если товар пришел и ушел в один день, сумма будет больше нуля, и день попадет в расчет.
Важно понимать особенность работы виртуальной таблицы $РегистрОстаткиОбороты в 1С 7.7. Она возвращает строки только для тех дней, когда по товару были движения или на границы периода. Если товар лежал на складе 10 дней без движения, запрос может вернуть только одну строку на дату начала остатка, и простое суммирование строк SUM(1) выдаст результат 1 день вместо 10.
Для решения этой проблемы в SQL-запросах профессиональные разработчики используют соединение с таблицей дат (календарем). Разберем по шагам, как это работает:
LEFT JOIN остатков товара.Проанализируем пример логики «протягивания» остатка без использования календаря, но с расчетом разницы дат, как пытался сделать автор:
-- Если товар есть на начало, считаем дни до следующего изменения или до конца периода
CASE
WHEN ВлЗапрос.НачОст > 0 THEN DATEDIFF(day, ВлЗапрос.Период, СледующаяДатаДвижения)
WHEN ВлЗапрос.Приход > 0 THEN DATEDIFF(day, ВлЗапрос.Период, СледующаяДатаДвижения)
ELSE 0
END
Однако такой подход крайне сложен в реализации на чистом SQL версии 7.7 без оконных функций. Поэтому наиболее стабильным вариантом остается либо использование календаря, либо расчет в Таблице Значений (ТЗ) после выгрузки данных запроса.
Обратим внимание на технический нюанс, упомянутый в обсуждении. При передаче дат в запрос 1С++ часто используют конструкцию convert(datetime, :НачДата, 112). Посмотрим на более лаконичный и быстрый способ передачи параметров, который предлагает сама компонента 1С++:
Использование суффикса ~~ (две тильды) позволяет передать дату напрямую в формате SQL без лишних преобразований:
:НачДата~~ вместо convert(datetime, :НачДата, 112).
Это не только упрощает код, но и избавляет от потенциальных ошибок конвертации, связанных с региональными настройками сервера базы данных.
Подведем итоги и сформируем алгоритм действий для корректного расчета:
(НачальныйОстаток > 0 ИЛИ Приход > 0).День в виртуальной таблице, убедитесь, что вы обрабатываете пропуски между днями, если товар лежал без движения.:Дата~~) и типизацию полей ($РегистрОстаткиОбороты), чтобы избежать проблем с производительностью.ТаблицаЗначений и произведите итерационный расчет циклом. Для небольших и средних справочников номенклатуры разница в скорости будет незначительной, а код станет гораздо понятнее.Соблюдение этих правил позволит вам получать точные данные о складских запасах и корректно рассчитывать оборачиваемость товаров в вашей системе на базе 1С 7.7.