Как получить остатки номенклатуры, включая нулевые, прямым запросом в 1С++?

Программист 1С v7.7 Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке отчетов и обработок в 1С:Предприятии 7.7 с использованием внешнего компонента 1С++ и класса ПрямойЗапрос разработчики часто сталкиваются с ситуацией, когда позиции номенклатуры с нулевым остатком исчезают из результирующей выборки. Разберем причины такого поведения платформы и виртуальных таблиц, а также рассмотрим правильные способы построения SQL-запросов к файловым (DBF) и клиент-серверным базам данных.

Почему нулевые остатки не попадают в выборку?

Проанализируем структуру хранения данных в регистрах остатков 1С 7.7. В таблицах итогов (файлы RG*.DBF) физически отсутствуют строки по тем позициям номенклатуры, по которым никогда не оформлялись движения. Более того, если движения были, но итоговый остаток обнулился, при регламентном пересчете итогов платформа удаляет записи с нулевыми значениями.

Виртуальная таблица $РегистрОстатки формирует срез только по фактически существующим записям. Если мы запрашиваем остаток по товару, которого нет в таблице итогов, подзапрос или соединение возвращает значение NULL, а не число 0.

В реляционной логике SQL любое сравнение числа с NULL (например, Спр.МинОстаток > прОстаток) дает результат UNKNOWN (ложь для блока WHERE). В результате товар с остатком 0 (то есть NULL) исключается из выборки, даже если его минимальный остаток строго больше нуля.

Способ 1. Использование функции обработки NULL в подзапросе

Рассмотрим первый способ обхода проблемы. Если мы используем подзапрос в блоке SELECT, необходимо явно преобразовать результат отсутствующей записи к числу 0 с помощью функции IFNULL (для SQLite/DBF) или COALESCE (универсальный стандарт SQL).

Посмотрим на пример такого запроса:


SELECT 
    Спр.ID as [Товар :Справочник.Товары], 
    IFNULL((SELECT ОстатокТовараОстаток 
            FROM $РегистрОстатки.ОстаткиТоваров( , , , ОстатокТовара) 
            WHERE Товар = Спр.ID), 0) AS прОстаток,
    $Спр.МинОстаток as [МинОст :Число.15.5]
FROM $Справочник.Товары as Спр 
WHERE Спр.IsFolder = 2 
    AND $Спр.МинОстаток > 0
    AND $Спр.МинОстаток > IFNULL((SELECT ОстатокТовараОстаток 
                                  FROM $РегистрОстатки.ОстаткиТоваров( , , , ОстатокТовара) 
                                  WHERE Товар = Спр.ID), 0)
    AND Спр.ISMARK <> '*'

Также можно проверить значение на IS NULL непосредственно в блоке WHERE:

AND (прОстаток IS NULL OR $Спр.МинОстаток > прОстаток)

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

Способ 2. Оптимальное решение через левое соединение (LEFT JOIN)

Разберем наиболее эффективный и архитектурно правильный способ решения задачи. Вместо подзапроса в секции SELECT свяжем основную таблицу справочника номенклатуры с виртуальной таблицей остатков через LEFT JOIN (левое соединение).

Выполним построение запроса по шагам:

  1. Выбираем элементы справочника, отсекая группы (IsFolder = 2) и удаленные элементы (ISMARK <> '*').
  2. Соединяем справочник с виртуальной таблицей $РегистрОстатки, указав в ее параметрах только нужные группировки и ресурсы.
  3. Оборачиваем поле остатка в функцию COALESCE(Поле, 0) или конструкцию CASE WHEN ... IS NULL THEN 0 ELSE ... END.
  4. В условии WHERE сопоставляем минимальный остаток с вычисленным остатком.

Посмотрим на готовый пример оптимизированного запроса:


SELECT
    Спр.ID AS [Товар :Справочник.Товары],
    $Спр.МинОстаток AS [МинОст :Число.15.5],
    COALESCE(Рег.ОстатокТовараОстаток, 0) AS [Остаток :Число.15.5]
FROM 
    $Справочник.Товары AS Спр
LEFT JOIN 
    $РегистрОстатки.ОстаткиТоваров( , , (Товар), (ОстатокТовара)) AS Рег 
    ON Спр.ID = Рег.Товар
WHERE 
    Спр.IsFolder = 2 
    AND Спр.ISMARK <> '*'
    AND $Спр.МинОстаток > 0
    AND $Спр.МинОстаток > COALESCE(Рег.ОстатокТовараОстаток, 0)

Рекомендации по оптимизации виртуальных таблиц в 1С++

Выясним важные детали работы с виртуальной таблицей $РегистрОстатки:

  1. Передача списка измерений и ресурсов: Всегда явно указывайте измерения и ресурсы в параметрах таблицы: $РегистрОстатки.ИмяРегистра(Период, Фильтр, (Измерения), (Ресурсы)). Это исключает лишнюю группировку по неиспользуемым полям и существенно экономит оперативную память.
  2. Универсальность функций: Для кроссплатформенности кода между SQLite (DBF) и MS SQL Server рекомендуется использовать функцию COALESCE, так как функция IFNULL поддерживается не всеми СУБД, а ISNULL в MS SQL имеет другой синтаксис.
  3. Порядок фильтрации: Условия отбора по самому справочнику (папки, пометки удаления) всегда располагайте в блоке WHERE, а условия соединения таблиц — строго в блоке ON.
← На главную