Как правильно соединить данные из нескольких регистров накопления в запросе 1С 8.3?

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

При разработке отчетов в 1С:Управление торговым предприятием (УТП) или других конфигурациях на платформе 1С 8.3 программисты часто сталкиваются с ситуацией, когда запрос к двум регистрам накопления возвращает пустой результат или работает некорректно. Рассмотрим типичную проблему: соединение регистров Закупки и ЗаказыПокупателей по номенклатуре и периоду.

Почему запрос к физическим таблицам регистров может возвращать пустой результат?

Проанализируем исходную ситуацию. Автор пытается соединить две физические таблицы регистров напрямую через ВНУТРЕННЕЕ СОЕДИНЕНИЕ по полям Номенклатура и Период. Выясним причину, по которой такой подход обречен на ошибку в большинстве случаев:

  1. Разница в секундах: Поле Период в физических таблицах 1С хранит дату и время с точностью до секунды. Вероятность того, что документ «Заказ покупателя» и документ «Поступление товаров» (создающий запись в регистре «Закупки») будут проведены в одну и ту же секунду, практически равна нулю. Даже если они оформлены в один день, время будет разным, и условие ЗаказыПокупателей.Период = Закупки.Период не выполнится.
  2. Избыточность данных: Обращаясь к физической таблице (РегистрНакопления.ИмяРегистра), мы заставляем систему сканировать все записи, включая сторнирующие и корректирующие, что крайне неэффективно.
  3. Тип соединения: Использование ВНУТРЕННЕЕ СОЕДИНЕНИЕ отсекает все записи, для которых нет пары во второй таблице. Если по товару была закупка, но не было заказа, в результат не попадет ничего.

Для решения подобных задач в 1С разработаны специальные механизмы — виртуальные таблицы. Разберем по шагам, как построить правильный и быстрый запрос.

Шаг 1. Использование виртуальных таблиц «Обороты»

Вместо того чтобы обращаться к сырым данным, мы будем использовать таблицу Обороты. Это позволит нам получить уже агрегированные данные за нужный период. Более того, фильтрацию (например, по конкретной номенклатуре) нужно накладывать не в секции ГДЕ, а в параметрах самой виртуальной таблицы. Это критически важно для производительности СУБД.

Рассмотрим структуру получения данных из первого регистра:


ВЫБРАТЬ
    ЗакупкиОбороты.Номенклатура КАК Номенклатура,
    ЗакупкиОбороты.КоличествоОборот КАК Количество,
    ЗакупкиОбороты.Регистратор КАК Регистратор,
    ЗакупкиОбороты.Регистратор.Комментарий КАК Комментарий
ПОМЕСТИТЬ ВТ_Закупки
ИЗ
    РегистрНакопления.Закупки.Обороты(&НачалоПериода, &КонецПериода, Авто, Номенклатура = &Тов) КАК ЗакупкиОбороты
ИНДЕКСИРОВАТЬ ПО
    Номенклатура
;

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

Шаг 2. Работа с временными таблицами в пакетном запросе

Проанализируем ситуацию: нам нужно сопоставить данные двух регистров. Лучшая практика — поместить данные каждого регистра в свою временную таблицу, а затем соединить их в финальной части пакета. Рассмотрим вторую часть запроса для регистра ЗаказыПокупателей:


ВЫБРАТЬ
    ЗаказыПокупателейОбороты.Номенклатура КАК Номенклатура,
    ЗаказыПокупателейОбороты.КоличествоОборот КАК Количество,
    ЗаказыПокупателейОбороты.Регистратор КАК Регистратор,
    ЗаказыПокупателейОбороты.Регистратор.Комментарий КАК Комментарий
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
    РегистрНакопления.ЗаказыПокупателей.Обороты(&НачалоПериода, &КонецПериода, Авто, Номенклатура = &Тов) КАК ЗаказыПокупателейОбороты
ИНДЕКСИРОВАТЬ ПО
    Номенклатура
;

Здесь мы использовали параметр Авто в качестве периодичности. Это позволяет получить в результате поле Регистратор, что часто необходимо для детализации отчета.

Шаг 3. Финальное соединение и обработка пустых значений

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


ВЫБРАТЬ
    ЕСТЬNULL(ВТ_Заказы.Номенклатура, ВТ_Закупки.Номенклатура) КАК Номенклатура,
    ВТ_Заказы.Количество КАК КолЗаказано,
    ВТ_Закупки.Количество КАК КолЗакуплено,
    ВТ_Заказы.Регистратор КАК РегистраторЗаказа,
    ВТ_Закупки.Регистратор КАК РегистраторЗакупки
ИЗ
    ВТ_Заказы КАК ВТ_Заказы
        ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Закупки КАК ВТ_Закупки
        ПО ВТ_Заказы.Номенклатура = ВТ_Закупки.Номенклатура

Важные нюансы:

Альтернативный метод: ОБЪЕДИНИТЬ ВСЕ

В системе 1С существует еще один «каноничный» способ решения данной задачи, который часто работает даже быстрее, чем соединения. Это использование конструкции ОБЪЕДИНИТЬ ВСЕ с последующей группировкой.

Разберем этот метод:

  1. Первым запросом выбираем данные из первого регистра, добавляя «пустые» поля для данных второго регистра (например, 0 КАК КоличествоЗаказа).
  2. Вторым запросом через ОБЪЕДИНИТЬ ВСЕ выбираем данные из второго регистра, добавляя пустые поля для первого.
  3. Весь результат помещаем во временную таблицу или делаем вложенный запрос.
  4. Группируем результат по номенклатуре и другим аналитикам, суммируя числовые показатели.

Этот способ избавляет от проблем с NULL и позволяет легко добавлять неограниченное количество регистров в один отчет без потери производительности.

Рекомендации по работе с датами

При передаче параметров в запрос из кода 1С, всегда убеждайтесь, что дата окончания периода включает в себя последнюю секунду дня. Для этого используйте встроенную функцию КонецДня():


Запрос.УстановитьПараметр("НачалоПериода", ДатаНачала);
Запрос.УстановитьПараметр("КонецПериода", КонецДня(ДатаОкончания));

Если этого не сделать, документы, созданные, например, в 14:00 последнего дня периода, не попадут в виртуальную таблицу оборотов, так как по умолчанию дата без указания времени считается началом дня (00:00:00).

Следуя этим правилам — используя виртуальные таблицы вместо физических, пакетные запросы с временными таблицами и правильную установку параметров — вы сможете создавать надежные и производительные запросы в 1С 8.3.

← На главную