Как получить одну последнюю запись при объединении нескольких запросов в 1С?

Программист 1С v8.3 (Обычные формы) 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При разработке отчетов или механизмов анализа данных в системе «1С:Предприятие 8» программисты часто сталкиваются с ситуацией, когда данные необходимо собрать из нескольких источников или по разным условиям, но в итоге получить строго одну — самую актуальную — запись. Типичный пример: поиск даты последней оплаты, которая могла пройти либо через кассу, либо через банковский счет.

В данной статье мы подробно разберем, почему использование конструкции ВЫБРАТЬ ПЕРВЫЕ 1 внутри объединения (ОБЪЕДИНИТЬ ВСЕ) не дает желаемого результата, развеем мифы об ограничениях виртуальных таблиц и научимся писать оптимальные запросы для подобных задач.

Проблема локального ограничения ВЫБРАТЬ ПЕРВЫЕ

Рассмотрим ситуацию, представленную в исходном вопросе. Разработчик пытается выбрать последнюю оплату, используя два запроса, соединенных через ОБЪЕДИНИТЬ ВСЕ. В каждом из них стоит ограничение ВЫБРАТЬ ПЕРВЫЕ 1. Ожидается, что результат вернет одну строку, но на практике запрос возвращает две записи (если оплаты были и по банку, и по кассе).

Почему так происходит? В языке запросов 1С конструкция ВЫБРАТЬ ПЕРВЫЕ работает локально для каждого блока объединения. Процессор запросов сначала выполняет первый запрос и берет из него одну запись, затем выполняет второй запрос и берет из него одну запись. После этого он «склеивает» эти результаты. Итоговое УПОРЯДОЧИТЬ ПО в конце всего пакета лишь сортирует эти две полученные записи, но не отсекает лишние.

Решение №1: Использование вложенного запроса

Проанализируем самый надежный способ получения строго одной записи из объединенных данных. Для этого нам необходимо превратить результат объединения во временную таблицу или вложенный запрос, а затем применить ограничение ПЕРВЫЕ 1 уже ко всей выборке целиком.

Рассмотрим пример правильной структуры запроса:


ВЫБРАТЬ ПЕРВЫЕ 1
    Т.Дата КАК ДатаОплаты,
    Т.Сумма КАК Сумма
ИЗ (
    ВЫБРАТЬ ПЕРВЫЕ 1
        Типовой.Регистратор.Дата КАК Дата,
        Типовой.Сумма КАК Сумма
    ИЗ
        РегистрБухгалтерии.Типовой.ДвиженияССубконто(&ДатаНач, &ДатаКон, СчетДт = &Счет1030 И ...) КАК Типовой
    УПОРЯДОЧИТЬ ПО
        Дата УБЫВ

    ОБЪЕДИНИТЬ ВСЕ

    ВЫБРАТЬ ПЕРВЫЕ 1
        Типовой.Регистратор.Дата КАК Дата,
        Типовой.Сумма КАК Сумма
    ИЗ
        РегистрБухгалтерии.Типовой.ДвиженияССубконто(&ДатаНач, &ДатаКон, СчетДт = &Счет1010 И ...) КАК Типовой
    УПОРЯДОЧИТЬ ПО
        Дата УБЫВ
) КАК Т
УПОРЯДОЧИТЬ ПО
    Т.Дата УБЫВ

В данном случае внешний запрос «схлопнет» результаты двух внутренних и выберет из них одну самую свежую запись. Обратите внимание, что упорядочивание внутри подзапросов помогает СУБД быстрее отобрать кандидатов, но решающую роль играет внешнее упорядочивание.

Решение №2: Оптимизация через параметры виртуальной таблицы

Выясним причину, по которой разработчики часто прибегают к громоздким объединениям. В сообщении форума упоминается заблуждение, что в параметрах виртуальной таблицы нельзя использовать условие ИЛИ. Это не так. Использование оператора В или ИЛИ непосредственно в параметрах виртуальной таблицы гораздо эффективнее с точки зрения производительности.

Проанализируем, как можно упростить исходную задачу, избавившись от объединения вообще:


ВЫБРАТЬ ПЕРВЫЕ 1
    Типовой.Регистратор.Дата КАК Дата,
    Типовой.Сумма КАК Сумма
ИЗ
    РегистрБухгалтерии.Типовой.ДвиженияССубконто(
        ,
        &Дата1,
        СчетКт = &Счет1210 
        И СчетДт В (&СписокСчетовОплаты) 
        И СубконтоКт3 = &Докт3,
        ,
    ) КАК Типовой
УПОРЯДОЧИТЬ ПО
    Дата УБЫВ

Где &СписокСчетовОплаты — это массив, содержащий ссылки на счета 1010 и 1030. Такой подход позволяет базе данных выполнить поиск за один проход, используя индексы, и сразу вернуть одну нужную запись.

Важные аспекты производительности

Разберем по шагам, на что стоит обратить внимание при работе с регистрами бухгалтерии, чтобы запрос работал максимально быстро:

  1. Отказ от обращения через точку к коду счета: В исходном коде использовалось сравнение СчетКт.Код = "1210". Это плохая практика. Платформа 1С в таком случае выполняет неявное соединение с таблицей плана счетов. Кроме того, сравнение строк — медленная операция. Всегда передавайте ссылки на счета через параметры запроса.
  2. Фильтрация внутри параметров: Убедитесь, что все условия (по счетам, субконто, периоду) находятся именно внутри скобок виртуальной таблицы ДвиженияССубконто(...), а не в секции ГДЕ. Это критически важно: параметры обрезают данные на уровне СУБД при получении выборки, а ГДЕ фильтрует уже полученный в память результат.
  3. Выбор таблицы: Таблица ДвиженияССубконто — самая «тяжелая», так как она разворачивает проводку по всем аналитикам. Если вам нужно только условие по счету и общая сумма, рассмотрите использование таблицы ДвиженияСПроводками, она работает быстрее.

Точность определения «последней» записи

Посмотрим на ситуацию глубже: что если две оплаты (по банку и по кассе) прошли в одну и ту же секунду? При использовании УПОРЯДОЧИТЬ ПО Дата УБЫВ результат может быть непредсказуемым.

Для «железного» получения самой последней записи в системе 1С рекомендуется использовать Момент Времени. Это объект, включающий в себя не только дату и время до секунды, но и уникальную ссылку на документ-регистратор. В запросе это выглядит так:


УПОРЯДОЧИТЬ ПО
    Типовой.Период УБЫВ,
    Типовой.Регистратор УБЫВ

Такая сортировка гарантирует, что даже при совпадении времени до секунды, последовательность записей будет строго определена внутренним идентификатором ссылки, и вы всегда будете получать один и тот же результат при повторных вызовах запроса.

Подводя итог, можно сказать, что решение задачи сводится к двум путям: либо правильная обертка объединения во вложенный запрос, либо (что предпочтительнее) использование гибких условий внутри параметров одной виртуальной таблицы. Второй метод не только делает код чище, но и существенно снижает нагрузку на сервер базы данных.

← На главную