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