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