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