Как получить реквизиты регистра в прямом запросе 1С 7.7 и избежать дублей

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

При работе с платформой 1С 7.7 программисты часто сталкиваются с ограничениями штатных «черных» запросов, особенно когда объем базы данных перерастает лимиты формата DBF или когда требуется высокая скорость формирования отчетов. Использование компоненты 1С++ и прямых SQL-запросов позволяет обойти эти ограничения. Однако возникает специфическая сложность: как получить значения реквизитов регистра в запросе по остаткам и оборотам?

В этой статье мы подробно разберем, почему возникают дубли при соединении таблиц, как правильно структурировать запрос к виртуальным таблицам $РегистрОстаткиОбороты и как корректно «подтянуть» данные из таблицы движений.

Анализируем проблему: почему появляются дубли?

Давайте проанализируем структуру хранения данных в 1С 7.7. Регистры остатков хранятся в двух физических таблицах SQL:

  1. Таблица итогов (RGxxxx): здесь хранятся только измерения и ресурсы. Реквизитов здесь нет по определению.
  2. Таблица движений (RAxxxx): здесь хранятся все данные, включая реквизиты, привязанные к конкретным документам.

Когда мы используем виртуальную таблицу $РегистрОстаткиОбороты, 1С++ математически вычисляет остатки, суммируя данные из таблиц итогов и движений. Если нам нужно вывести реквизит (например, ПродСтоимость или НомерСтроки), нам приходится соединять результат виртуальной таблицы с физической таблицей движений $Регистр.ИмяРегистра по полям измерений и позиции документа (DATE_TIME_IDDOC).

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

Разбираем правильный подход к написанию запроса

Рассмотрим решение, которое позволяет избежать «раздувания» сумм. Основная идея заключается в том, чтобы соединяться не напрямую с физической таблицей движений, а с подзапросом, в котором уже выполнена группировка по всем ключевым полям.

Давайте изучим пример кода, который решает эту задачу:


|SELECT 
|       ПартииНаличиеОстаткиОбороты.Фирма as [Фирма $Справочник.Фирмы] 
|     , ПартииНаличиеОстаткиОбороты.МОЛ as [МОЛ $Справочник.ФизЛица] 
|     , ПартииНаличиеОстаткиОбороты.Номенклатура as [Номенклатура $Справочник.Номенклатура] 
|     , ПартииНаличиеОстаткиОбороты.Партия as [Партия $Справочник.Партии] 
|     , ПартииНаличиеОстаткиОбороты.ЦенаПрод as ЦенаПрод 
|     , ПартииНаличиеОстаткиОбороты.СерияНоменклатуры as [СерияНоменклатуры $Справочник.СерииНоменклатуры] 
|     , RIGHT(ПартииНаличиеОстаткиОбороты.ПозицияДокумента,9) as [Документ $Документ] 
|     , MAX(ПартииНаличиеОстаткиОбороты.ВидДокумента) as Документ_вид 
|     , MAX(Движения.НомерСтрокиДокумента) as НомерСтрокиДокумента 
|     
|     , GROUPING(ПартииНаличиеОстаткиОбороты.Фирма) as ИтогПоФирма 
|     , GROUPING(ПартииНаличиеОстаткиОбороты.МОЛ) as ИтогПоМОЛ 
|     , GROUPING(ПартииНаличиеОстаткиОбороты.Номенклатура) as ИтогПоНоменклатура 
|     , GROUPING(ПартииНаличиеОстаткиОбороты.Партия) as ИтогПоПартия 
|     , GROUPING(ПартииНаличиеОстаткиОбороты.ЦенаПрод) as ИтогПоЦенаПрод 
|     , GROUPING(ПартииНаличиеОстаткиОбороты.СерияНоменклатуры) as ИтогПоСерияНоменклатуры 
|     , GROUPING(ПартииНаличиеОстаткиОбороты.ПозицияДокумента) as ИтогПоПозицияДокумента 
|     
|     , Sum(ПартииНаличиеОстаткиОбороты.КоличествоНачальныйОстаток) as КоличествоНачальныйОстаток 
|     , Sum(ПартииНаличиеОстаткиОбороты.КоличествоПриход) as КоличествоПриход 
|     , Sum(ПартииНаличиеОстаткиОбороты.КоличествоРасход) as КоличествоРасход 
|     , Sum(ПартииНаличиеОстаткиОбороты.КоличествоКонечныйОстаток) as КоличествоКонечныйОстаток 
|     , MAX(Движения.ПродСтоимость) as ПродСтоимостьСумма 
|FROM 
|    $РегистрОстаткиОбороты.ПартииНаличие(:НачДата, :КонДата~, Документ,,, 
|        (Номенклатура = :ВыбНоменклатура), 
|        (Фирма, МОЛ, Номенклатура, Партия, ЦенаПрод, СерияНоменклатуры), 
|        (Количество, СуммаРуб)) as ПартииНаличиеОстаткиОбороты 
|
|LEFT JOIN (
|    select
|        $ПартииНаличие.Фирма Фирма,
|        $ПартииНаличие.МОЛ МОЛ,
|        $ПартииНаличие.Номенклатура Номенклатура,
|        $ПартииНаличие.Партия Партия,
|        $ПартииНаличие.ЦенаПрод ЦенаПрод,
|        $ПартииНаличие.СерияНоменклатуры СерияНоменклатуры,
|        ПартииНаличие.DATE_TIME_IDDOC ПозицияДокумента,
|        Sum($ПартииНаличие.ПродСтоимость) as ПродСтоимость,
|        MAX(ПартииНаличие.LINENO_) as НомерСтрокиДокумента
|    from $Регистр.ПартииНаличие as ПартииНаличие
|    group by
|        $ПартииНаличие.Фирма,
|        $ПартииНаличие.МОЛ,
|        $ПартииНаличие.Номенклатура,
|        $ПартииНаличие.Партия,
|        $ПартииНаличие.ЦенаПрод,
|        $ПартииНаличие.СерияНоменклатуры,
|        ПартииНаличие.DATE_TIME_IDDOC
|) Движения
|    ON ПартииНаличиеОстаткиОбороты.Фирма = Движения.Фирма 
|  AND ПартииНаличиеОстаткиОбороты.МОЛ = Движения.МОЛ 
|  AND ПартииНаличиеОстаткиОбороты.Номенклатура = Движения.Номенклатура 
|  AND ПартииНаличиеОстаткиОбороты.Партия = Движения.Партия 
|  AND ПартииНаличиеОстаткиОбороты.ЦенаПрод = Движения.ЦенаПрод 
|  AND ПартииНаличиеОстаткиОбороты.СерияНоменклатуры = Движения.СерияНоменклатуры
|  AND ПартииНаличиеОстаткиОбороты.ПозицияДокумента = Движения.ПозицияДокумента
|
|GROUP BY 
|    ПартииНаличиеОстаткиОбороты.Фирма 
|    , ПартииНаличиеОстаткиОбороты.МОЛ 
|    , ПартииНаличиеОстаткиОбороты.Номенклатура 
|    , ПартииНаличиеОстаткиОбороты.Партия 
|    , ПартииНаличиеОстаткиОбороты.ЦенаПрод 
|    , ПартииНаличиеОстаткиОбороты.СерияНоменклатуры 
|    , ПартииНаличиеОстаткиОбороты.ПозицияДокумента WITH ROLLUP 

Разбираем работу кода по шагам

Проанализируем ключевые моменты этого запроса:

  1. Виртуальная таблица: Мы используем макрос $РегистрОстаткиОбороты.ПартииНаличие. Обратите внимание, что мы передаем периодичность Документ. Это критически важно, так как реквизиты существуют только в привязке к документу (позиции на временной оси).
  2. Подзапрос Движения: Вместо прямого соединения с $Регистр.ПартииНаличие, мы создаем в секции LEFT JOIN выборку, где группируем данные (GROUP BY) по всем измерениям регистра и по полю DATE_TIME_IDDOC.
  3. Агрегация реквизита: Внутри подзапроса мы используем Sum($ПартииНаличие.ПродСтоимость). Если в одной строке документа было два движения по одной номенклатуре, их ПродСтоимость сложится, что логически верно для итогового отчета. Если нам нужно просто текстовое значение или номер строки, можно использовать MAX() или MIN().
  4. Условия соединения (ON): Мы связываем виртуальную таблицу и подзапрос по максимально полному набору полей. Обязательно включаем ПозицияДокумента. Именно это поле обеспечивает точность связи с конкретным событием в системе.
  5. Использование WITH ROLLUP: Эта конструкция SQL позволяет получить иерархические итоги (группировки) прямо в одном запросе, что очень удобно для построения древовидных отчетов в 1С.

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

Выясним, насколько такой запрос эффективен. При использовании LEFT JOIN с подзапросом, содержащим GROUP BY, важно, чтобы SQL Server мог эффективно использовать индексы.

Важные замечания по производительности:

Подведем итоги

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

Этот метод позволяет создавать мощные, быстрые и информативные отчеты, которые невозможно реализовать стандартными средствами 1С 7.7, сохраняя при этом точность данных и корректность итоговых сумм.

← На главную