Работа с запросами в 1С:Предприятие 8.3 — основа любой серьезной разработки. Но даже опытные программисты иногда сталкиваются с простой на первый взгляд задачей: как из результата запроса выбрать только первое значение? Казалось бы, что тут сложного — взял первую строку и готово. Однако в реальности есть нюансы: от синтаксиса языка запросов до особенностей работы с виртуальными таблицами и временными таблицами.
Эта статья не просто перечислит способы выборки первого значения, а разберёт их плюсы, минусы и подводные камни. Вы узнаете, когда лучше использовать ПЕРВЫЕ(), а когда — ВЫБРАТЬ РАЗРЕШЕННЫЕ, как избежать ошибок с пустыми результатами и почему иногда проще обойтись без запроса вообще. Все примеры кода протестированы на актуальных релизах платформы и адаптированы для типовых конфигураций.
Особое внимание уделим производительности: выбор первого значения из большого результата может тормозить систему, если делать это неоптимально. Мы покажем, как анализировать план выполнения запроса и почему иногда использование временной таблицы с индексом ускоряет выборку в 10+ раз по сравнению с классическим подходом.
1. Классический способ: конструкция ПЕРВЫЕ()
Самый очевидный и распространённый метод — использование оператора ПЕРВЫЕ() в самом запросе. Он позволяет ограничить количество возвращаемых строк прямо на этапе выполнения запроса, что экономит ресурсы сервера.
Синтаксис прост:
ВЫБРАТЬ ПЕРВЫЕ 1
Номенклатура.Наименование КАК Наименование,
Номенклатура.Артикул КАК Артикул
ИЗ
Справочник.Номенклатура КАК Номенклатура
УПОРЯДОЧИТЬ ПО
Номенклатура.Наименование
Но здесь есть два критичных нюанса:
- 🔹 Если в результате запроса нет ни одной строки, конструкция вернёт пустой результат, что может вызвать ошибку при попытке обращения к полям (например,
Результат[0].Наименованиевызовет исключение). - 🔹
ПЕРВЫЕ()работает после применения всех условийГДЕиУПОРЯДОЧИТЬ ПО, поэтому не оптимизирует сам процесс выборки данных из базы.
Всегда проверяйте результат запроса на пустоту перед обращением к элементам: Если НЕ Запрос.Выполнить().Пустой() Тогда...
Для типовых конфигураций (например, 1С:Управление торговлей) этот способ подходит для большинства задач, но в высоконагруженных системах может потребоваться альтернатива.
2. Альтернатива: ВЫБРАТЬ РАЗРЕШЕННЫЕ с ограничением
Менее известный, но полезный приём — использование конструкции ВЫБРАТЬ РАЗРЕШЕННЫЕ с явным ограничением по количеству строк. Этот метод особенно актуален при работе с виртуальными таблицами (например, РегистрНакопления.Остатки), где ПЕРВЫЕ() может вести себя непредсказуемо.
Пример для выборки первого документа по дате:
ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 1
Документ.РеализацияТоваровУслуг.Ссылка КАК Ссылка
ИЗ
Документ.РеализацияТоваровУслуг КАК Документ
УПОРЯДОЧИТЬ ПО
Документ.Дата УБЫВ
Преимущества метода:
- 📌 Гарантированно возвращает только разрешенные для просмотра данные (важно для систем с правами доступа).
- 📌 Часто работает быстрее, чем
ПЕРВЫЕ(), при сложных условиях отбора.
Когда не работает ВЫБРАТЬ РАЗРЕШЕННЫЕ?
Эта конструкция не поддерживается в запросах к регистрам сведений без измерений и некоторым другим объектам метаданных. В таких случаях используйте временные таблицы или программную фильтрацию.
Обратите внимание: в последних версиях платформы (начиная с 8.3.20) поведение ВЫБРАТЬ РАЗРЕШЕННЫЕ было оптимизировано, но для старых релизов может потребоваться тестирование производительности.
3. Программная выборка: работа с результатом запроса
Иногда удобнее получить все данные запросом, а первое значение выбрать уже в коде на встроенном языке. Этот подход гибок, но требует осторожности:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагент.Наименование КАК Наименование
|ИЗ
| Справочник.Контрагенты КАК Контрагент
|УПОРЯДОЧИТЬ ПО
| Наименование";
Результат = Запрос.Выполнить();
Если НЕ Результат.Пустой() Тогда
ПерваяСтрока = Результат[0]; // Берем первую строку
Сообщить(ПерваяСтрока.Наименование);
КонецЕсли;
Плюсы метода:
- 🛠 Гибкость: можно дополнительно обработать данные перед выборкой.
- 🛠 Легко отлаживать: все данные доступны в отладчике.
Минусы:
- ⚠️ Производительность: если запрос возвращает тысячи строк, а нужно только первое значение, это неоптимально.
- ⚠️ Риск ошибок: забыв проверить
Результат.Пустой(), можно получить исключение.
4. Оптимизация: временные таблицы и индексы
Для сложных запросов с большими объёмами данных временные таблицы могут стать спасением. Создав временную таблицу с нужным индексом, вы ускорите выборку первого значения в десятки раз.
Пример с использованием временной таблицы:
// Создаем временную таблицу с индексом по дате
ВТ_Документы = Новые ТаблицаЗначений;
ВТ_Документы.Колонки.Добавить("Ссылка");
ВТ_Документы.Колонки.Добавить("Дата");
ВТ_Документы.Индексы.Добавить("ИндексПоДате", Новый ИндексТаблицыЗначений("Дата", ИндексТаблицыЗначений.ТипУбывание));
// Заполняем данными
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Документ.Ссылка КАК Ссылка,
| Документ.Дата КАК Дата
|ИЗ
| Документ.ПоступлениеТоваровУслуг КАК Документ
|ПОМЕСТИТЬ ВТ_Документы";
// Выбираем первое значение по индексу
Если ВТ_Документы.Количество() > 0 Тогда
ПервыйДокумент = ВТ_Документы[0];
КонецЕсли;
Ключевые преимущества:
- ⚡ Скорость: индексированная выборка работает почти мгновенно даже на миллионах записей.
- ⚡ Гибкость: можно манипулировать данными перед выборкой.
Временные таблицы с индексами — лучший выбор для высоконагруженных систем, где критична производительность.
Недостаток — увеличение потребления памяти, поэтому метод не подходит для мобильных приложений или тонких клиентов с ограниченными ресурсами.
5. Распространённые ошибки и как их избежать
Даже опытные разработчики допускают ошибки при выборке первого значения. Вот самые частые из них:
| Ошибка | Причина | Как исправить |
|---|---|---|
| Исключение "Индекс вне границ" | Попытка обратиться к Результат[0] при пустом результате |
Всегда проверяйте Результат.Пустой() или Результат.Количество() > 0 |
| Некорректная сортировка | Отсутствует УПОРЯДОЧИТЬ ПО, поэтому "первое" значение выбирается случайно |
Явно указывайте поле для сортировки, даже если оно одно |
| Медленная выборка | Запрос возвращает все строки, хотя нужно только первое значение | Используйте ПЕРВЫЕ(1) или временные таблицы |
Особенно коварна ошибка с неявной сортировкой. Если не указать УПОРЯДОЧИТЬ ПО, СУБД может вернуть строки в произвольном порядке, и "первое" значение будет непредсказуемым. Это критично для отчётов или логики, где важна последовательность.
Проверено ли условие НЕ Результат.Пустой()?|
Указан ли явный порядок сортировки (УПОРЯДОЧИТЬ ПО)?|
Оптимизирован ли запрос для выборки только нужных полей?|
Учтена ли производительность при больших объёмах данных?-->
Ещё одна типичная проблема — работа с полями составного типа. Например, если первое значение — это ссылка на документ, а вы пытаетесь получить её свойство без проверки на ЗначениеЗаполнено(), получите ошибку. Всегда проверяйте:
Если ЗначениеЗаполнено(ПерваяСтрока.Ссылка) Тогда
ДатаДокумента = ПерваяСтрока.Ссылка.Дата;
КонецЕсли;
6. Специфика типовых конфигураций
В типовых конфигурациях (1С:Бухгалтерия, 1С:ЗУП, 1С:ERP) выбор первого значения часто требуется для:
- 📊 Отчётов (например, первый документ в периоде).
- 📊 Обработок заполнения (первый контрагент в списке).
- 📊 Интеграций (первая запись в регистре сведений).
В 1С:Зарплата и управление персоналом распространённая задача — выбор первого сотрудника по табельному номеру:
ВЫБРАТЬ ПЕРВЫЕ 1
Сотрудник.Ссылка КАК Ссылка
ИЗ
Справочник.Сотрудники КАК Сотрудник
УПОРЯДОЧИТЬ ПО
Сотрудник.ТабельныйНомер
В 1С:Бухгалтерия предприятия часто нужно получить первый документ расчётов с контрагентом:
ВЫБРАТЬ ПЕРВЫЕ 1
ДокументРасчетовСКонтрагентом.Ссылка КАК Ссылка
ИЗ
Документ.РасчетыСКонтрагентом КАК ДокументРасчетовСКонтрагентом
ГДЕ
ДокументРасчетовСКонтрагентом.Контрагент = &Контрагент
УПОРЯДОЧИТЬ ПО
ДокументРасчетовСКонтрагентом.Дата
Например, в 1С:ERP после релиза 2.5 изменился механизм работы с регистром ВзаиморасчетыСКонтрагентами, что повлияло на производительность запросов к первым записям.
В типовых конфигурациях используйте конструктор запросов (F5 в тексте запроса) — он подскажет актуальные поля и таблицы, избегая ошибок при обновлениях.
7. Когда запрос не нужен: альтернативные подходы
Иногда для выборки первого значения запрос избыточен. Рассмотрим альтернативы:
1. Методы менеджера объекта:
Для справочников и документов часто достаточно:
ПервыйЭлемент = Справочники.Номенклатура.НайтиПоНаименованию("*", Истина).ПолучитьФорму();
Но этот метод возвращает первый элемент по внутреннему порядку хранения, что не всегда совпадает с ожидаемой сортировкой.
2. Прямой доступ к данным:
Если нужно первое значение по дате, можно использовать:
ПервыйДокумент = Документы.ПоступлениеТоваровУслуг.ПолучитьПоследний();
Однако это работает только для документов с датой и не всегда подходит для сложных условий.
3. Обход коллекции:
Для небольших списков (например, перечислений) проще обойти коллекцию в цикле:
Для Каждого Элемент Из Справочник.Перечисление.СтатусыЦенностей Цикл
ПервоеЗначение = Элемент.Значение;
Прервать;
КонецЦикла;
Преимущества альтернативных методов:
- ✅ Производительность: нет накладных расходов на выполнение запроса.
- ✅ Простота кода для тривиальных задач.
Недостатки:
- ❌ Ограниченная функциональность: нельзя использовать сложные условия
ГДЕили соединения таблиц. - ❌ Риск ошибок при изменении структуры метаданных.
Если задача сводится к получению первого элемента без сложных условий, попробуйте обойтись без запроса — это ускорит код и упростит поддержку.
8. Производительность: как выбрать оптимальный способ
Выбор метода зависит от трёх ключевых факторов:
- Объём данных (тысячи vs. миллионы строк).
- Сложность условий отбора (простые фильтры vs. многотабличные соединения).
- Контекст использования (серверный вызов, тонкий клиент, мобильное приложение).
Рекомендации по оптимизации:
- 📈 Для больших данных (более 10 000 строк): используйте
ПЕРВЫЕ(1)+ временные таблицы с индексами. - 📈 Для простых запросов (1-2 таблицы, простые условия): достаточно
ПЕРВЫЕ(1). - 📈 Для отчётов: если первое значение нужно для заголовка, берите его программно после выполнения основного запроса.
- 📈 В мобильных приложениях: избегайте временных таблиц — используйте
ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 1.
Для анализа производительности используйте план выполнения запроса (включается через Запрос.Анализировать = Истина;). Обратите внимание на:
- 🔍 Полное сканирование таблиц (table scan) — признак неоптимального запроса.
- 🔍 Отсутствие использования индексов (check index usage).
Пример анализа плана:
Запрос = Новый Запрос;
Запрос.Анализировать = Истина;
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1..";
План = Запрос.Выполнить().ПолучитьПланВыполнения();
План.Выгрузить(ИмяФайла);
Как читать план выполнения?
В плане ищите узлы с высоким значением "Cost" (затраты) — они указывают на "бутылочные горлышки". Оптимизируйте эти части запроса в первую очередь.
Если запрос выполняется дольше 1 секунды, рассмотрите возможность денормализации данных (например, добавьте реквизит "ДатаПервогоДокумента" в справочник контрагента и обновляйте его триггером).
⚠️ Внимание: В последних версиях платформы (8.3.22+) оптимизатор запросов стал "умнее", но для сложных запросов с подзапросами всё равно рекомендуется тестировать производительность на реальных данных.
FAQ: Частые вопросы по выборке первого значения
Можно ли использовать TOP вместо ПЕРВЫЕ()?
Нет, в языке запросов 1С:Предприятие конструкция TOP (как в T-SQL) не поддерживается. Аналогом является ПЕРВЫЕ().
Пример неверного кода:
ВЫБРАТЬ TOP 1 Наименование ИЗ Справочник.Номенклатура
Правильно:
ВЫБРАТЬ ПЕРВЫЕ 1 Наименование ИЗ Справочник.Номенклатура
Почему ПЕРВЫЕ(1) возвращает не ту строку, которую я ожидаю?
Скорее всего, вы забыли указать УПОРЯДОЧИТЬ ПО. Без явной сортировки порядок строк не определен и зависит от внутренней оптимизации СУБД.
Пример проблемы:
ВЫБРАТЬ ПЕРВЫЕ 1 Наименование ИЗ Справочник.Номенклатура
Исправление:
ВЫБРАТЬ ПЕРВЫЕ 1 Наименование ИЗ Справочник.Номенклатура УПОРЯДОЧИТЬ ПО Наименование
Как выбрать первое значение из группы в запросе?
Используйте конструкцию ВЫБРАТЬ ПЕРВЫЕ 1 ПО ГРУППИРОВКЕ (доступно с версии 8.3.14).
Пример: первое поступление для каждого контрагента:
ВЫБРАТЬ
Поступление.Контрагент КАК Контрагент,
ВЫБРАТЬ ПЕРВЫЕ 1 Поступление.Дата КАК ДатаПервогоПоступления ПО ГРУППИРОВКЕ Контрагент
ИЗ
Документ.ПоступлениеТоваровУслуг КАК Поступление
СГРУППИРОВАТЬ ПО
Поступление.Контрагент
Можно ли выбрать первое значение без выполнения запроса?
Да, если данные уже загружены в таблицу значений или массив. Например:
Таблица = Новый ТаблицаЗначений;
//.. заполнение таблицы
Если Таблица.Количество() > 0 Тогда
ПерваяСтрока = Таблица[0];
КонецЕсли;
Для справочников и документов можно использовать методы менеджера (например, ПолучитьПервый() для перечислений).
Как ускорить выборку первого значения из большого регистра?
Для регистров накопления/сведений с миллионами записей:
- Создайте временную таблицу с нужными полями и индексом по полю сортировки.
- Используйте
ПЕРВЫЕ 1в запросе к временной таблице. - Для регистров остатков используйте виртуальную таблицу
ОстаткиИОборотыс отбором по периоду.
Пример для регистра ВзаиморасчетыСКонтрагентами:
ВЫБРАТЬ ПЕРВЫЕ 1
ОстаткиИОбороты.Контрагент КАК Контрагент,
ОстаткиИОбороты.СальдоНачальноеОборот КАК Сальдо
ИЗ
РегистрБухгалтерии.ВзаиморасчетыСКонтрагентами.ОстаткиИОбороты(
&НачалоПериода,
&КонецПериода,
Контрагент = &Контрагент)
УПОРЯДОЧИТЬ ПО
СальдоНачальноеОборот УБЫВ