Почему пропадают записи в запросе 1С 7.7 при обращении к реквизитам документа через регистр?

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

При работе со штатными («черными») запросами в платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с крайне неприятной проблемой: в итоговую выборку попадают далеко не все записи регистра, хотя визуально в отчетах или карточке регистра движения присутствуют. Особенно ярко этот эффект проявляется, когда мы пытаемся «дотянуться» до реквизитов документа-регистратора (например, получить контрагента) через виртуальное поле ТекущийДокумент.

В этой статье мы подробно разберем, почему возникает эта проблема, как особенности СУБД-транслятора 1С 7.7 влияют на результат выборки, и изучим несколько эффективных способов решения задачи без потери важных данных.

Выясняем причину: Эффект неявного INNER JOIN

Давайте проанализируем фрагмент исходного запроса, который вызывает затруднения:

|Контрагент = Регистр.ПартииНаличие.ТекущийДокумент.Реализация.Контрагент;

На первый взгляд, конструкция выглядит логично. Мы хотим получить значение реквизита Контрагент из документа Реализация, который сделал движение по регистру остатков ПартииНаличие. Однако на уровне базы данных (будь то СУБД MS SQL Server или локальные DBF-таблицы) этот запрос интерпретируется платформой весьма специфическим образом:

  1. Платформа 1С 7.7 не умеет делать «мягкое» левое соединение (LEFT JOIN) при обращении к конкретному виду документа через точку от обобщенного поля ТекущийДокумент.
  2. Вместо этого генерируется строгое внутреннее соединение (INNER JOIN) таблицы движений регистра с таблицей шапки документа Реализация.
  3. Результат: Если движение по регистру ПартииНаличие было сформировано любым другим документом, кроме Реализация (например, документами ПоступлениеТМЦ, СписаниеТМЦ, Перемещение, ВозвратОтПокупателя или ОтчетККМ), эти записи будут полностью отброшены из итоговой выборки запроса. Они не просто вернут пустое значение контрагента — они вообще исчезнут из результатов, исказив тем самым общую сумму расхода количества товара.

Именно поэтому у автора вопроса «пропадали» записи регистра. Как только мы накладываем условие связи с конкретным документом, мы автоматически отсекаем все остальные хозяйственные операции.

---

Способ 1. Получение данных при обходе (Постобработка выборки)

Рассмотрим первый, наиболее надежный и универсальный способ решения проблемы в рамках стандартного запроса. Если нам необходимо гарантированно получить абсолютно все движения регистра (включая списания и перемещения, где контрагента может и не быть) и при этом отобразить контрагента там, где это возможно, нам следует отказаться от получения реквизитов документа внутри самого текста запроса.

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

  1. Вместо обращения к реквизиту Контрагент через точку, мы объявляем в запросе саму переменную документа-регистратора.
  2. Добавляем группировку по этой переменной.
  3. При обходе результатов запроса в цикле мы программно получаем объект документа и считываем из него контрагента.

Посмотрим на пример реализации этого подхода в коде:


Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Период с ДатаНачала по ДатаКонца;
|Фирма = Регистр.ПартииНаличие.Фирма;
|Производитель = Регистр.ПартииНаличие.Партия.Производитель;
|Товар = Регистр.ПартииНаличие.Номенклатура;
|Количество = Регистр.ПартииНаличие.Количество;
|Поставщик = Регистр.ПартииНаличие.Партия.Поставщик;
|Док = Регистр.ПартииНаличие.ТекущийДокумент;
|Функция Сумма = Расход(Количество);
|Группировка Док;
|Условие(Фирма = ВыбФирма);
|Условие(Производитель = ВыбПроизводитель);
|Условие(Поставщик = ВыбПоставщик);
|";

Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
    Возврат;
КонецЕсли;

Пока Запрос.Группировка("Док") = 1 Цикл
    ТекДок = Запрос.Док;
    ТекКонтрагент = ПолучитьПустоеЗначение("Справочник.Контрагенты");
    
    // Определяем вид документа и извлекаем контрагента
    Если ТекДок.Выбран() = 1 Тогда
        ВидДока = ТекДок.Вид();
        // Список документов, у которых есть реквизит "Контрагент"
        Если (ВидДока = "Реализация") Или (ВидДока = "РеализацияРозница") Или (ВидДока = "ВозвратОтПокупателя") Тогда
            ТекКонтрагент = ТекДок.Контрагент;
        КонецЕсли;
    КонецЕсли;
    
    // Здесь выполняем вывод строки в отчет или заполнение Таблицы Значений
    // Например: Сообщить("Документ: " + ТекДок + " | Контрагент: " + ТекКонтрагент + " | Кол-во: " + Запрос.Сумма);
КонецЦикла;

Преимущества метода: Мы не теряем ни одной записи регистра. Даже если товар был списан по инвентаризации, транзакция попадет в отчет с пустым контрагентом, что обеспечит идеальную сходимость остатков и оборотов.

---

Способ 2. Использование специализированного регистра «Продажи»

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

Для анализа отгрузок в типовых конфигурациях 1С 7.7 (например, в «Торговля и Склад 7.7») предусмотрен специализированный оборотный регистр Продажи. В нем измерение Покупатель (или Контрагент) вынесено на уровень измерений самого регистра.

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


Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Период с ДатаНачала по ДатаКонца;
|Фирма = Регистр.Продажи.Фирма;
|Товар = Регистр.Продажи.Номенклатура;
|Контрагент = Регистр.Продажи.Покупатель;
|Количество = Регистр.Продажи.Количество;
|ПродСтоимость = Регистр.Продажи.ПродСтоимость;
|Функция ВсегоКол = Оборот(Количество);
|Функция ВсегоСумма = Оборот(ПродСтоимость);
|Группировка Контрагент Без Групп;
|Группировка Товар Без Групп;
|Условие(Фирма = ВыбФирма);
|";

Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
    Возврат;
КонецЕсли;

Почему это работает лучше:

---

Способ 3. Перечисление документов через запятую

Если специфика вашей задачи всё же требует получения контрагента непосредственно из регистра ПартииНаличие (например, вам крайне важен партионный контекст конкретной отгрузки), и вы не хотите использовать постобработку в цикле, можно использовать механизм перечисления видов документов через запятую.

Обратите внимание: синтаксис, предложенный в сообщении 9 форума, напрямую работать с регистром не будет. Нам нужно строить путь от измерения или реквизита регистра через ТекущийДокумент.

Правильный синтаксис конструкции множественного выбора документов для регистра выглядит следующим образом:


|Контрагент = Регистр.ПартииНаличие.ТекущийДокумент.Реализация.Контрагент, 
|             Регистр.ПартииНаличие.ТекущийДокумент.РеализацияРозница.Контрагент,
|             Регистр.ПартииНаличие.ТекущийДокумент.ВозвратОтПокупателя.Контрагент;

Важное предупреждение: При использовании этого подхода платформа выполнит операцию, аналогичную объединению нескольких левых соединений. Но помните, что если движение по регистру было сделано документом, который вы не указали в этом списке через запятую (например, СписаниеТМЦ или Перемещение), эта строка регистра все равно будет полностью отсечена из результата выполнения запроса.

---

Совет для крупных баз данных (SQL)

Если вы работаете на SQL-версии «1С:Предприятие 7.7» и объем базы данных измеряется гигабайтами, стандартные («черные») запросы при обращении к реквизитам документов через регистр могут вызывать длительные зависания и блокировки таблиц базы данных.

В профессиональной среде разработчиков эту проблему решают с помощью внешней компоненты 1С++ и выполнения прямого SQL-запроса через класс ODBCRecordset. Использование прямого запроса позволяет написать классический LEFT JOIN к общему журналу документов и таблицам конкретных документов, а использование SQL-функции COALESCE позволит собрать контрагентов из разных типов документов в одно поле за один проход СУБД без малейшей потери производительности и записей.

← На главную