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