При работе с высоконагруженными базами на платформе 1С 7.7 стандартные средства получения остатков по регистрам (объект Запрос или метод Остатки()) могут работать неудовлетворительно медленно. В таких случаях программисты прибегают к использованию внешних компонент, таких как 1C++, для написания прямых SQL-запросов. Рассмотрим подробно, как реализовать расчет себестоимости через регистр ПартииНаличие с использованием виртуальных таблиц и дополнительной бизнес-логики.
Для начала разберем самый простой и эффективный способ получения остатков — использование макроса $РегистрОстатки. Этот макрос автоматически разворачивается компонентой 1С++ в сложный SQL-запрос, который учитывает актуальные итоги в таблицах RG* и движения в таблицах RA*.
Для получения количественного и суммового остатка в разрезе номенклатуры по конкретной фирме, проанализируем следующий пример кода:
SELECT
Рег.Номенклатура AS [Номенклатура $Справочник.Номенклатура],
Рег.КоличествоОстаток AS Количество,
Рег.СуммаРубОстаток AS СуммаРуб
FROM
$РегистрОстатки.ПартииНаличие(:ВыбДата~,,
Фирма = :ВбрФирма AND Номенклатура = :ВыбТовар,
(Номенклатура),
(Количество, СуммаРуб)) AS Рег
В этом запросе мы используем несколько важных параметров:
Номенклатура).Количество и СуммаРуб).Чтобы получить среднюю себестоимость, нам необходимо разделить общую сумму остатков на общее количество. Однако в SQL-запросах нужно быть крайне осторожными с операцией деления, чтобы избежать ошибки Division by zero (деление на ноль), если товара на остатке нет.
Рассмотрим доработанный вариант запроса, который вычисляет себестоимость "на лету":
SELECT
Рег.Номенклатура AS [Номенклатура $Справочник.Номенклатура],
Рег.КоличествоОстаток AS [Количество $Число],
Рег.СуммаРубОстаток AS [СуммаРуб $Число],
CASE
WHEN Рег.КоличествоОстаток = 0 THEN 0
ELSE Рег.СуммаРубОстаток / Рег.КоличествоОстаток
END AS [Себестоимость $Число]
FROM
$РегистрОстатки.ПартииНаличие(:ВыбДата~,,
Фирма = :ВбрФирма,
(Номенклатура),
(Количество, СуммаРуб)) AS Рег
Использование конструкции CASE WHEN гарантирует стабильность работы отчета даже при наличии некорректных данных в базе.
Иногда бизнес-процессы требуют не просто средней себестоимости, а учета статуса документов. Например, если в последнем приходе цена еще не установлена (равна нулю), то и в отчете себестоимость должна отображаться как нулевая, чтобы сигнализировать о незавершенном вводе данных.
Для решения такой задачи нам потребуется:
Разберем пример реализации через временную таблицу товаров:
SELECT
subsel.Номенклатура AS [Номенклатура $Справочник.Номенклатура],
CASE
WHEN (ISNULL(РегПосл.СуммаРуб, 0)) = 0 THEN 0
ELSE (sebestoimost.СуммаРуб / NULLIF(sebestoimost.Количество, 0))
END AS [Себестоимость $Число]
FROM
#TempTable temp
LEFT JOIN (
-- Поиск даты и времени последнего прихода по каждому товару
SELECT
$Рег.Номенклатура AS [Номенклатура],
MAX(Рег.Date_Time_IDDOC) AS ДатаПослПрихода
FROM
$Регистр.ПартииНаличие as Рег
INNER JOIN
_1Sjourn as ЖурналДок ON ЖурналДок.IDDoc = Рег.IDDoc
WHERE
ЖурналДок.iddocdef = $ВидДокумента.ПоступлениеТМЦ_Склад
AND $Рег.Фирма = :ВбрФирма
GROUP BY
$Рег.Номенклатура
) subsel ON temp.val = subsel.Номенклатура
LEFT JOIN (
-- Получение общих остатков через виртуальную таблицу
SELECT
РегОст.Номенклатура AS [Номенклатура],
РегОст.КоличествоОстаток AS Количество,
РегОст.СуммаРубОстаток AS СуммаРуб
FROM
$РегистрОстатки.ПартииНаличие(:ВыбДата~,,
Фирма = :ВбрФирма, (Номенклатура), (Количество, СуммаРуб)) AS РегОст
) sebestoimost ON temp.val = sebestoimost.Номенклатура
LEFT JOIN
$Регистр.ПартииНаличие AS РегПосл ON $РегПосл.Номенклатура = subsel.Номенклатура
AND РегПосл.Date_time_iddoc = subsel.ДатаПослПрихода
В данном запросе мы используем MAX(Date_Time_IDDOC). Это стандартное поле в 1С 7.7, которое объединяет дату, время и уникальный идентификатор документа, что позволяет точно определить "самый последний" документ в хронологии системы.
При написании прямых запросов важно учитывать внутреннее устройство хранения данных 1С:
1. Учет доп. расходов. В типовых конфигурациях (например, ТиС 9.2) ресурс СуммаРуб в регистре ПартииНаличие включает в себя не только стоимость по накладной, но и распределенные дополнительные расходы. Если ваши цифры в запросе не сходятся с "простой" ценой закупки, проверьте наличие записей по услугам, увеличивающим стоимость партий.
2. Блокировки (NOLOCK). SQL-сервер при выполнении тяжелых отчетов может блокировать таблицы итогов, мешая операторам проводить документы. Чтобы избежать этого, рекомендуется добавлять хинт WITH (NOLOCK) к таблицам, если вы используете "чистый" SQL без макросов 1С++.
3. Точка актуальности (ТА). Прямой запрос к таблице итогов RG* вернет данные на момент ТА. Если вы строите отчет на "вчера", макрос $РегистрОстатки автоматически вычтет обороты за текущий месяц из таблицы RA*. Это ресурсоемкая операция, поэтому для оперативных отчетов лучше всегда ориентироваться на текущее состояние итогов.
4. Обработка пустых значений. В SQL NULL не равен нулю. Всегда используйте функцию ISNULL(ВашеПоле, 0) при выполнении арифметических операций с результатами LEFT JOIN, иначе один отсутствующий приход превратит всю итоговую сумму в NULL.
Подводя итог, использование прямых запросов для расчета себестоимости — это мощный инструмент, позволяющий сократить время формирования отчетов в десятки раз. Однако он требует от программиста четкого понимания структуры метаданных и внимательности при обработке исключительных ситуаций, таких как отсутствие движений или нулевые остатки.