При разработке отчетов и обработок в 1С:Предприятии 7.7 с использованием внешнего компонента 1С++ и класса ПрямойЗапрос разработчики часто сталкиваются с ситуацией, когда позиции номенклатуры с нулевым остатком исчезают из результирующей выборки. Разберем причины такого поведения платформы и виртуальных таблиц, а также рассмотрим правильные способы построения SQL-запросов к файловым (DBF) и клиент-серверным базам данных.
Проанализируем структуру хранения данных в регистрах остатков 1С 7.7. В таблицах итогов (файлы RG*.DBF) физически отсутствуют строки по тем позициям номенклатуры, по которым никогда не оформлялись движения. Более того, если движения были, но итоговый остаток обнулился, при регламентном пересчете итогов платформа удаляет записи с нулевыми значениями.
Виртуальная таблица $РегистрОстатки формирует срез только по фактически существующим записям. Если мы запрашиваем остаток по товару, которого нет в таблице итогов, подзапрос или соединение возвращает значение NULL, а не число 0.
В реляционной логике SQL любое сравнение числа с NULL (например, Спр.МинОстаток > прОстаток) дает результат UNKNOWN (ложь для блока WHERE). В результате товар с остатком 0 (то есть 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 приводит к многократному расчету итогов, что критически замедляет выполнение запроса на больших базах.
Разберем наиболее эффективный и архитектурно правильный способ решения задачи. Вместо подзапроса в секции SELECT свяжем основную таблицу справочника номенклатуры с виртуальной таблицей остатков через LEFT JOIN (левое соединение).
Выполним построение запроса по шагам:
IsFolder = 2) и удаленные элементы (ISMARK <> '*').$РегистрОстатки, указав в ее параметрах только нужные группировки и ресурсы.COALESCE(Поле, 0) или конструкцию CASE WHEN ... IS NULL THEN 0 ELSE ... END.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)
Выясним важные детали работы с виртуальной таблицей $РегистрОстатки:
$РегистрОстатки.ИмяРегистра(Период, Фильтр, (Измерения), (Ресурсы)). Это исключает лишнюю группировку по неиспользуемым полям и существенно экономит оперативную память.COALESCE, так как функция IFNULL поддерживается не всеми СУБД, а ISNULL в MS SQL имеет другой синтаксис.WHERE, а условия соединения таблиц — строго в блоке ON.