При проведении инвентаризации в крупных организациях или при переносе данных часто возникает ситуация, когда число позиций основных средств в одной выборке превышает сотню тысяч строк. При попытке записать такой массив в табличную часть документа разработчики сталкиваются с техническим ограничением платформы 1С — не более 99 999 строк.
Разберем, почему возникает эта ошибка, в чем заключается коварство конструкции ВЫБРАТЬ ПЕРВЫЕ без сортировки и какими способами можно правильно обработать такой объем данных.
Рассмотрим распространенную ошибку, когда разработчик пытается скрестить две части выборки, отсекая первые 99 999 записей с помощью подзапроса вида ГДЕ НЕ ОС В (ВЫБРАТЬ ПЕРВЫЕ 99999 ...).
На практике такой подход приводит к тому, что во вторую часть попадут совсем не те элементы, которые ожидает программист. Происходит это по следующим причинам:
УПОРЯДОЧИТЬ ПО. Без сортировки первый и второй запросы вернут совершенно разные последовательности записей.НЕ В (...) с вложенной виртуальной таблицей остатков на массивах в десятки тысяч элементов заставляет SQL-сервер выполнять тяжелое сканирование вместо использования индексов, что приводит к задержкам и зависаниям базы.Ограничение в 99 999 строк связано со служебным реквизитом НомерСтроки табличных частей документов, который исторически имеет фиксированную длину. Попытка добавить 100 000-ю строку вызывает системную ошибку сохранения объекта.
Кроме того, размещение 100 000 и более элементов на форме документа создает колоссальную нагрузку на оперативную память сервера приложений и клиента, вплоть до аварийного завершения работы платформы.
Наиболее надежный и правильный с точки зрения архитектуры 1С способ — разбивка большого массива данных на несколько документов инвентаризации. Для этого создается внешняя обработка, которая заполняет документы порциями по 80 000 – 90 000 строк.
Проанализируем пример логики формирования документов в цикле:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕПСБУОстатки.Счет КАК Счет,
| ЕПСБУОстатки.Субконто2 КАК ОС,
| ЕПСБУОстатки.Субконто3 КАК ЦМО,
| ЕПСБУОстатки.СуммаОстаток КАК СтоимостьБалансовая
|ИЗ
| РегистрБухгалтерии.ЕПСБУ.Остатки(&ДатаЗапроса, Счет В ИЕРАРХИИ (&Счет101), , Учреждение = &Учреждение) КАК ЕПСБУОстатки";
Запрос.УстановитьПараметр("ДатаЗапроса", ДатаИнвентаризации);
Запрос.УстановитьПараметр("Счет101", Счет101);
Запрос.УстановитьПараметр("Учреждение", Учреждение);
Выборка = Запрос.Выполнить().Выбрать();
СчетчикСтрок = 0;
ЛимитСтрок = 90000; // Безопасный порог для одного документа
ДокументОбъект = Неопределено;
Пока Выборка.Следующий() Цикл
Если ДокументОбъект = Неопределено Или СчетчикСтрок >= ЛимитСтрок Тогда
Если ДокументОбъект <> Неопределено Тогда
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
ДокументОбъект = Документы.ИнвентаризацияОС.СоздатьДокумент();
ДокументОбъект.Дата = ДатаИнвентаризации;
ДокументОбъект.Учреждение = Учреждение;
СчетчикСтрок = 0;
КонецЕсли;
НоваяСтрока = ДокументОбъект.ОС.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
СчетчикСтрок = СчетчикСтрок + 1;
КонецЦикла;
Если ДокументОбъект <> Неопределено И СчетчикСтрок > 0 Тогда
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
Если необходимо выбирать данные порциями с гарантированным порядком и без дублирования, воспользуемся выборкой по уникальному полю (например, коду или ссылкам объектов). В этом случае в каждом следующем запросе выбираются элементы, значение ключа которых строго больше последнего обработанного значения из предыдущей порции.
Рассмотрим схему построения запроса с фильтрацией по границе ключа:
ВЫБРАТЬ ПЕРВЫЕ 90000
ЕПСБУОстатки.Счет КАК Счет,
ЕПСБУОстатки.Субконто2 КАК ОС,
ВЫРАЗИТЬ(ЕПСБУОстатки.Субконто2 КАК Справочник.ОсновныеСредства).Код КАК ОСКод,
ЕПСБУОстатки.СуммаОстаток КАК СтоимостьБалансовая
ИЗ
РегистрБухгалтерии.ЕПСБУ.Остатки(&ДатаЗапроса, Счет В ИЕРАРХИИ (&Счет101), , Учреждение = &Учреждение) КАК ЕПСБУОстатки
ГДЕ
ВЫРАЗИТЬ(ЕПСБУОстатки.Субконто2 КАК Справочник.ОсновныеСредства).Код > &ПоследнийКод
УПОРЯДОЧИТЬ ПО
ОСКод ВОЗР
Выясним главные правила использования этого метода:
УПОРЯДОЧИТЬ ПО по уникальному полю (код справочника, Ссылка и т.д.).&ПоследнийКод пустую строку "".ОСКод из последней строки предыдущей выборки и передаем его в параметр &ПоследнийКод.Обратим внимание на условие, часто встречающееся в текстах запросов:
И ВЫБОР
КОГДА &БалансОтбор = НЕОПРЕДЕЛЕНО
ТОГДА ИСТИНА
ИНАЧЕ Баланс = &БалансОтбор
КОНЕЦ
Важно: Использование логических условий ВЫБОР КОГДА внутри параметров виртуальных таблиц блокирует эффективное использование индексов СУБД. Оптимизатор запросов SQL не может заранее построить план выполнения и переходит к полному сканированию остатков.
Правильнее формировать текст запроса динамически в программном коде перед выполнением. Если параметр задан — подставляем прямое условие Баланс = &БалансОтбор, если не задан — полностью исключаем эту строчку из условий виртуальной таблицы.