При разработке запросов в 1С часто возникает ситуация: необходимо получить связанные данные из нескольких источников (например, плановую сумму из регистра заказов и фактические оплаты из регистра бухгалтерии), но по одному из источников движений еще нет. В результате весь запрос возвращает пустой результат, либо разработчик сталкивается с ошибками при попытке проверить поле на Неопределено или NULL.
Разберем типичные причины такого поведения, выясним разницу между типами данных и рассмотрим правильные варианты построения запросов.
Проанализируем распространенное заблуждение: разработчики часто пытаются проверить отсутствующие значения в запросе через ПОЛЕ = Неопределено или конструкцию ВЫБОР КОГДА Поле ЕСТЬ НЕОПРЕДЕЛЕНО.
В платформе 1С эти понятия строго разграничены:
NULL возвращается полями правой таблицы при ЛЕВОМ СОЕДИНЕНИИ, если для строки левой таблицы не нашлось соответствия.Попытка проверить числовые ресурсы регистра на Неопределено приведет либо к синтаксической ошибке, либо к неработающему условию. Для замены отсутствующих данных на число 0 всегда используется функция ЕСТЬNULL.
Посмотрим на классическую ошибку в запросе:
ВЫБРАТЬ
СуммаДоговораВсего.СуммаДоговора КАК СуммаДоговора,
СУММА(ХозрасчетныйОстаткиИОбороты.СуммаОборотКт) КАК СуммаАвансов
ИЗ
СуммаДоговораВсего КАК СуммаДоговораВсего
ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(...) КАК ХозрасчетныйОстаткиИОбороты
ПО СуммаДоговораВсего.ДоговорКонтрагента = ХозрасчетныйОстаткиИОбороты.Субконто2
ГДЕ
ХозрасчетныйОстаткиИОбороты.Субконто2 = &Субконто2
И ХозрасчетныйОстаткиИОбороты.Регистратор ССЫЛКА Документ.ПлатежноеПоручениеВходящее
Выясним причину, почему этот запрос не возвращает строки, если авансов не было. Секция ГДЕ применяется после выполнения соединения ко всей выборке. Если оплат нет, поля правой таблицы содержат NULL. Условие NULL = &Субконто2 возвращает значение «Ложь», и строка целиком отсекается. Таким образом, ЛЕВОЕ СОЕДИНЕНИЕ фактически превращается во внутреннее.
Правило: условия на правую (присоединяемую) таблицу необходимо накладывать либо в параметрах виртуальной таблицы, либо в секции ПО условия соединения.
Если в выборке нет строк для группировки, агрегатная функция СУММА() возвращает NULL, даже если внутри нее написать ЕСТЬNULL(Поле, 0). Чтобы гарантированно получить число 0, функцию замены необходимо оборачивать вокруг агрегата:
ЕСТЬNULL(СУММА(ХозрасчетныйОстаткиИОбороты.СуммаОборотКт), 0) КАК СуммаАвансов
Перенесем все фильтры правой таблицы в параметры виртуальной таблицы и условия связи:
ВЫБРАТЬ
ЗаказыОбороты.ДоговорКонтрагента КАК ДоговорКонтрагента,
ЕСТЬNULL(ЗаказыОбороты.СуммаВзаиморасчетовПриход, 0) КАК СуммаДоговора
ПОМЕСТИТЬ ВТ_СуммаДоговора
ИЗ
РегистрНакопления.ЗаказыПокупателей.Обороты(, , , ДоговорКонтрагента = &ДоговорКонтрагента) КАК ЗаказыОбороты
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_СуммаДоговора.ДоговорКонтрагента КАК ДоговорКонтрагента,
ВТ_СуммаДоговора.СуммаДоговора КАК СуммаДоговора,
ЕСТЬNULL(СУММА(ХозрасчетныйОбороты.СуммаОборотКт), 0) КАК СуммаАвансов
ИЗ
ВТ_СуммаДоговора КАК ВТ_СуммаДоговора
ЛЕВОЕ СОЕДИНЕНИЕ РегистрБухгалтерии.Хозрасчетный.Обороты(
,
,
Регистратор,
Счет = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.РасчетыПоАвансамПолученным),
,
Субконто2 = &ДоговорКонтрагента,
,
) КАК ХозрасчетныйОбороты
ПО (ВТ_СуммаДоговора.ДоговорКонтрагента = ХозрасчетныйОбороты.Субконто2)
И (ХозрасчетныйОбороты.Регистратор ССЫЛКА Документ.ПлатежноеПоручениеВходящее)
СГРУППИРОВАТЬ ПО
ВТ_СуммаДоговора.ДоговорКонтрагента,
ВТ_СуммаДоговора.СуммаДоговора
Когда требуется соединить данные из двух независимых регистров (например, регистра накопления и регистра бухгалтерии), объединение выборок с последующей группировкой работает надежнее внешних соединений. Этот паттерн исключает потерю строк, даже если данные есть только в одном из источников:
ВЫБРАТЬ
ДоговорыИОбороты.ДоговорКонтрагента КАК ДоговорКонтрагента,
СУММА(ДоговорыИОбороты.СуммаДоговора) КАК СуммаДоговора,
СУММА(ДоговорыИОбороты.СуммаАвансов) КАК СуммаАвансов
ИЗ
(ВЫБРАТЬ
ЗаказыОбороты.ДоговорКонтрагента КАК ДоговорКонтрагента,
ЗаказыОбороты.СуммаВзаиморасчетовПриход КАК СуммаДоговора,
0 КАК СуммаАвансов
ИЗ
РегистрНакопления.ЗаказыПокупателей.Обороты(, , , ДоговорКонтрагента = &ДоговорКонтрагента) КАК ЗаказыОбороты
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ХозрасчетныйОбороты.Субконто2 КАК ДоговорКонтрагента,
0 КАК СуммаДоговора,
ХозрасчетныйОбороты.СуммаОборотКт КАК СуммаАвансов
ИЗ
РегистрБухгалтерии.Хозрасчетный.Обороты(
,
,
Регистратор,
Счет = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.РасчетыПоАвансамПолученным),
,
Субконто2 = &ДоговорКонтрагента,
,
) КАК ХозрасчетныйОбороты
ГДЕ
ХозрасчетныйОбороты.Регистратор ССЫЛКА Документ.ПлатежноеПоручениеВходящее) КАК ДоговорыИОбороты
СГРУППИРОВАТЬ ПО
ДоговорыИОбороты.ДоговорКонтрагента
Обратим внимание на обработку выборки во встроенном языке. Если по договору не было ни заказов, ни оплат, результат запроса будет полностью пустым. В таком случае цикл Пока Выборка.Следующий() не выполнится ни разу, и переменные останутся со значением Неопределено.
Инициализируем переменные значениями по умолчанию перед обходом выборки:
Авансы = 0;
ВсегоПоДоговору = 0;
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Авансы = Выборка.СуммаАвансов;
ВсегоПоДоговору = Выборка.СуммаДоговора;
КонецЦикла;
КонецЕсли;
Такой подход защитит код от непредвиденных ошибок при расчетах с числовыми переменными.