Обработка элементов ссылочных типов порциями

11.02.19

Разработка - Запросы

Небольшое дополнение к рекомендации от 1С по оптимизации использования оперативной памяти (https://its.1c.ru/db/v8std#content:2149184374:hdoc).

В рекомендации от 1С Оптимизация использования оперативной памяти предлагается получать данные порциями при потенциально неограниченных выборках.

В качестве примера приводится такой участок кода:

ВсеОбработано = Ложь;
Пока Истина Цикл
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Наименование КАК Наименование,
	|	Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	<условие выборки необработанных записей>";
	
	РезультатЗапроса = Запрос.Выполнить();
	ВсеОбработано = РезультатЗапроса.Пустой();
	Если ВсеОбработано Тогда
		Прервать;
	КонецЕсли;
	
	// Обход порции результата запроса
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
	Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
		// Обработка элемента выборки
		// ...
	КонецЦикла;
	
КонецЦикла;

Но зачастую бывает сложно подобрать это самое "условие выборки необработанных записей".

Я предлагаю простое решение для выборки ссылочных данных: использовать в качестве указателя на порцию данных ссылку.

Для этого необходимо внести следующие изменения:

  • Перед погружением в цикл запросов получаем пустую ссылку (она всегда меньше любой непустой ссылки) и записываем ее в переменную указывающую на последнюю полученную ссылку (ПоследняяСсылка).
  • В запросе упорядочиваем выборку по ссылке (не включая автоупорядочивание, т.к. это приведет к сортировке по представлению) и добавляем отбор по ссылке (Ссылка > &ПоследняяСсылка).
  • На каждом проходе выборки обновляем переменную ПоследняяСсылка, присваивая ей текущую обрабатываемую ссылку.

Получим следующий код:

ВсеОбработано = Ложь;
ПоследняяСсылка = Справочники.Номенклатура.ПустаяСсылка();
Пока Истина Цикл
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Наименование КАК Наименование,
	|	Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	Номенклатура.Ссылка > &ПоследняяСсылка
	|	И <условия отбора требуемые для прикладной задачи>
	|
	|УПОРЯДОЧИТЬ ПО
	|	Ссылка";
	Запрос.УстановитьПараметр("ПоследняяСсылка",ПоследняяСсылка);	
	РезультатЗапроса = Запрос.Выполнить();
	ВсеОбработано = РезультатЗапроса.Пустой();
	Если ВсеОбработано Тогда
		Прервать;
	КонецЕсли;
	
	// Обход порции результата запроса
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
	Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
		ПоследняяСсылка = ВыборкаДетальныеЗаписи.Ссылка;
		// Обработка элемента выборки
		// ...
	КонецЦикла;
	
КонецЦикла;

Предложенное решение лежит на поверхности, но мне в голову пришло не сразу, так что, надеюсь, может быть полезным :)

П.С. Я намеренно не стал проводить рефакторинг кода с ИТС, чтобы добавленный код был более заметен. На мой взгляд создавать запрос стоит перед циклом, а не внутри него и нет необходимости создавать переменную ВсеОбработано.

Код после рефакторинга
 

См. также

SALE! 20%

Infostart Toolkit: Инструменты разработчика 1С 8.3 на управляемых формах

Инструментарий разработчика Роли и права Запросы СКД Платформа 1С v8.3 Управляемые формы Запросы Система компоновки данных Конфигурации 1cv8 Платные (руб)

Набор инструментов программиста и специалиста 1С для всех конфигураций на управляемых формах. В состав входят инструменты: Консоль запросов, Консоль СКД, Консоль кода, Редактор объекта, Анализ прав доступа, Метаданные, Поиск ссылок, Сравнение объектов, Все функции, Подписки на события и др. Редактор запросов и кода с раскраской и контекстной подсказкой. Доработанный конструктор запросов тонкого клиента. Продукт хорошо оптимизирован и обладает самым широким функционалом среди всех инструментов, представленных на рынке.

13000 10400 руб.

02.09.2020    122175    670    389    

714

Для чего используют конструкцию запроса "ГДЕ ЛОЖЬ" в СКД на примере конфигурации 1С:ERP

Запросы СКД Платформа 1С v8.3 Запросы Система компоновки данных 1С:ERP Управление предприятием 2 Бесплатно (free)

В типовых конфигурациях разработчики компании 1С иногда используют в отчетах, построенных на СКД, такую конструкцию, как "ГДЕ ЛОЖЬ". Такая конструкция говорит о том, что данные в запросе не будут получены совсем. Для чего же нужен тогда запрос?

13.02.2024    5746    KawaNoNeko    23    

23

Набор-объект для СКД по тексту или запросу

Запросы СКД Платформа 1С v8.3 Управляемые формы Конфигурации 1cv8 Абонемент ($m)

Есть список полей в виде текста, или запрос - закидываем в набор СКД.

1 стартмани

31.01.2024    2000    2    Yashazz    0    

29

Запрос 1С copilot

Инструментарий разработчика Запросы Платформа 1С v8.3 Управляемые формы Конфигурации 1cv8 Абонемент ($m)

Пишем на человеческом языке, что нам надо, и получаем текст запроса на языке 1С. Используются большие языковые модели (LLM GPT) от OpenAI или Яндекс на выбор.

5 стартмани

15.01.2024    6286    31    mkalimulin    25    

50

PrintWizard: поддержка представлений ЗУП в конструкторе

Инструментарий разработчика Запросы Платформа 1С v8.3 Бесплатно (free)

Одной из интересных задач, стоящих в процессе разработки, была поддержка механизма представлений в ЗУП. Но не просто возможность исполнения запросов с ними. Основная проблема была в том, чтобы с ними было удобно работать, а именно: создавать, модифицировать и отлаживать. Кратко о том, что в итоге получилось...

14.12.2023    1742    vandalsvq    7    

29

Объектная модель запроса "Схема запроса" 2

Запросы Платформа 1С v8.3 Запросы Конфигурации 1cv8 Бесплатно (free)

Далеко уже не новый тип данных "Схема запроса". Статья о том, как использовать его "попроще". Примеры создания текста запроса с нуля и изменение имеющегося запроса.

06.12.2023    5388    user1923546    26    

43

Начните уже использовать хранилище запросов

HighLoad оптимизация Запросы

Очень немногие из тех, кто занимается поддержкой MS SQL, работают с хранилищем запросов. А ведь хранилище запросов – это очень удобный, мощный и, главное, бесплатный инструмент, позволяющий быстро найти и локализовать проблему производительности и потребления ресурсов запросами. В статье расскажем о том, как использовать хранилище запросов в MS SQL и какие плюсы и минусы у него есть.

11.10.2023    16186    skovpin_sa    14    

98
Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. A_Max 19 11.02.19 14:55 Сейчас в теме
Это конечно хорошо, но ВНЕЗАПНО может появиться объект с УИД меньше последнего обработанного.
Почему:
1. Никто вам не обещал и гарантирет того, что уиды генерируются последовательно.
2. Обмены

Поэтому и создаются регистры сведений ОчередьХХХХХХХХ
Артано; dsdred; Irwin; +3 Ответить
2. triviumfan 92 11.02.19 18:11 Сейчас в теме
(1) такие вещи делают в монопольном режиме имхо
Plotks2017; +1 1 Ответить
7. lunjio 66 11.02.19 20:53 Сейчас в теме
(1) Речь просто о необходимости порционно обрабатывать информацию, в случае что вы описываете, есть ещё необходимость последовательности, ну хорошо, в этом случае используем МоментВремени() ссылки и вопрос решен. Регистры же, как правило создают для отложенной обработки, в статье же в первом предложении говорится, что порционно при неограниченно больших данных.
8. Eleepod 59 12.02.19 00:27 Сейчас в теме
(1)Согласен, в случае если во время обработки в системе могут появится новые данные они могут быть не обработаны. Данный вариант стоит рассматривать как альтернативу запросу, который получает все данные за одну выборку.
dsdred; Plotks2017; +2 Ответить
23. markers 274 14.02.19 04:51 Сейчас в теме
(8) А что изменится если запросить сразу все? При запросе всей информации данный риск даже выше. И данный способ настолько прост и от этого хорош! При этом не надо добавлять реквизиты и пр.
11. Darklight 32 12.02.19 13:15 Сейчас в теме
(1)Просто так такие UID не появляются. У них есть стандарт генерирования (у 1С он свой UUID, у мелкомягких свой - GUID) - и он хронологический. Вариантов появления младших UID только два:
1. Обмен данных с другой системой, формирующих UID на другом компьютере, выполненный параллельно с обработкой
2. UID задаётся вручную строкой не по тем же правилам его формирования - опять таки каким-то параллельным процессом

Для данного примера вероятен только п.1. (п.2. почти невероятен - но кто его знает, что там пишу криворукие программисты на местах)
Поэтому, можно на время обработки остановить обмен. Это вообще важное замечание для ряда подобных обработок - ведь появление пропущенной ссылки тут не самое страшное, что может произойти, когда в данные вклинится параллельный процесс.
Ну или, хотя бы блокировку данных надо наложить (увы - в данном примере - на всю таблицу)!

Да, кстати, замечу, что если вообще не заморачиваться - и выбрать все Ссылки разом а потом их обрабатывать - то это так же ни гарантирует, что в процессе обработки не появятся новые Ссылки (в любой "хронологии"), которые останутся не обработанными - проблема не повторяемого чтения (или наоборот - будут удалены - проблема фантомов). Вот для этого и существуют блокировки данных!
Ali1976; dsdred; Krio2; acanta; +4 Ответить
3. lemz 123 11.02.19 18:32 Сейчас в теме
Хочется убивать конечно уже за использование запроса в цикле. Ограничение выборки хотя бы по ссылке имеет здравое зерно
6. lunjio 66 11.02.19 19:38 Сейчас в теме
(3) Запросы в цикле не всегда зло, есть таблица 10 гб размером, сервак просто помирает при попытке выбрать из нее, как сделать как ни порционно и в цикле ? Убивать хочется за тупую архитектуру и кривую логику, когда люди вместо того чтобы попытаться найти фунционал или взять удачный пример из типовых или других решений, придумывают велосипеды, да причем очень кустарные.
NoRazum; Артано; Plotks2017; +3 Ответить
10. Shmell 533 12.02.19 11:29 Сейчас в теме
(3) такой подход очень оправдан. При этом у Вас не засоряется кэш объектов вследствии использования ПолучитьОбъект(), и немаловажный момент - что Вы можете параллелить обработку порций объектов разными фоновыми заданиями.
Артано; Plotks2017; +2 Ответить
14. Darklight 32 12.02.19 13:28 Сейчас в теме
(3)Не понимаю к чему Вы так предрались. Запросы в цикле - это не есть абсолютное зло, особенно когда это цикли порционной обработки. При работе с Big Data так вообще без этого НИКАК не обойтись!

Вот создавать каждый раз запрос в цикле - не очень красиво, но для данного примера - абсолютно не критично!
Plotks2017; for_sale; +2 Ответить
4. acanta 11.02.19 18:35 Сейчас в теме
То есть сравнение на больше меньше работает по Гуиду как по строке?
for_sale; +1 Ответить
9. Eleepod 59 12.02.19 01:11 Сейчас в теме
(4) Примерно, но только не в том представлении как оно выводится методом УникальныйИдентификатор. Например ГУИД dee6e178-55bc-11d9-848a-00112f435cbd при ошибке "объект не найден" будет выведен как 848a00112f435cbd11d955bcdee6e178, т.е. вид A-B-C-D-E превращается в DECBA. Думаю в таком виде они и сравниваются.
18. kuzyara 1900 13.02.19 07:41 Сейчас в теме
(9) гуиды имеют тип binary(16) и сравниваются побитово как бинарные данные: binary bit-for-bit comparison behavior
Функция ПолучитьGUIDПоУникальномуИдентификатору(UUID1)
    UUID=ВРЕГ(UUID1);
    ч1 = Сред(UUID,20,4);
    ч2 = Сред(UUID,25,12);
    ч3 = Сред(UUID,15,4);
    ч4 = Сред(UUID,10,4);
    ч5 = Сред(UUID,1,8);
    Возврат "0x" + ч1 + ч2 + ч3 + ч4 + ч5;
КонецФункции
Показать

А если кто-то как Darklight думает что гуиды формируются хронологически - почитайте статью Как формируется GUID?

Гуиды выдаются пулом по 32 штуки для каждого сеанса отдельно, за исключением random based uuid получаемых через Новый УникальныйИдентификатор()
5. lunjio 66 11.02.19 19:36 Сейчас в теме
Боже мой, уже весь мир давно использует, посмотрите процедуры корректировки стоимости списания в УПП, ПЕРВЫЕ 3000 ГДЕ &ПоследнийОбъектОчереди = НЕОПРЕДЕЛЕНО ИЛИ Объект.Ссылка > &ПоследнийОбъектОчереди УПОРЯДОЧИТЬ ПО Объект.Ссылка.
dsdred; Irwin; Shmell; +3 Ответить
12. Darklight 32 12.02.19 13:18 Сейчас в теме
(5)Думаю не весь мир. Решение очевидно понятное (если ссылки сортируются, значит они могут сравниваться), да неприглядное ;-) не привычно вот таки воспринимать ссылки большинству!
13. Darklight 32 12.02.19 13:25 Сейчас в теме

в данном случае нельзя сортировать данные по другим полям, кроме ссылки, так что если нужно обрабатывать данные в определенной последовательности, то такой метод не подойдет.

Зачем так людей пугаете. Конечно же можно - если поля сортировки будут ниже поля упорядочивания по ссылеке - то никаких проблем (но это бывает редко - когда ссылка это и есть ключевой обрабатываемый объект)
А вот, если поля для сортировки стоят раньше ссылки - то тогда их так же придётся включить в контроллируемые (по классическому подходу) - и пример несколько усложнится, потеряв своё изначальное изящество - но будет работать. Хоршо бы и такой пример привести тоже.



Так же следует учитывать, что сортировка по ссылке не означает сортировку по моменту времени.

Если речь про момент времени документов - то да, Ссылка <> МоментВремени - но там как раз Ссылку лучше заменить на МоментВремени - он так же уникален (и не повторяем) как и Ссылка.
Если же имелся в виду какой-то другой момент времени - то надо уточнить - какой и в чём проблема!
17. Eleepod 59 12.02.19 22:45 Сейчас в теме
(13) Похоже про сортировку я действительно не прав. А насчет ссылок и моментов времени хотел предостеречь от заблуждения что сортировка по ссылке равна сортировке по моменту времени. Уберу пока эти моменты. Спасибо за комментарии!
15. lunjio 66 12.02.19 13:37 Сейчас в теме
Единственный трындец, который может наступить, это допустим когда мы не прямую таблицу типа обрабатываем, допустим справочника номенклатуры или документа, а какой-нибудь регистр сведений и за такое поле принимаем измерение у которого может пустая ссылка в измерении и таких ссылок больше, чем обрабатываемая порция, вот тогда да, конец света )
16. Darklight 32 12.02.19 17:57 Сейчас в теме
(15)У Вас пример, немного из другой оперы, хотя автор в теме статьи не ограничил её только обработкой источников, являющихся только объектными (аля справочников и документов). Да, там где обрабатываемый объект не является ключевым (как ссылка) для выборки - эта техника не сгодится. Но ничего особо сложного - делаете структуру со свойствами - где все ключевые измерения и сохраняеете текующую позицию туда. В запросе по ним всем сортируете, а условие будет на "ИЛИ" по этим измерениям (и так же на ">"), а в цикле обновляете их все из данных выборки в структуре через ЗаполнитьЗначенияСвойств - как -то так - вроде ничего особенного - код сложнее где-то на (10 + количество ключевых полей) процентов.
19. Glebis 13 13.02.19 09:29 Сейчас в теме
Предлагаю ещё немного рефакторинга:
 	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Наименование КАК Наименование,
	|	Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	Номенклатура.Ссылка > &ПоследняяСсылка
	|	И <условие выборки необработанных записей>
	|
	|УПОРЯДОЧИТЬ ПО
	|	Ссылка";
	Запрос.УстановитьПараметр("ПоследняяСсылка", Справочники.Номенклатура.ПустаяСсылка());
	РезультатЗапроса = Запрос.Выполнить();
	Пока НЕ РезультатЗапроса.Пустой() Цикл
		// Обход порции результата запроса
		ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
		Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
			// Обработка элемента выборки
			// ...
		КонецЦикла;
		Запрос.УстановитьПараметр("ПоследняяСсылка", ВыборкаДетальныеЗаписи.Ссылка);
		РезультатЗапроса = Запрос.Выполнить();
	КонецЦикла;
Показать
20. Glebis 13 13.02.19 09:43 Сейчас в теме
Если есть проблема с новыми ссылками, то почему бы не складывать уже обработанные в массив и проверять вхождение при обработки каждой порции?

Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Наименование КАК Наименование,
	|	Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	НЕ Номенклатура.Ссылка В (&МассивОбработанныхСсылок)
	|	И <условие выборки необработанных записей";
	МассивОбработанныхСсылок = Новый Массив;
	Запрос.УстановитьПараметр("МассивОбработанныхСсылок", МассивОбработанныхСсылок);
	РезультатЗапроса = Запрос.Выполнить();
	Пока НЕ РезультатЗапроса.Пустой() Цикл
		// Обход порции результата запроса
		ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
		Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
			МассивОбработанныхСсылок.Добавить(ВыборкаДетальныеЗаписи.Ссылка);
			// Обработка элемента выборки
			// ...
		КонецЦикла;
		Запрос.УстановитьПараметр("МассивОбработанныхСсылок", МассивОбработанныхСсылок);
		РезультатЗапроса = Запрос.Выполнить();
	КонецЦикла;
Показать
21. Glebis 13 13.02.19 11:03 Сейчас в теме
Если предыдущий вариант со складированием ссылок обработанных объектов в массив не подходит, то можно попробовать ещё один особо извращенский способ: переделать запрос на НЕ вхождение порции в таблицу изменений справочника. Но здесь имеются подводные камни, которые нужно проверять: наложение общей транзакции, отдельный план обмена с ручной регистрацией и ещё куча других вопросов.
22. VmvLer 13.02.19 14:23 Сейчас в теме
если немного подумать то АВТОНОМЕРЗАПИСИ() решает задачу разбиения выборки на порции

размер порции 1000, 10 000,... задаем параметром и дальше простая математика в SQL без шаманских пассов получает порционную выборку
24. user970589 10 14.02.19 10:05 Сейчас в теме
Картинка зачотная!!! пацсталом. ссылочные элементы. порциями. аххах..
25. dsdred 3251 17.02.19 08:40 Сейчас в теме
Старый способ...
уже описывал в статье https://infostart.ru/public/893304/

А вообще способ "декоративный" - не надежный!
Не дураки придумали ПланОбмена и номера сообщений - этот вариант тоже показан в статье https://infostart.ru/public/893304/
Оставьте свое сообщение