В процессе оптимизации и ускорения отчетов в системе 1С:Предприятие 7.7 разработчики часто сталкиваются с необходимостью замены стандартного механизма БухгалтерскиеИтоги на прямые SQL-запросы к базе данных. Особенно остро эта проблема встает в базах данных крупного размера, где расчет остатков по субконто «Основные средства» и «Материально-ответственные лица (МОЛ)» штатными средствами может занимать минуты.
В этой статье мы подробно разберем, как устроена структура хранения бухгалтерских итогов в SQL-базе 1С 7.7, проанализируем типичные ошибки при написании таких запросов и рассмотрим два правильных способа решения задачи: «чистый» SQL-запрос и профессиональное решение с использованием внешней компоненты 1С++.
---
Для того чтобы написать эффективный и быстрый SQL-запрос, нам необходимо отказаться от привычных объектов метаданных 1С и понять, как данные хранятся на уровне СУБД. Бухгалтерский учет в версии 7.7 опирается на три ключевые таблицы:
_1SACCS (План счетов): Содержит информацию о счетах, их кодах (SCHKOD), признаках активности (ACTIVE) и идентификаторах (ID)._1SBKTTL (Таблица итогов): Хранит рассчитанные остатки и обороты в разрезе периодов (кварталов). Именно сюда 1С записывает свернутые данные для ускорения работы. Здесь находятся поля субконто SC0, SC1, SC2 и соответствующие им виды субконто VSC0, VSC1, VSC2._1SENTRY (Проводки / Журнал операций): Содержит детальные записи о каждой проводке. Поля ACCDTID (счет дебета), ACCKTID (счет кредита), DTSC0–DTSC2 (субконто дебета), KTSC0–KTSC2 (субконто кредита) и сумма SUM_ являются основными для получения оборотов за произвольный период.Важно помнить, что физическое расположение конкретного субконто в полях SC0, SC1 или SC2 таблицы итогов жестко привязано к порядку субконто на счете в конфигураторе. Если для счета 01 «Основные средства» первым субконто задан справочник «Основные средства», а вторым — «Места хранения» (МОЛ), то идентификатор ОС всегда будет записываться в поле SC0, а идентификатор МОЛ — в поле SC1.
---
Проанализируем фрагмент кода, представленный в Сообщении 15 темы форума. В нем автор совершает несколько грубых архитектурных ошибок:
DH*): Автор пытается связать проводки с таблицами конкретных документов перемещения и ввода в эксплуатацию (через UNION ALL). Это в корне неверно. Все проводки по счетам учета ОС уже автономно содержат ссылки на объекты субконто непосредственно в полях таблицы _1SENTRY. Обращение к таблицам документов приводит к колоссальному падению производительности и потере универсальности (запрос не увидит проводки ручных операций или новых видов документов).DESCR): Конструкции вида ON (СпрОС.DESCR = БИ.НаименованиеСубконто1) недопустимы в SQL. Поиск и слияние строк по текстовым полям происходят крайне медленно, а при наличии дублей в наименованиях ОС вы получите некорректно удвоенные или утроенные суммы. Соединение необходимо выполнять строго по уникальным буквенно-цифровым идентификаторам 1С типа char(9) (поля ID).---
Для того чтобы посмотреть, какой именно SQL-запрос генерирует сама платформа при вызове методов БухгалтерскиеИтоги, выполните следующие действия:
DatabaseName).SQL:BatchCompleted и RPC:Completed.ВыполнитьЗапрос(). Это даст вам готовую пищу для анализа структуры запроса под вашу конфигурацию.---
Рассмотрим правильный алгоритм получения остатков ОС в разрезе МОЛ без использования сторонних компонент. Нам потребуется определить остатки на начало ближайшего квартала из таблицы _1SBKTTL и скорректировать их оборотами из таблицы _1SENTRY за период от начала квартала до требуемой даты.
Проанализируем пример такого SQL-запроса:
DECLARE @DateTarget datetime;
DECLARE @DateQuarterBegin datetime;
DECLARE @Acc01 ID char(9);
DECLARE @MolID char(9);
-- Задаем параметры (идентификаторы счетов и субконто предварительно получаем из метаданных)
SET @DateTarget = '20231015'; -- Дата, на которую нужны остатки
SET @DateQuarterBegin = '20231001'; -- Начало квартала, в который попадает дата
SET @Acc01 = ' 1R '; -- Внутренний ID счета 01.1 в таблице _1SACCS
SET @MolID = ' 2W0 '; -- Идентификатор искомого МОЛ в справочнике SC208
-- Выбираем остатки ОС по конкретному МОЛ
SELECT
ResultTable.OsId AS [Идентификатор_ОС],
SprOS.DESCR AS [Наименование_ОС],
SUM(ResultTable.BalanceDebet) AS [Остаток_Дт]
FROM (
-- Шаг 1: Получаем остатки на начало квартала из таблицы итогов
SELECT
SC0 AS OsId,
SD AS BalanceDebet
FROM _1SBKTTL (NOLOCK)
WHERE ACCID = @Acc01
AND DATE = @DateQuarterBegin
AND KIND = 1 -- признак аналитических итогов
AND SC1 = @MolID
UNION ALL
-- Шаг 2: Прибавляем дебетовые обороты с начала квартала по целевую дату
SELECT
DTSC0 AS OsId,
SUM_ AS BalanceDebet
FROM _1SENTRY (NOLOCK)
WHERE ACCDTID = @Acc01
AND DATE_TIME_DOCID >= @DateQuarterBegin
AND DATE_TIME_DOCID < DATEADD(day, 1, @DateTarget)
AND DTSC1 = @MolID
UNION ALL
-- Шаг 3: Вычитаем кредитовые обороты с начала квартала по целевую дату
SELECT
KTSC0 AS OsId,
-SUM_ AS BalanceDebet
FROM _1SENTRY (NOLOCK)
WHERE ACCKTID = @Acc01
AND DATE_TIME_DOCID >= @DateQuarterBegin
AND DATE_TIME_DOCID < DATEADD(day, 1, @DateTarget)
AND KTSC1 = @MolID
) AS ResultTable
INNER JOIN SC11450 AS SprOS (NOLOCK) ON ResultTable.OsId = SprOS.ID
GROUP BY ResultTable.OsId, SprOS.DESCR
HAVING SUM(ResultTable.BalanceDebet) > 0;
Этот запрос отрабатывает мгновенно благодаря использованию кластеризованных индексов по полям дат и счетов. Мы полностью исключили тяжелое чтение документов и непроизводительное текстовое сравнение.
---
Если в вашей конфигурации зарегистрирована и используется внешняя компонента 1С++, решение задачи упрощается в разы. Нам больше не нужно вручную прописывать сложную логику вычисления квартальных срезов и соединений таблиц остатков и оборотов. Класс AccountsRecordset предоставляет мощный механизм «виртуальных таблиц» (аналог встроенного языка запросов 1С 8.х).
Разберем по шагам пример кода на встроенном языке 1С 7.7 с использованием метазапроса 1С++:
Процедура СформироватьОстаткиОС()
ДП = СоздатьОбъект("ДанныеРаботы"); // Создаем контекст 1С++
// Инициализируем Прямой Запрос через класс AccountsRecordset
Запрос = СоздатьОбъект("ODBCRecordset");
ТекстЗапроса = "
|SELECT
| Рег.Субконто1 AS [ОС $Справочник.ОсновныеСредства],
| Рег.Субконто2 AS [МОЛ $Справочник.ФизическиеЛица],
| Рег.СуммаОстатокДт AS СуммаОстаток
|FROM
| $БИ.Остатки.Основной(
| :ВыбДата,
| ,
| Счет = :ВыбСчет,
| (Субконто2 = :ВыбМОЛ)
| ) AS Рег
|";
Запрос.УстановитьТекстовыйПараметр("ВыбДата", КонДата);
Запрос.УстановитьТекстовыйПараметр("ВыбСчет", глНайтиСчет("01.1"));
Запрос.УстановитьТекстовыйПараметр("ВыбМОЛ", ВыбранныйМОЛ);
// Выполняем запрос и выгружаем результат в Таблицу Значений
ТЗ = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
// Выводим результат в консоль или таблицу
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
Сообщить("ОС: " + ТЗ.ОС + " | МОЛ: " + ТЗ.МОЛ + " | Остаток: " + ТЗ.СуммаОстаток);
КонецЦикла;
КонецПроцедуры
В данном примере конструкция $БИ.Остатки.Основной — это мощный виртуальный генератор запросов. Внутри себя компонента 1С++ сама определяет, из каких физических таблиц СУБД (будь то _1SBKTTL или _1SENTRY) нужно собрать данные на указанную дату :ВыбДата, автоматически применяет нужные фильтры по счету и субконто и отдает программисту готовую, типизированную таблицу результатов.
Использование этого подхода позволяет сократить время разработки в несколько раз, исключает возникновение случайных ошибок при расчете остатков на границах периодов и сохраняет переносимость кода при обновлении конфигурации.