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