Чтение и выборки данных
Учимся доставать данные из базы 1С: получать конкретный элемент по ссылке и обходить весь справочник выборкой.
Ссылка — это лёгкий указатель на элемент справочника или документа (по сути его адрес в базе), а объект — полная загруженная в память копия того же элемента, которую можно менять и записывать.
Почти любая задача в 1С начинается с одного и того же вопроса: «а как мне добраться до нужной записи в базе?». У вас есть номенклатура, контрагенты, документы — и всё это надо читать из кода: узнать цену товара, проверить ИНН контрагента, пробежать по всем сотрудникам. В 1С для этого есть два разных инструмента, и путать их — классическая ошибка новичка. Первый — ссылка, когда нам нужно просто «показать пальцем» на элемент. Второй — объект, когда элемент надо целиком загрузить, чтобы прочитать много реквизитов или что-то в нём поменять.
Зачем это нужно на практике
Представьте, что вы пишете обработку, которая формирует прайс-лист. Вам нужно пройти по всей номенклатуре и для каждого товара взять наименование и артикул. Или другая задача: пользователь выбрал в форме контрагента, и вы хотите подставить его расчётный счёт. В первом случае вы будете обходить справочник выборкой, во втором — получите один элемент по ссылке. Разберём оба сценария.
Ссылка и объект: в чём разница
Когда вы пишете Справочники.Номенклатура.НайтиПоКоду("000000123"), вы получаете ссылку. Ссылка занимает мало памяти и её удобно хранить в переменных, передавать в функции, класть в реквизиты документов. Через ссылку можно читать реквизиты напрямую — 1С сама подгрузит нужное значение из базы:
// Находим товар по коду — получаем ССЫЛКУ
ТоварСсылка = Справочники.Номенклатура.НайтиПоКоду("000000123");
Если ТоварСсылка.Пустая() Тогда
Сообщить("Товар с таким кодом не найден");
Иначе
// Читаем реквизиты прямо через ссылку
Сообщить("Наименование: " + ТоварСсылка.Наименование);
Сообщить("Артикул: " + ТоварСсылка.Артикул);
КонецЕсли;
Обратите внимание на проверку .Пустая(). Метод НайтиПоКоду() всегда возвращает ссылку, даже если ничего не нашёл — просто это будет «пустая ссылка». Если сразу обратиться к .Наименование у пустой ссылки, вы получите пустую строку, а не ошибку, и молча уйдёте с неверными данными. Поэтому проверять найденность нужно почти всегда.
А теперь объект. Он нужен, когда вы собираетесь элемент менять и записывать (об этом подробно в следующем уроке) либо когда надо активно работать с его табличными частями. Объект получают из ссылки методом .ПолучитьОбъект():
// Из ссылки получаем ОБЪЕКТ — полную копию в памяти
ТоварОбъект = ТоварСсылка.ПолучитьОбъект();
// У объекта доступны те же реквизиты, но его уже можно менять
Сообщить("Единица: " + ТоварОбъект.ЕдиницаИзмерения);
Правило большого пальца: нужно только прочитать — работайте со ссылкой. Нужно изменить — берите объект через
.ПолучитьОбъект(). Не загружайте объект «на всякий случай»: это лишняя нагрузка на память и базу.
Выборка: обходим весь справочник
Когда нужен не один элемент, а перебор многих, применяют выборку — метод Выбрать(). Он открывает курсор по таблице справочника, а метод Следующий() сдвигает его на очередную запись и возвращает Ложь, когда записи кончились. Классический шаблон обхода выглядит так:
// Получаем выборку по всему справочнику Номенклатура
Выборка = Справочники.Номенклатура.Выбрать();
Пока Выборка.Следующий() Цикл
// Внутри цикла Выборка ведёт себя как текущий элемент
Сообщить(Выборка.Код + " — " + Выборка.Наименование);
КонецЦикла;
Результат:
000000001 — Кофе зерновой
000000002 — Чай чёрный
000000003 — Сахар-песок
Выборку можно ограничить, чтобы не тащить лишнее. Например, обойти только элементы внутри конкретной группы (папки) справочника — для этого у Выбрать() есть параметр «родитель»:
// Только элементы внутри группы "Напитки"
ГруппаНапитки = Справочники.Номенклатура.НайтиПоНаименованию("Напитки", Истина);
Выборка = Справочники.Номенклатура.Выбрать(ГруппаНапитки);
Пока Выборка.Следующий() Цикл
// Пропускаем сами папки, берём только товары
Если НЕ Выборка.ЭтоГруппа Тогда
Сообщить(Выборка.Наименование);
КонецЕсли;
КонецЦикла;
Здесь важны две детали. Второй аргумент НайтиПоНаименованию(..., Истина) велит искать в том числе среди групп. А внутри цикла реквизит ЭтоГруппа отличает папку от обычного элемента — у иерархических справочников это нужно почти всегда, иначе в отчёт попадут названия папок.
Как это работает
Под капотом Выбрать() не загружает весь справочник в память разом. Он создаёт курсор — движок 1С читает записи из базы порциями по мере вызовов Следующий(). Поэтому обход даже большого справочника на десятки тысяч элементов не «съедает» память: в каждый момент в памяти живёт лишь текущая запись. Сама Выборка при этом отдаёт данные в режиме чтения — менять через неё элемент нельзя, для этого пришлось бы на каждой итерации звать .ПолучитьОбъект().
Ссылка устроена ещё экономнее: физически это уникальный идентификатор записи (GUID) плюс информация о типе. Когда вы пишете ТоварСсылка.Наименование, платформа при первом обращении подтягивает значение реквизита из базы. Поэтому обращение к реквизитам ссылки в цикле по многу раз — это скрытые запросы к базе, и на больших объёмах данных правильнее взять данные одним запросом на языке запросов 1С (эту тему разберём в отдельной книге про запросы).
Частые ошибки
- Не проверяют
.Пустая()после поиска.НайтиПоКоду()иНайтиПоНаименованию()для ненайденного элемента возвращают пустую ссылку, а неНеопределено. Код продолжит работать, но с пустыми данными — и вы получите тихую логическую ошибку. - Берут объект там, где хватило бы ссылки. Если вы только читаете реквизиты,
.ПолучитьОбъект()не нужен — это лишняя загрузка данных из базы и нагрузка на память. - Забывают про
ЭтоГруппав иерархических справочниках. Тогда при обходе в результат попадают папки наравне с элементами, и отчёт «разъезжается». - Меняют элемент прямо в выборке. Выборка — только для чтения. Присвоить
Выборка.Наименование = "..."и «записать» не выйдет; менять надо объект, полученный через.ПолучитьОбъект().
Итоги
- Ссылка — лёгкий указатель на элемент, годится для чтения реквизитов и хранения; объект (
.ПолучитьОбъект()) — полная копия в памяти, нужен для изменения. НайтиПоКоду()/НайтиПоНаименованию()возвращают ссылку; всегда проверяйте её на.Пустая().- Обход справочника — через
Выбрать()иПока Выборка.Следующий() Цикл; курсор читает записи порциями и не грузит память. - В иерархических справочниках отделяйте папки от элементов через реквизит
ЭтоГруппа. - Обращение к реквизитам ссылки внутри цикла — это скрытые запросы к базе; на больших объёмах предпочитайте язык запросов 1С.