При работе с высоконагруженными базами данных на платформе 1С:Предприятие 7.7 стандартные механизмы получения остатков через объект Регистры могут работать недостаточно быстро, особенно в модулях проведения документов. В таких случаях программисты прибегают к использованию прямых запросов к базе данных (SQL или через SQLite для DBF-версий). В данной статье мы разберем, как эффективно получить задолженность контрагента, учитывая специфику хранения данных в "семерке", и проанализируем структуру связей между справочниками и регистрами.
Прежде чем приступать к написанию запроса, нам необходимо разобраться, как 1С 7.7 связывает контрагентов с их задолженностью. В большинстве конфигураций (например, "Торговля и Склад") задолженность хранится в разрезе Договоров, а не самих контрагентов напрямую. Договор является подчиненным справочником по отношению к контрагенту.
Для реализации прямого запроса крайне важно знать следующие технические моменты:
SC) ссылка на контрагента хранится в поле PARENTEXT. Многие путают его с полем PARENTID, однако PARENTID отвечает за иерархию внутри самого справочника (группы), а PARENTEXT — за связь с владельцем.RG) и таблицы движений (префикс RA).Для работы с прямыми запросами чаще всего используется внешняя компонента 1CPP. Она позволяет использовать метаимена (в квадратных скобках), которые компонента сама преобразует в реальные имена таблиц SQL. Рассмотрим ситуацию, когда нам нужно получить долг по списку контрагентов.
Проанализируем базовый синтаксис соединения таблиц для фильтрации по владельцу (контрагенту) в синтаксисе SQLite/SQL:
|INNER JOIN [Справочник.Договоры] AS Договор ON Договор.ID = Рег.Договор
|AND Договор.PARENTEXT IN (SELECT Val FROM :ВыбКонтрагент)
В данном примере мы используем виртуальную таблицу или переданный список :ВыбКонтрагент, чтобы отфильтровать только те договоры, которые принадлежат нужным нам клиентам.
Одной из самых сложных задач в 1С 7.7 является расчет остатков на конкретный момент времени (не на текущую точку актуальности). Для этого нам нужно объединить данные из таблицы итогов за предыдущий период и движения за текущий межитоговый период. Рассмотрим подробный пример кода, который можно использовать непосредственно в модуле проведения документа для контроля лимита кредита.
Для начала инициализируем объекты и подготовим параметры запроса:
мд = СоздатьОбъект("MetaDataWork");
АктуальностьИтогов = ИтогиАктуальны();
НачПериод = мд.ПолучитьНачПериода(ДатаДок); // Начало текущего месяца
ПредПериод = мд.ПолучитьНачПериода(НачПериод - 1); // Начало предыдущего месяца
Запрос.Подставлять("НачПериод", НачПериод);
Запрос.Подставлять("КонПериод", ТекущийДокумент()); // Граница расчета
Запрос.Подставлять("ВыбКонтрагент", ВыбКонтрагент);
Теперь составим текст самого SQL-запроса. Обратите внимание на использование конструкции UNION ALL для объединения итогов и движений:
ТекстЗапроса = "
|SELECT
| Договор.PARENTEXT AS [Контрагент :Справочник.Контрагенты],
| SUM(Рег.Сумма) AS [Долг :Число]
|FROM (
| -- Получаем итоги на начало текущего месяца
| SELECT
| Итоги.Договор AS Договор,
| Итоги.СуммаВзаиморасчетов AS Сумма
| FROM
| [РегистрИтоги.Покупатели] AS Итоги
| WHERE
| Итоги.PERIOD = :ПредПериод
|";
Если АктуальностьИтогов = 0 Тогда
// Если мы считаем долг не на ТА, добавляем движения текущего периода
ТекстЗапроса = ТекстЗапроса + "
| UNION ALL
|
| SELECT
| Движения.Договор,
| Движения.СуммаВзаиморасчетов * (1 - Движения.DEBKRED * 2)
| FROM
| [Регистр.Покупатели] AS Движения
| WHERE
| Движения.idx_date_time_iddoc_lineno_actno BETWEEN :НачПериод AND :КонПериод~~~
|";
КонецЕсли;
ТекстЗапроса = ТекстЗапроса + "
|) AS Рег
|INNER JOIN [Справочник.Договоры] AS Договор ON Договор.ID = Рег.Договор
|AND Договор.PARENTEXT = :ВыбКонтрагент
|GROUP BY Договор.PARENTEXT
|";
Проанализируем важную формулу в запросе: Движения.СуммаВзаиморасчетов * (1 - Движения.DEBKRED * 2). В регистрах остатков 1С 7.7 тип движения (приход или расход) определяется полем DEBKRED:
DEBKRED = 0 (Приход), формула превращается в Сумма * (1 - 0) = Сумма (положительное значение).DEBKRED = 1 (Расход), формула превращается в Сумма * (1 - 2) = -Сумма (отрицательное значение).Такой подход позволяет корректно математически складывать движения для получения итогового сальдо в рамках одного запроса SUM().
Если в вашей конфигурации используется 1CPP, работу можно значительно упростить. Вместо ручного объединения RG и RA, можно вызвать виртуальную таблицу $РегистрОстатки. Рассмотрим, как это выглядит:
|SELECT
| Договор.PARENTEXT AS [Контрагент :Справочник.Контрагенты],
| Остатки.СуммаВзаиморасчетовОстаток AS Долг
|FROM
| $РегистрОстатки.Покупатели(:ДатаРасчета~,,
| (Договор), (СуммаВзаиморасчетов), ) AS Остатки
|INNER JOIN [Справочник.Договоры] AS Договор ON Договор.ID = Остатки.Договор
|WHERE
| Договор.PARENTEXT IN (SELECT Val FROM :СписокКонтрагентов)
Этот метод предпочтительнее по нескольким причинам:
При использовании прямых запросов для получения долга, выясним причины возможных замедлений и устраним их:
Проверьте индексы: Для того чтобы соединение со справочником договоров работало быстро, в базе данных должен существовать индекс по полю PARENTEXT. Если его нет, SQL сервер будет вынужден сканировать всю таблицу договоров целиком (Full Table Scan).
Блокировки в SQL: Если вы выполняете запрос в транзакции проведения документа, помните о блокировках. Использование WITH (NOLOCK) в прямых запросах к таблицам итогов может ускорить чтение, но может привести к получению "грязных" данных. Для контроля лимита задолженности лучше читать актуальные зафиксированные данные.
Типизация параметров: Всегда передавайте параметры через метод Подставлять() или используйте типизацию в тексте запроса (например, [Контрагент :Справочник.Контрагенты]). Это предотвратит ошибки приведения типов и обеспечит правильное сопоставление ID в формате Base36.
Таким образом, мы рассмотрели основные способы получения долга контрагента прямым запросом в 1С 7.7. Использование 1CPP и виртуальных таблиц является наиболее современным и эффективным подходом, позволяющим значительно ускорить работу системы в условиях больших объемов данных.