Чтение и выборки данных

Учимся доставать данные из базы 1С: получать конкретный элемент по ссылке и обходить весь справочник выборкой.

Ссылка — это лёгкий указатель на элемент справочника или документа (по сути его адрес в базе), а объект — полная загруженная в память копия того же элемента, которую можно менять и записывать.

Почти любая задача в 1С начинается с одного и того же вопроса: «а как мне добраться до нужной записи в базе?». У вас есть номенклатура, контрагенты, документы — и всё это надо читать из кода: узнать цену товара, проверить ИНН контрагента, пробежать по всем сотрудникам. В 1С для этого есть два разных инструмента, и путать их — классическая ошибка новичка. Первый — ссылка, когда нам нужно просто «показать пальцем» на элемент. Второй — объект, когда элемент надо целиком загрузить, чтобы прочитать много реквизитов или что-то в нём поменять.

Зачем это нужно на практике

Представьте, что вы пишете обработку, которая формирует прайс-лист. Вам нужно пройти по всей номенклатуре и для каждого товара взять наименование и артикул. Или другая задача: пользователь выбрал в форме контрагента, и вы хотите подставить его расчётный счёт. В первом случае вы будете обходить справочник выборкой, во втором — получите один элемент по ссылке. Разберём оба сценария.

Ссылка и объект: в чём разница

Когда вы пишете Справочники.Номенклатура.НайтиПоКоду("000000123"), вы получаете ссылку. Ссылка занимает мало памяти и её удобно хранить в переменных, передавать в функции, класть в реквизиты документов. Через ссылку можно читать реквизиты напрямую — 1С сама подгрузит нужное значение из базы:

// Находим товар по коду — получаем ССЫЛКУ
ТоварСсылка = Справочники.Номенклатура.НайтиПоКоду("000000123");

Если ТоварСсылка.Пустая() Тогда
    Сообщить("Товар с таким кодом не найден");
Иначе
    // Читаем реквизиты прямо через ссылку
    Сообщить("Наименование: " + ТоварСсылка.Наименование);
    Сообщить("Артикул: " + ТоварСсылка.Артикул);
КонецЕсли;

Обратите внимание на проверку .Пустая(). Метод НайтиПоКоду() всегда возвращает ссылку, даже если ничего не нашёл — просто это будет «пустая ссылка». Если сразу обратиться к .Наименование у пустой ссылки, вы получите пустую строку, а не ошибку, и молча уйдёте с неверными данными. Поэтому проверять найденность нужно почти всегда.

А теперь объект. Он нужен, когда вы собираетесь элемент менять и записывать (об этом подробно в следующем уроке) либо когда надо активно работать с его табличными частями. Объект получают из ссылки методом .ПолучитьОбъект():

// Из ссылки получаем ОБЪЕКТ — полную копию в памяти
ТоварОбъект = ТоварСсылка.ПолучитьОбъект();

// У объекта доступны те же реквизиты, но его уже можно менять
Сообщить("Единица: " + ТоварОбъект.ЕдиницаИзмерения);

Правило большого пальца: нужно только прочитать — работайте со ссылкой. Нужно изменить — берите объект через .ПолучитьОбъект(). Не загружайте объект «на всякий случай»: это лишняя нагрузка на память и базу.

Выборка: обходим весь справочник

Когда нужен не один элемент, а перебор многих, применяют выборку — метод Выбрать(). Он открывает курсор по таблице справочника, а метод Следующий() сдвигает его на очередную запись и возвращает Ложь, когда записи кончились. Классический шаблон обхода выглядит так:

// Получаем выборку по всему справочнику Номенклатура
Выборка = Справочники.Номенклатура.Выбрать();

Пока Выборка.Следующий() Цикл
    // Внутри цикла Выборка ведёт себя как текущий элемент
    Сообщить(Выборка.Код + " — " + Выборка.Наименование);
КонецЦикла;

Результат:

000000001 — Кофе зерновой
000000002 — Чай чёрный
000000003 — Сахар-песок

Выборку можно ограничить, чтобы не тащить лишнее. Например, обойти только элементы внутри конкретной группы (папки) справочника — для этого у Выбрать() есть параметр «родитель»:

// Только элементы внутри группы "Напитки"
ГруппаНапитки = Справочники.Номенклатура.НайтиПоНаименованию("Напитки", Истина);
Выборка = Справочники.Номенклатура.Выбрать(ГруппаНапитки);

Пока Выборка.Следующий() Цикл
    // Пропускаем сами папки, берём только товары
    Если НЕ Выборка.ЭтоГруппа Тогда
        Сообщить(Выборка.Наименование);
    КонецЕсли;
КонецЦикла;

Здесь важны две детали. Второй аргумент НайтиПоНаименованию(..., Истина) велит искать в том числе среди групп. А внутри цикла реквизит ЭтоГруппа отличает папку от обычного элемента — у иерархических справочников это нужно почти всегда, иначе в отчёт попадут названия папок.

Как это работает

Под капотом Выбрать() не загружает весь справочник в память разом. Он создаёт курсор — движок 1С читает записи из базы порциями по мере вызовов Следующий(). Поэтому обход даже большого справочника на десятки тысяч элементов не «съедает» память: в каждый момент в памяти живёт лишь текущая запись. Сама Выборка при этом отдаёт данные в режиме чтения — менять через неё элемент нельзя, для этого пришлось бы на каждой итерации звать .ПолучитьОбъект().

Ссылка устроена ещё экономнее: физически это уникальный идентификатор записи (GUID) плюс информация о типе. Когда вы пишете ТоварСсылка.Наименование, платформа при первом обращении подтягивает значение реквизита из базы. Поэтому обращение к реквизитам ссылки в цикле по многу раз — это скрытые запросы к базе, и на больших объёмах данных правильнее взять данные одним запросом на языке запросов 1С (эту тему разберём в отдельной книге про запросы).

Частые ошибки

  • Не проверяют .Пустая() после поиска. НайтиПоКоду() и НайтиПоНаименованию() для ненайденного элемента возвращают пустую ссылку, а не Неопределено. Код продолжит работать, но с пустыми данными — и вы получите тихую логическую ошибку.
  • Берут объект там, где хватило бы ссылки. Если вы только читаете реквизиты, .ПолучитьОбъект() не нужен — это лишняя загрузка данных из базы и нагрузка на память.
  • Забывают про ЭтоГруппа в иерархических справочниках. Тогда при обходе в результат попадают папки наравне с элементами, и отчёт «разъезжается».
  • Меняют элемент прямо в выборке. Выборка — только для чтения. Присвоить Выборка.Наименование = "..." и «записать» не выйдет; менять надо объект, полученный через .ПолучитьОбъект().

Итоги

  • Ссылка — лёгкий указатель на элемент, годится для чтения реквизитов и хранения; объект (.ПолучитьОбъект()) — полная копия в памяти, нужен для изменения.
  • НайтиПоКоду()/НайтиПоНаименованию() возвращают ссылку; всегда проверяйте её на .Пустая().
  • Обход справочника — через Выбрать() и Пока Выборка.Следующий() Цикл; курсор читает записи порциями и не грузит память.
  • В иерархических справочниках отделяйте папки от элементов через реквизит ЭтоГруппа.
  • Обращение к реквизитам ссылки внутри цикла — это скрытые запросы к базе; на больших объёмах предпочитайте язык запросов 1С.
Проверьте себя
1. Вам нужно только прочитать наименование и артикул товара, ничего не меняя. Что правильнее использовать?
AСсылку — читать реквизиты через неё, объект не получать
BОбязательно вызвать .ПолучитьОбъект() перед чтением любого реквизита
CМетод Выбрать() по всему справочнику и найти нужный товар в цикле
DНичего не подойдёт — реквизиты читаются только запросом
2. Метод НайтиПоКоду() не нашёл элемент с указанным кодом. Что он вернёт?
AЗначение Неопределено
BПустую ссылку, у которой .Пустая() возвращает Истина
CВызовет исключение и остановит выполнение
DПустую строку