При переходе с платформы 1С:Предприятие 8 на 1С 7.7 или при разработке интеграционных механизмов разработчики часто сталкиваются с необходимостью получить только первые несколько записей (например, два последних документа определенного вида). В платформе 1С 8.x для этого предусмотрено ключевое слово ВЫБРАТЬ ПЕРВЫЕ (аналог конструкции SELECT TOP N или LIMIT в реляционных СУБД). Однако во встроенном языке запросов 1С 7.7 аналогичной синтаксической конструкции нет.
Штатный объект Запрос в 1С 7.7 при вызове метода Выполнить() всегда полностью обрабатывает данные по указанному условию Где ... и строит полные временные структуры. Если в выборку попадают тысячи документов, выполнение запроса создает существенную нагрузку на дисковую подсистему и процессор, даже если при последующем обходе мы прерываем цикл после второй записи. Разберем рабочие и эффективные альтернативы решения этой задачи.
В платформе 1С 7.7 штатная объектная выборка документов или справочников зачастую работает значительно быстрее встроенного объекта Запрос. При использовании методов объектной выборки чтение данных осуществляется последовательно по существующим системным индексам без построения промежуточных таблиц на диске.
Рассмотрим, как быстро получить первые два документа определенного вида в заданном периоде:
Док = СоздатьОбъект("Документ.РасходнаяНакладная");
Счетчик = 0;
// Открываем выборку документов за нужный период
Док.ВыбратьДокументы(НачДата, КонДата);
Пока Док.ПолучитьДокумент() = 1 Цикл
// Выполняем действия с полученным документом
Сообщить("Найден документ: " + Док.ТекущийДокумент());
Счетчик = Счетчик + 1;
Если Счетчик >= 2 Тогда
Прервать; // Прекращаем чтение, как только набрали нужное количество
КонецЕсли;
КонецЦикла;
Если требуется фильтрация по конкретному реквизиту (например, по контрагенту или складу), при условии, что для реквизита включен флаг отбора, эффективнее применить метод ВыбратьПоЗначению():
Док = СоздатьОбъект("Документ.РасходнаяНакладная");
Счетчик = 0;
Док.ВыбратьПоЗначению(НачДата, КонДата, "Контрагент", ВыбКонтрагент);
Пока Док.ПолучитьДокумент() = 1 Цикл
Счетчик = Счетчик + 1;
// Обработка документа
Если Счетчик >= 2 Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Данный подход не создает лишней нагрузки на базу, так как считывание прерывается сразу после получения необходимого количества записей.
Если задача выполняется в рамках интеграции через COM-соединение (OLE Automation) из внешней системы или другой базы 1С, мы также можем использовать возможности внешних компонент. Внешняя компонента 1С++ (1cpp.dll) отлично инициализируется внутри сессии COM-сервера 1С 7.7.
Разберем по шагам подключение и выполнение прямого запроса с конструкцией SELECT TOP через COM-объект:
1cpp.dll в контексте сессии 1С 7.7.ODBCRecordSet и выполняем прямой SQL-запрос с ограничением TOP.Посмотрим на пример кода на стороне внешней базы 1С 8.x:
База77 = Новый COMОбъект("V1CEnterprise.Application");
КаталогИБ = "C:\Bases\Trade77\";
Пользователь = "Администратор";
Пароль = "123";
Если База77.Initialize(База77.RMTrade, "/D" + КаталогИБ + " /N" + Пользователь + " /P" + Пароль, "NO_SPLASH_SHOW") Тогда
// Загружаем внешнюю компоненту 1С++ в контексте 7.7
База77.ЗагрузитьВнешнююКомпоненту("1cpp.dll");
// Создаем объект для прямых запросов
ЗапросSQL = База77.СоздатьОбъект("ODBCRecordSet");
// Формируем текст запроса с ограничением TOP 2
ТекстЗапроса = "
|SELECT TOP 2
| Жур.IDDOC as [Документ $Документ]
|FROM
| _1SJOURN AS Жур WITH (NOLOCK)
|WHERE
| Жур.IDDOCDEF = $ВидДокумента.РасходнаяНакладная
| AND Жур.DATE_TIME_IDDOC >= '20230101'
|ORDER BY
| Жур.DATE_TIME_IDDOC ASC";
ТЗ = ЗапросSQL.ВыполнитьИнструкцию(ТекстЗапроса);
// Обрабатываем полученную таблицу значений
КоличествоСтрок = ТЗ.КоличествоСтрок();
Для НомерСтроки = 1 По КоличествоСтрок Цикл
ТекДок = ТЗ.ПолучитьЗначение(НомерСтроки, "Документ");
КонецЦикла;
Иначе
Сообщить("Не удалось подключиться к базе 1С 7.7");
КонецЕсли;
Прямой запрос выполняется СУБД мгновенно, передавая через COM только необходимые строки, что исключает избыточный сетевой трафик и накладные расходы.
Если база небольшая, использование внешней компоненты недопустимо по политикам безопасности, а сложная структура связей требует именно объекта Запрос, можно выгрузить результат во временную структуру:
ТаблицаЗначений с помощью метода Выгрузить().N строк таблицы в цикле.
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "
|Период с НачДата по КонДата;
|Док = Документ.РасходнаяНакладная.ТекущийДокумент;
|Группировка Док упорядочить по Док.ДатаДок;
|";
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
ТЗ = СоздатьОбъект("ТаблицаЗначений");
Запрос.Выгрузить(ТЗ, 0, 0);
Количество = Мин(2, ТЗ.КоличествоСтрок());
Для Стр = 1 По Количество Цикл
ТекДокумент = ТЗ.ПолучитьЗначение(Стр, "Док");
Сообщить("Документ: " + ТекДокумент);
КонецЦикла;
КонецЕсли;
Важно: этот способ наименее производителен при больших массивах данных, так как 1С 7.7 сначала построит полный результат запроса в памяти/файлах подкачки.
Если база данных 1С 7.7 работает под управлением Microsoft SQL Server, для получения первых записей из внешнего приложения вообще не обязательно запускать COM-сервер 1С. Можно подключиться напрямую к СУБД с помощью ADODB.Connection.
Структура таблиц 1С 7.7 (сопоставление имен таблиц и полей) хранится в файле словаря базы данных 1cv7.dds. Запрос с выражением SELECT TOP N ... к таблице журнала _1SJOURN или шапок документов DH<ID> вернет требуемые данные с максимальной скоростью без расхода лицензий и накладных расходов платформы 1С 7.7.
Проанализируем оптимальный выбор инструмента под конкретные условия:
ВыбратьДокументы() или ВыбратьПоЗначению() со счетчиком и оператором Прервать.1С++ (класс ODBCRecordSet) с инструкцией SELECT TOP N.