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

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

При работе с высоконагруженными базами данных на платформе 1С:Предприятие 7.7 стандартные механизмы получения остатков через объект Регистры могут работать недостаточно быстро, особенно в модулях проведения документов. В таких случаях программисты прибегают к использованию прямых запросов к базе данных (SQL или через SQLite для DBF-версий). В данной статье мы разберем, как эффективно получить задолженность контрагента, учитывая специфику хранения данных в "семерке", и проанализируем структуру связей между справочниками и регистрами.

Особенности структуры данных 1С 7.7

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

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

  1. Владелец справочника: В таблице справочника договоров (обычно это таблица с префиксом SC) ссылка на контрагента хранится в поле PARENTEXT. Многие путают его с полем PARENTID, однако PARENTID отвечает за иерархию внутри самого справочника (группы), а PARENTEXT — за связь с владельцем.
  2. Формат ID: Все ссылки в 1С 7.7 хранятся в формате Base36 (строка из 13 символов). При написании условий в запросе необходимо учитывать этот формат или использовать средства типизации внешних компонент.
  3. Таблицы регистров: Оперативные итоги хранятся в двух типах таблиц: таблицы итогов (префикс 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:

Такой подход позволяет корректно математически складывать движения для получения итогового сальдо в рамках одного запроса SUM().

Оптимизация: Использование виртуальных таблиц 1CPP

Если в вашей конфигурации используется 1CPP, работу можно значительно упростить. Вместо ручного объединения RG и RA, можно вызвать виртуальную таблицу $РегистрОстатки. Рассмотрим, как это выглядит:


|SELECT
|    Договор.PARENTEXT AS [Контрагент :Справочник.Контрагенты],
|    Остатки.СуммаВзаиморасчетовОстаток AS Долг
|FROM
|    $РегистрОстатки.Покупатели(:ДатаРасчета~,,
|                               (Договор), (СуммаВзаиморасчетов), ) AS Остатки
|INNER JOIN [Справочник.Договоры] AS Договор ON Договор.ID = Остатки.Договор
|WHERE
|    Договор.PARENTEXT IN (SELECT Val FROM :СписокКонтрагентов)

Этот метод предпочтительнее по нескольким причинам:

  1. Скорость разработки: Вам не нужно описывать логику периодичности итогов.
  2. Надежность: Компонента сама оптимизирует запрос под конкретную СУБД (MS SQL Server или SQLite).
  3. Читаемость: Код становится намного короче и понятнее для поддержки.

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

При использовании прямых запросов для получения долга, выясним причины возможных замедлений и устраним их:

Проверьте индексы: Для того чтобы соединение со справочником договоров работало быстро, в базе данных должен существовать индекс по полю PARENTEXT. Если его нет, SQL сервер будет вынужден сканировать всю таблицу договоров целиком (Full Table Scan).

Блокировки в SQL: Если вы выполняете запрос в транзакции проведения документа, помните о блокировках. Использование WITH (NOLOCK) в прямых запросах к таблицам итогов может ускорить чтение, но может привести к получению "грязных" данных. Для контроля лимита задолженности лучше читать актуальные зафиксированные данные.

Типизация параметров: Всегда передавайте параметры через метод Подставлять() или используйте типизацию в тексте запроса (например, [Контрагент :Справочник.Контрагенты]). Это предотвратит ошибки приведения типов и обеспечит правильное сопоставление ID в формате Base36.

Таким образом, мы рассмотрели основные способы получения долга контрагента прямым запросом в 1С 7.7. Использование 1CPP и виртуальных таблиц является наиболее современным и эффективным подходом, позволяющим значительно ускорить работу системы в условиях больших объемов данных.

← На главную