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