При разработке отчетов и получении данных из табличных частей часто возникает задача: выбрать несколько разных показателей (например, минимальные даты различных событий или печатных форм) по конкретному документу и расположить их в одной итоговой строке. Начинающие разработчики нередко пытаются решить эту задачу простым перечислением двух вложенных запросов в секции ИЗ через запятую. Однако если хотя бы по одному из условий данных нет, весь итоговый запрос возвращает пустую выборку. Разберем причины такого поведения и рассмотрим правильные варианты построения запроса.
Проанализируем исходный вариант запроса, с которым возникла сложность:
ВЫБРАТЬ
ВложенныйЗапрос.ДатаПроверки КАК ДатаПроверки,
ВложенныйЗапрос1.ДатаПечати КАК ДатаПечати
ИЗ
(ВЫБРАТЬ ПЕРВЫЕ 1
ЗаказПокупателяВремяПечати.ДатаПечати КАК ДатаПроверки
ИЗ
Документ.ЗаказПокупателя.ВремяПечати КАК ЗаказПокупателяВремяПечати
ГДЕ
ЗаказПокупателяВремяПечати.Ссылка = &Док
И ЗаказПокупателяВремяПечати.ПечатнаяФорма = "Проверен"
УПОРЯДОЧИТЬ ПО
ДатаПроверки) КАК ВложенныйЗапрос,
(ВЫБРАТЬ ПЕРВЫЕ 1
ЗаказПокупателяВремяПечати.ДатаПечати КАК ДатаПечати
ИЗ
Документ.ЗаказПокупателя.ВремяПечати КАК ЗаказПокупателяВремяПечати
ГДЕ
ЗаказПокупателяВремяПечати.Ссылка = &Док
И ЗаказПокупателяВремяПечати.ПечатнаяФорма = "На склад"
УПОРЯДОЧИТЬ ПО
ДатаПечати) КАК ВложенныйЗапрос1
Когда в секции ИЗ указываются две таблицы через запятую без явного указания условия связи, СУБД выполняет декартово произведение (CROSS JOIN). Результирующее количество строк равно произведению количества строк первой таблицы на количество строк второй таблицы.
Если первая таблица возвращает 1 строку, а вторая — 0 строк (так как документ еще не отправлялся на склад или не проверялся), результат декартова произведения равен 1 * 0 = 0. Вся выборка оказывается пустой, несмотря на то что данные по одной из печатных форм в базе присутствуют.
Рассмотрим по шагам несколько способов решения этой задачи.
Наиболее быстрый и канонический способ развернуть значения из разных строк табличной части в отдельные колонки одной строки (pivot) — использование агрегатных функций с конструкцией ВЫБОР КОГДА.
Посмотрим на пример запроса:
ВЫБРАТЬ
МИНИМУМ(ВЫБОР
КОГДА ТЧ.ПечатнаяФорма = "На склад" ТОГДА ТЧ.ДатаПечати
КОНЕЦ) КАК ДатаПечати,
МИНИМУМ(ВЫБОР
КОГДА ТЧ.ПечатнаяФорма = "Проверен" ТОГДА ТЧ.ДатаПечати
КОНЕЦ) КАК ДатаПроверки
ИЗ
Документ.ЗаказПокупателя.ВремяПечати КАК ТЧ
ГДЕ
ТЧ.Ссылка = &Док
И ТЧ.ПечатнаяФорма В ("На склад", "Проверен")
Разберем преимущества этого подхода:
ВЫБОР не сработает и вернет NULL. Агрегатная функция МИНИМУМ проигнорирует NULL, а итоговая колонка корректно примет значение NULL, не затирая данные второй колонки.СГРУППИРОВАТЬ ПО всегда возвращает строго одну строку, даже если в табличной части документа вообще нет подходящих записей.Если требуется сохранить логику с подзапросами и выборкой первых записей, таблицы необходимо связать с помощью ПОЛНОЕ СОЕДИНЕНИЕ по условию ИСТИНА. Это гарантирует, что при отсутствии записей в одной из таблиц данные из другой не пропадут.
Посмотрим на реализацию:
ВЫБРАТЬ
ВложенныйЗапрос.ДатаПечати КАК ДатаПечати,
ВложенныйЗапрос1.ДатаПроверки КАК ДатаПроверки
ИЗ
(ВЫБРАТЬ ПЕРВЫЕ 1
ЗаказПокупателяВремяПечати.ДатаПечати КАК ДатаПечати
ИЗ
Документ.ЗаказПокупателя.ВремяПечати КАК ЗаказПокупателяВремяПечати
ГДЕ
ЗаказПокупателяВремяПечати.Ссылка = &Док
И ЗаказПокупателяВремяПечати.ПечатнаяФорма = "На склад"
УПОРЯДОЧИТЬ ПО
ЗаказПокупателяВремяПечати.ДатаПечати) КАК ВложенныйЗапрос
ПОЛНОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ ПЕРВЫЕ 1
ЗаказПокупателяВремяПечати.ДатаПечати КАК ДатаПроверки
ИЗ
Документ.ЗаказПокупателя.ВремяПечати КАК ЗаказПокупателяВремяПечати
ГДЕ
ЗаказПокупателяВремяПечати.Ссылка = &Док
И ЗаказПокупателяВремяПечати.ПечатнаяФорма = "Проверен"
УПОРЯДОЧИТЬ ПО
ЗаказПокупателяВремяПечати.ДатаПечати) КАК ВложенныйЗапрос1
ПО ИСТИНА
Обратите внимание: конструкция ПОЛНОЕ СОЕДИНЕНИЕ ... ПО ИСТИНА заставляет СУБД выполнять сопоставление подзапросов без индексного ключа, поэтому при работе с большими объемами данных такой вариант уступает условной агрегации по скорости выполнения.
Еще один рабочий вариант — объединить выборки через ОБЪЕДИНИТЬ ВСЕ, подставив в недостающие колонки NULL, а затем свернуть полученный результат во внешнем запросе с помощью функции МАКСИМУМ.
Рассмотрим структуру такого запроса:
ВЫБРАТЬ
МАКСИМУМ(Объединение.ДатаПечати) КАК ДатаПечати,
МАКСИМУМ(Объединение.ДатаПроверки) КАК ДатаПроверки
ИЗ
(ВЫБРАТЬ ПЕРВЫЕ 1
ТЧ.ДатаПечати КАК ДатаПечати,
NULL КАК ДатаПроверки
ИЗ
Документ.ЗаказПокупателя.ВремяПечати КАК ТЧ
ГДЕ
ТЧ.Ссылка = &Док
И ТЧ.ПечатнаяФорма = "На склад"
УПОРЯДОЧИТЬ ПО
ТЧ.ДатаПечати
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ПЕРВЫЕ 1
NULL,
ТЧ.ДатаПечати
ИЗ
Документ.ЗаказПокупателя.ВремяПечати КАК ТЧ
ГДЕ
ТЧ.Ссылка = &Док
И ТЧ.ПечатнаяФорма = "Проверен"
УПОРЯДОЧИТЬ ПО
ТЧ.ДатаПечати) КАК Объединение
Если документ уже прочитан в коде как объект (например, в модуле объекта перед записью или в форме документа), выполнять отдельный запрос к базе данных избыточно. Для извлечения дат можно использовать методы коллекции строк табличной части:
ПараметрыОтбора = Новый Структура("ПечатнаяФорма", "Проверен");
НайденныеСтроки = Объект.ВремяПечати.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
ДатаПроверки = НайденныеСтроки[0].ДатаПечати;
Иначе
ДатаПроверки = Неопределено;
КонецЕсли;
Итог: для получения нескольких минимальных или максимальных реквизитов из одной таблицы всегда отдавайте предпочтение запросам с МИНИМУМ(ВЫБОР КОГДА ...). Это исключает декартово умножение, гарантирует получение одной строки и обеспечивает максимальную производительность СУБД.