Разработчики, работающие с базой данных 1С:Предприятие 7.7 на MS SQL Server, часто сталкиваются с проблемой производительности стандартных бухгалтерских итогов. Когда стандартный метод БухгалтерскиеИтоги.ВыполнитьЗапрос() начинает работать слишком медленно из-за колоссального объема данных, логичным шагом становится использование прямых SQL-запросов.
Однако при попытке написать такой запрос "в лоб" через ADO возникает множество сложностей, связанных с архитектурой хранения бухгалтерских итогов в системе 1С 7.7. В рамках этой статьи мы подробно разберем, как устроено хранение данных в SQL-таблицах 1С, почему классический подход через ADO крайне трудоемок, и как решить задачу элегантно и быстро с помощью классов внешней компоненты 1С++.
Прежде чем приступать к написанию кода, проанализируем структуру хранения данных в СУБД. Вся бухгалтерская информация в 1С 7.7 распределена по нескольким ключевым таблицам:
_1SACCS — план счетов. Хранит список счетов, их коды, признаки валютного, количественного и многоаналитического учета._1SENTRY — проводки. Сюда записываются все первичные движения (дебет, кредит, суммы, количества)._1SBKTTLC — обороты между счетами по месяцам (шахматка)._1SBKTTL — остатки и обороты по счетам в разрезе аналитики (субконто). Именно эта таблица отвечает за хранение итогов по субконто, которые необходимы автору вопроса.Важный нюанс: остатки в таблице _1SBKTTL хранятся только на начало расчетных периодов (в 1С 7.7 это всегда первый день квартала). Если нам нужно получить актуальный остаток на произвольную дату (например, на 18 мая), простая выборка из таблицы остатков не поможет. Нам придется выполнить следующий алгоритм:
_1SBKTTL._1SENTRY начиная с первого дня квартала по указанную дату включительно.Такой расчет вручную на чистом T-SQL требует написания громоздких конструкций со множеством соединений (JOIN) и объединений (UNION ALL).
_1SBKTTL (Итоги по субконто)Если мы все же решим посмотреть на структуру таблицы _1SBKTTL, нам понадобятся следующие ключевые поля:
DATE — дата начала квартала, на который рассчитаны итоги.ACCID — внутренний 9-символьный идентификатор счета (ссылается на поле ID в таблице _1SACCS).KIND — тип итога в строке: 1 — суммовой учет (в рублях), 2 — валютный учет, 3 — количественный учет.SC0, SC1, SC2 — идентификаторы значений субконто 1, 2 и 3 соответственно (например, ссылки на элементы справочников).SD — дебетовый остаток на начало периода.SK — кредитовый остаток на начало периода.Рассмотрим ситуацию, когда нам повезло, и запрашиваемая дата строго совпадает с границей квартала (например, 1 января). В этом случае мы можем обойтись без обращения к таблице проводок _1SENTRY. Напишем концептуальный запрос к базе через ADO, используя подключение, аналогичное коду автора темы:
// Создаем соединение с базой данных SQL
ConnectString = "Provider=SQLOLEDB; Server=192.168.0.100;User ID=sa;Password=your_password;Database=kupec;";
Соединение = CreateObject("ADODB.Connection");
Соединение.ConnectionTimeOut = 10;
Попытка
Соединение.Open(ConnectString);
Исключение
Сообщить("Не удалось подключиться к серверу SQL. ERROR - " + СокрЛП(ОписаниеОшибки()));
Возврат;
КонецПопытки;
Cmd = СоздатьОбъект("ADODB.Command");
Rs = СоздатьОбъект("ADODB.RecordSet");
Cmd.ActiveConnection = Соединение;
Cmd.CommandTimeOut = 15;
Cmd.CommandType = 1; // Запрос на языке SQL
// Формируем текст запроса к таблице итогов по субконто
// Обратите внимание: идентификаторы ACCID и SC0 в реальной базе 1С
// хранятся в виде специфических 9-символьных строк базы 36 (Base36)
ТекстЗапроса = "
|SELECT
| SUM(CASE WHEN KIND = 1 THEN SD - SK ELSE 0 END) AS SumBalance,
| SUM(CASE WHEN KIND = 3 THEN SD - SK ELSE 0 END) AS QtyBalance
|FROM
| _1SBKTTL
|WHERE
| ACCID = ' 1A ' -- Идентификатор счета (например, 41.1)
| AND DATE = '2026-01-01' -- Начало интересующего нас квартала
| AND SC0 = ' 22I ' -- Идентификатор конкретного субконто (Номенклатура)
|";
Cmd.Commandtext = ТекстЗапроса;
Попытка
Rs.ActiveConnection = Соединение;
Rs = Cmd.Execute();
Если Rs.EOF() = 0 Тогда
Сообщить("Суммовой остаток: " + Rs.Fields("SumBalance").Value);
Сообщить("Количественный остаток: " + Rs.Fields("QtyBalance").Value);
КонецЕсли;
Исключение
Сообщить("Ошибка выполнения запроса: " + ОписаниеОшибки());
КонецПопытки;
Недостатки этого метода: нам приходится вручную выяснять внутренние идентификаторы счетов (ACCID) и элементов справочников (SC0), заглядывая в файл словаря данных 1Cv7.DDS. Кроме того, этот код абсолютно не подходит для получения остатков на произвольную дату внутри квартала.
Как совершенно справедливо отметили участники форума, писать прямые запросы через "чистый" ADO — занятие крайне неблагодарное. Специально для решения таких задач сообществом разработчиков была создана внешняя компонента 1С++. Она позволяет использовать типизированные параметры и метасимволы (знак $), которые библиотека автоматически транслирует в физические имена таблиц СУБД.
Разберем, как выглядит аналогичный запрос с использованием ODBCRecordset из библиотеки 1С++:
Запрос = СоздатьОбъект("ODBCRecordset");
// Подготавливаем текст запроса с использованием метасимволов 1С++
ТекстЗапроса = "
|SELECT
| SUM(CASE WHEN KIND = 1 THEN SD - SK ELSE 0 END) AS [Сумма $Число],
| SUM(CASE WHEN KIND = 3 THEN SD - SK ELSE 0 END) AS [Количество $Число]
|FROM
| $Таблица.ИтогиБухгалтерии AS Итоги
|WHERE
| Итоги.ACCID = :ВыбСчет
| AND Итоги.DATE = :НачалоКвартала
| AND Итоги.SC0 = :ВыбСубконто
|";
// Передаем привычные объекты 1С в качестве параметров.
// Компонента самостоятельно конвертирует их во внутренние ID базы данных!
Запрос.УстановитьПараметр("ВыбСчет", ВыбранныйСчет);
Запрос.УстановитьПараметр("НачалоКвартала", НачалоКвартала);
Запрос.УстановитьПараметр("ВыбСубконто", ВыбранныйОбъектСубконто);
// Выполняем инструкцию и получаем результат в виде Таблицы Значений
ТЗ = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
Если ТЗ.КоличествоСтрок() > 0 Тогда
Предупреждение("Сумма: " + ТЗ.ПолучитьЗначение(1, "Сумма") +
"; Кол-во: " + ТЗ.ПолучитьЗначение(1, "Количество"));
КонецЕсли;
Для максимально удобного получения бухгалтерских итогов в 1С++ предусмотрен специализированный класс AccountRecordset (или класс ПрямойЗапрос в зависимости от используемой сборки классов). Он предоставляет мощнейший механизм виртуальных таблиц (похожий на механизмы запросов в платформе 1С 8).
Виртуальная таблица $БИОстатки автоматически берет на себя всю рутину по расчету итогов на любую произвольную дату, самостоятельно собирая остатки из _1SBKTTL и прибавляя обороты из _1SENTRY.
Посмотрим на пример использования класса AccountRecordset для решения задачи автора:
// Создаем объект ПрямойЗапрос или ODBCRecordset с поддержкой бухгалтерских классов
Запрос = СоздатьОбъект("ODBCRecordset");
ТекстЗапроса = "
|SELECT
| Субконто1 [Номенклатура $Справочник.Номенклатура],
| СуммаОстатокДт AS Сумма,
| КоличествоОстатокДт AS Количество
|FROM
| $БИОстатки(:ВыбДата, , :ВыбСчет, , (Номенклатура))
|WHERE
| Субконто1 = :ВыбСубконто
|";
// Заполняем параметры. Передаем ЛЮБУЮ дату (хоть середину месяца!)
Запрос.УстановитьПараметр("ВыбДата", РабочаяДата());
Запрос.УстановитьПараметр("ВыбСчет", СчётПоКоду("41.1"));
Запрос.УстановитьПараметр("ВыбСубконто", ВыбНоменклатура);
ТЗ = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
// Выводим полученный результат
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
Сообщить("Товар: " + ТЗ.Номенклатура +
" | Остаток: " + ТЗ.Количество +
" | На сумму: " + ТЗ.Сумма);
КонецЦикла;
" 22I ".(Номенклатура)), а не физические имена колонок типа SP2464.Таким образом, для эффективной работы с SQL-версией 1С 7.7 настоятельно рекомендуется один раз установить и настроить библиотеку 1С++, избавив себя от необходимости вручную разбирать структуры таблиц хранения бухгалтерских остатков.