При работе с платформой 1С 7.7 программисты часто сталкиваются с ограничениями штатных «черных» запросов, особенно когда объем базы данных перерастает лимиты формата DBF или когда требуется высокая скорость формирования отчетов. Использование компоненты 1С++ и прямых SQL-запросов позволяет обойти эти ограничения. Однако возникает специфическая сложность: как получить значения реквизитов регистра в запросе по остаткам и оборотам?
В этой статье мы подробно разберем, почему возникают дубли при соединении таблиц, как правильно структурировать запрос к виртуальным таблицам $РегистрОстаткиОбороты и как корректно «подтянуть» данные из таблицы движений.
Давайте проанализируем структуру хранения данных в 1С 7.7. Регистры остатков хранятся в двух физических таблицах SQL:
Когда мы используем виртуальную таблицу $РегистрОстаткиОбороты, 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
Проанализируем ключевые моменты этого запроса:
$РегистрОстаткиОбороты.ПартииНаличие. Обратите внимание, что мы передаем периодичность Документ. Это критически важно, так как реквизиты существуют только в привязке к документу (позиции на временной оси).$Регистр.ПартииНаличие, мы создаем в секции LEFT JOIN выборку, где группируем данные (GROUP BY) по всем измерениям регистра и по полю DATE_TIME_IDDOC.Sum($ПартииНаличие.ПродСтоимость). Если в одной строке документа было два движения по одной номенклатуре, их ПродСтоимость сложится, что логически верно для итогового отчета. Если нам нужно просто текстовое значение или номер строки, можно использовать MAX() или MIN().ПозицияДокумента. Именно это поле обеспечивает точность связи с конкретным событием в системе.Выясним, насколько такой запрос эффективен. При использовании LEFT JOIN с подзапросом, содержащим GROUP BY, важно, чтобы SQL Server мог эффективно использовать индексы.
Важные замечания по производительности:
RA имеют индексы по измерениям и полю DATE_TIME_IDDOC. Если в условии соединения указана номенклатура и позиция документа, SQL-планировщик с большой вероятностью выберет Index Seek, что работает очень быстро.DATE_TIME_IDDOC обладает высочайшей селективностью, так как оно сужает поиск до конкретного документа.OPTION (MAXDOP 1), чтобы предотвратить неэффективное распараллеливание.#Temp), создать по ней индексы, и только потом соединять с движениями.Мы рассмотрели механизм получения реквизитов регистра с использованием прямых запросов. Главный вывод: никогда не соединяйте таблицу остатков с таблицей движений напрямую, если есть риск дублирования строк в документах. Всегда используйте предварительную группировку в подзапросе или временной таблице.
Этот метод позволяет создавать мощные, быстрые и информативные отчеты, которые невозможно реализовать стандартными средствами 1С 7.7, сохраняя при этом точность данных и корректность итоговых сумм.