вторник, 14 апреля 2020 г.

Агрегатные функции в запросах SQL без секции GROUP BY

Надо помнить, что если секции GROUP BY нет, то запрос с агрегатными функциями все равно вернет единственную строку данных, даже если условия в секции WHERE не выполнились.
Вот пример функции из типового решения 1С с этой ошибкой:
Функция ПолучитьДатуПоследнегоДокументаОснования()
 
 УстановитьПривилегированныйРежим(Истина);
 
 Запрос = Новый Запрос;
 Запрос.Текст = 
 "ВЫБРАТЬ
 | МАКСИМУМ(ДанныеПервичныхДокументов.ДатаРегистратора) КАК Дата
 |ИЗ
 | РегистрСведений.ДанныеПервичныхДокументов КАК ДанныеПервичныхДокументов
 |ГДЕ
 | ДанныеПервичныхДокументов.Организация = &Организация
 | И ДанныеПервичныхДокументов.Документ В (&ДокументыОснования)";
 Запрос.УстановитьПараметр("ДокументыОснования", ДокументыОснования.Выгрузить().ВыгрузитьКолонку("ДокументОснование"));
 Запрос.УстановитьПараметр("Организация", Организация);
 Результат = Запрос.Выполнить();
 
 УстановитьПривилегированныйРежим(Ложь);
 
 Если Результат.Пустой() Тогда
  Возврат Дата(1,1,1);                     //вот эта ветка никогда не выполнится
 Иначе
  Возврат Результат.Выгрузить()[0].Дата;   //здесь будет NULL, если условие в секции ГДЕ не выполнится
 КонецЕсли;
КонецФункции

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