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