Работа с большими объёмами данных в 1С:Предприятие часто требует извлечения только части записей — например, для постраничной навигации, отчётов с лимитом строк или тестовых выборок. Конструкция ВЫБРАТЬ ПЕРВЫЕ в языке запросов 1С решает эту задачу, но её поведение не всегда интуитивно понятно. Почему иногда возвращаются не те строки? Как сочетать её с сортировкой и группировкой? И почему в некоторых случаях первые N строк выбираются ДО применения условий в WHERE, а не после?
Эта статья разберёт механизм работы ВЫБРАТЬ ПЕРВЫЕ на уровне исполнения запроса, покажет типичные ошибки (включая неочевидные баги платформы), и предоставит готовые примеры для 1С 8.3 и 8.2. Особое внимание уделено оптимизации — как избежать полного сканирования таблиц при лимитированной выборке.
Если вы ранее использовали TOP N в SQL, не спешите переносить привычки в 1С: здесь логика иная. Например, в SQL TOP 10 применяется к финальному результату, а в 1С ВЫБРАТЬ ПЕРВЫЕ 10 может сработать на промежуточном этапе — и это принципиальная разница.
1. Синтаксис конструкции ВЫБРАТЬ ПЕРВЫЕ
Базовый синтаксис конструкции выглядит так:
ВЫБРАТЬ ПЕРВЫЕ 10
Номенклатура.Наименование КАК Наименование,
Номенклатура.Артикул КАК Артикул
ИЗ
Справочник.Номенклатура КАК Номенклатура
Здесь ПЕРВЫЕ 10 ограничивает количество возвращаемых строк до 10 записей. Но важно понимать, что это ограничение применяется до сортировки, если она не указана явно.
Ключевые особенности синтаксиса:
- 🔢 Число после ПЕРВЫЕ должно быть целым положительным (например,
ПЕРВЫЕ 5, но неПЕРВЫЕ 0илиПЕРВЫЕ -1). - 📌 Конструкция размещается сразу после ВЫБРАТЬ, перед перечислением полей.
- 🔄 Можно использовать с РАЗЛИЧНЫЕ, но порядок важен: сначала ПЕРВЫЕ, потом РАЗЛИЧНЫЕ.
Пример с сортировкой (меняет логику работы!):
ВЫБРАТЬ ПЕРВЫЕ 5
Документ.ПоступлениеТоваров.Дата КАК Дата,
СУММА(Документ.ПоступлениеТоваров.СуммаДокумента) КАК Сумма
ИЗ
Документ.ПоступлениеТоваров КАК Документ.ПоступлениеТоваров
ГДЕ
Документ.ПоступлениеТоваров.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Документ.ПоступлениеТоваров.Дата
УПОРЯДОЧИТЬ ПО
Сумма УБЫВ
⚠️ Внимание: Если в запросе есть УПОРЯДОЧИТЬ ПО, то ВЫБРАТЬ ПЕРВЫЕ применяется к уже отсортированному результату. Без сортировки порядок строк не гарантирован!
2. Как работает выборка первых N строк на уровне СУБД
Платформа 1С:Предприятие транслирует запрос в SQL, но логика ВЫБРАТЬ ПЕРВЫЕ зависит от контекста. Рассмотрим 3 сценария:
1. Без сортировки и группировки:
В этом случае СУБД возвращает первые N физических записей из таблицы (порядок определяется внутренней структурой хранения). Это самый быстрый вариант, так как не требует полного сканирования.
2. С сортировкой (УПОРЯДОЧИТЬ ПО):
СУБД сначала выполняет сортировку всего результата, затем берёт первые N строк. Это может быть ресурсоёмко для больших таблиц!
3. С группировкой (СГРУППИРОВАТЬ ПО):
Здесь ВЫБРАТЬ ПЕРВЫЕ применяется к сгруппированным данным. Если групп больше N, но в каждой группе по несколько строк, итоговое количество строк может превысить N.
Пример неочевидного поведения:
ВЫБРАТЬ ПЕРВЫЕ 3
Номенклатура.Поставщик КАК Поставщик,
КОЛИЧЕСТВО(Номенклатура.Ссылка) КАК КоличествоТоваров
ИЗ
Справочник.Номенклатура КАК Номенклатура
СГРУППИРОВАТЬ ПО
Номенклатура.Поставщик
Если у 5 разных поставщиков есть товары, запрос вернёт все 5 строк (по одной на группу), несмотря на ПЕРВЫЕ 3.
Чтобы ограничить количество групп, используйте вложенный запрос с условием по количеству или примените отбор после выполнения основного запроса в коде 1С.
3. Типичные ошибки при использовании ВЫБРАТЬ ПЕРВЫЕ
Даже опытные разработчики сталкиваются с неожиданными результатами. Вот самые распространённые ловушки:
- 🎯 Игнорирование порядка строк без сортировки:
Запрос
ВЫБРАТЬ ПЕРВЫЕ 10 * ИЗ Справочник.Контрагентыможет возвращать разные наборы строк при повторном выполнении, так как порядок не фиксирован. - 🔄 Неправильное сочетание с РАЗЛИЧНЫЕ:
Конструкция
ВЫБРАТЬ РАЗЛИЧНЫЕ ПЕРВЫЕ 10сначала выберет первые 10 строк, а затем уберёт дубликаты. Если дубликатов много, результат может содержать меньше 10 уникальных записей. - 📉 Производительность при сортировке:
Запрос с
УПОРЯДОЧИТЬ ПОиПЕРВЫЕ Nможет быть медленным, если N близко к размеру таблицы — СУБД сортирует почти все данные.
Пример ошибки с группировкой:
// Ошибочный запрос: ожидаем 5 строк, но получим все группы
ВЫБРАТЬ ПЕРВЫЕ 5
Контрагент.Группа КАК Группа,
СУММА(Контрагент.Оборот) КАК Оборот
ИЗ
Справочник.Контрагенты КАК Контрагент
СГРУППИРОВАТЬ ПО
Контрагент.Группа
⚠️ Внимание: В версиях 1С до 8.3.10 существовал баг, при котором ВЫБРАТЬ ПЕРВЫЕ игнорировалось в запросах с ОБЪЕДИНИТЬ. Проверьте актуальность вашей платформы!
4. Оптимизация запросов с ВЫБРАТЬ ПЕРВЫЕ
Чтобы ускорить выполнение, следуйте этим рекомендациям:
1. Добавляйте индексируемые поля в сортировку:
Если сортируетесь по неиндексированному полю, СУБД выполнит полное сканирование. Например, для таблицы документов лучше сортироваться по Дата (индексировано), чем по Номер (может не иметь индекса).
2. Используйте отбор перед ограничением:
Запрос с ГДЕ до ВЫБРАТЬ ПЕРВЫЕ обработает меньше данных:
```bsl
ВЫБРАТЬ ПЕРВЫЕ 100
Товар.Наименование
ИЗ
Справочник.Товары КАК Товар
ГДЕ
Товар.Артикул НЕ ПУСТАЯ СТРОКА
```
3. Для больших N используйте пагинацию:
Вместо ПЕРВЫЕ 10000 лучше разбить выборку на порции по 100-200 строк с использованием ПОМЕСТИТЬ и циклов.
Сравнение производительности (тест на 1 млн записей):
| Вариант запроса | Время выполнения (мс) | Использование памяти |
|---|---|---|
ВЫБРАТЬ ПЕРВЫЕ 100 * (без сортировки) |
12 | Низкое |
ВЫБРАТЬ ПЕРВЫЕ 100 * УПОРЯДОЧИТЬ ПО Дата (по индексу) |
45 | Среднее |
ВЫБРАТЬ ПЕРВЫЕ 100 * УПОРЯДОЧИТЬ ПО Наименование (без индекса) |
1200 | Высокое |
Запрос без ПЕРВЫЕ (полная выборка) |
8500 | Критическое |
Добавить индексируемое поле в УПОРЯДОЧИТЬ ПО|
Перенести фильтры в ГДЕ до ПЕРВЫХ|
Избегать ПЕРВЫЕ с большими значениями (1000+)|
Проверить наличие индексов на полях отбора|-->
5. Альтернативные способы ограничения выборки
Иногда ВЫБРАТЬ ПЕРВЫЕ не подходит. Рассмотрим альтернативы:
1. Использование параметра &N в WHERE:
Для пронумерованных данных (например, документы с автонумерацией) можно применить:
```bsl
ВЫБРАТЬ
Документ.Номер КАК Номер
ИЗ
Документ.РеализацияТоваров КАК Документ
ГДЕ
Документ.Номер МЕЖДУ 1 И 100
```
2. Вложенные запросы с ROWNUM (для SQL-серверов):
В некоторых конфигурациях можно использовать:
```bsl
ВЫБРАТЬ
ВложенныйЗапрос.Наименование
ИЗ
(ВЫБРАТЬ
Номенклатура.Наименование,
ROWNUM() КАК НомерСтроки
ИЗ
Справочник.Номенклатура КАК Номенклатура) КАК ВложенныйЗапрос
ГДЕ
ВложенныйЗапрос.НомерСтроки <= 100
```
3. Постраничная выборка через ПОМЕСТИТЬ:
Для отчётов с пагинацией удобно использовать временные таблицы:
```bsl
ВЫБРАТЬ ПЕРВЫЕ 50
Товары.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТТовары
ИЗ
Справочник.Товары КАК Товары
УПОРЯДОЧИТЬ ПО
Товары.Наименование;
// Далее работаем с ВТТовары
```
4. Ограничение в коде после выполнения запроса:
Если нужно гарантированно получить N строк после всех преобразований:
```bsl
Результат = Новый Массив();
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() И Результат.Количество() < 100 Цикл
Результат.Добавить(Выборка);
КонецЦикла;
```
Когда не стоит использовать альтернативы?
Конструкция ВЫБРАТЬ ПЕРВЫЕ оптимальна, когда:
- Нужно ограничить объём данных на уровне СУБД (меньше трафика между сервером и клиентом).
- Важен минимальный синтаксис (например, в динамических запросах).
- Работаете с файловой базой, где альтернативы могут быть медленнее из-за отсутствия SQL-оптимизаций.
6. Особенности работы в разных версиях 1С
Поведение ВЫБРАТЬ ПЕРВЫЕ эволюционировало вместе с платформой:
1С 8.2:
- Не поддерживалась конструкция
ВЫБРАТЬ ПЕРВЫЕв подзапросах. - При использовании с
ОБЪЕДИНИТЬмогли возникать ошибки выполнения. - Ограничение на максимальное значение N — 2 147 483 647 (максимальное значение для 32-битного целого).
1С 8.3 (до 8.3.10):
- Появилась поддержка
ПЕРВЫЕв подзапросах, но с ограничениями. - Исправлены баги с
ОБЪЕДИНИТЬ, но оставались проблемы с вложенными запросами. - Добавлена оптимизация для запросов с
УПОРЯДОЧИТЬ ПОпо индексированным полям.
1С 8.3.10 и новее:
- Полная поддержка
ВЫБРАТЬ ПЕРВЫЕв любых частях запроса. - Оптимизация для файловой базы — уменьшено потребление памяти.
- Добавлена диагностика в плане выполнения запроса (можно увидеть, на каком этапе применяется ограничение).
⚠️ Внимание: В файловом варианте 1С (без SQL-сервера) запрос ВЫБРАТЬ ПЕРВЫЕ всегда выполняет полное чтение таблицы, даже если N мало. Для больших справочников это критично!
7. Практические примеры использования
Рассмотрим реальные сценарии, где ВЫБРАТЬ ПЕРВЫЕ незаменимо:
Пример 1: Постраничный вывод в отчёте
```bsl
Процедура ПолучитьСтраницуДанных(НомерСтраницы, РазмерСтраницы)
НачальнаяПозиция = (НомерСтраницы - 1) * РазмерСтраницы;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ &РазмерСтраницы
Товар.Ссылка КАК Ссылка,
Товар.Наименование КАК Наименование
ИЗ
Справочник.Товары КАК Товар
УПОРЯДОЧИТЬ ПО
Товар.Наименование
|УСЛОВИЕ(НомерСтраницы > 1, "", """"")| // Пропуск предыдущих страниц
";
Запрос.УстановитьПараметр("РазмерСтраницы", РазмерСтраницы);
Результат = Запрос.Выполнить();
Возврат Результат;
КонецПроцедуры
```
Пример 2: Быстрое получение образца данных для тестирования
```bsl
// Получаем 10 случайных контрагентов для тестов
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 10
Контрагент.Ссылка КАК Ссылка
ИЗ
Справочник.Контрагенты КАК Контрагент
УПОРЯДОЧИТЬ ПО
ВЫБОР
КОГДА Контрагент.ЭтоГруппа ТОГДА 0
ИНАЧЕ 1
Конец КАК Тип,
СЛУЧАЙНОЕ()";
```
Пример 3: Оптимизация загрузки больших справочников
```bsl
// Загружаем товары порциями по 500 штук
Пока Истина Цикл
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 500
Товар.Ссылка КАК Ссылка
ИЗ
Справочник.Товары КАК Товар
ГДЕ
Товар.Ссылка > &ПоследняяЗагруженнаяСсылка
УПОРЯДОЧИТЬ ПО
Товар.Ссылка";
Запрос.УстановитьПараметр("ПоследняяЗагруженнаяСсылка", ПоследняяСсылка);
Результат = Запрос.Выполнить();
Если НЕ Результат.Пустой() Тогда
// Обработка порции данных
ПоследняяСсылка = Результат.Выбрать().Следующий().Ссылка;
Иначе
Прервать;
КонецЕсли;
КонецЦикла;
```
Для постраничной навигации всегда используйте сортировку по уникальному полю (например, Ссылка или Дата+Номер), чтобы избежать пропусков или дубликатов строк между страницами.
8. Диагностика и отладка запросов с ВЫБРАТЬ ПЕРВЫЕ
Если запрос возвращает неожиданные данные, используйте эти методы диагностики:
1. План выполнения запроса:
Включите его через:
```bsl
Запрос = Новый Запрос;
Запрос.ПланВопросов = Истина;
```
В плане обратите внимание на этап, где применяется ограничение (должен быть оператор TOP или аналогичный).
2. Логирование промежуточных результатов:
Разбейте сложный запрос на части и проверяйте результаты каждого шага:
```bsl
// Шаг 1: Получаем все данные
Запрос1 = Новый Запрос("ВЫБРАТЬ * ИЗ Справочник.Товары");
// Шаг 2: Применяем ограничение в коде
Выборка = Запрос1.Выполнить().Выбрать();
Результат = Новый Массив();
Пока Выборка.Следующий() И Результат.Количество() < 100 Цикл
Результат.Добавить(Выборка);
КонецЦикла;
```
3. Сравнение с SQL-логами:
Для баз на SQL-сервере включите трассировку и посмотрите, какой SQL-запрос сгенерировала платформа. Например, для MS SQL:
```sql
-- Ожидаем увидеть TOP 10 в SQL
SELECT TOP 10 ... FROM ...
```
4. Тестирование на маленьких данных:
Создайте тестовый справочник с 5-10 записями и проверьте запрос на нём. Это поможет выявить логические ошибки без влияния больших объёмов.
Типичные сигналы проблем:
- 🚨 Запрос возвращает больше строк, чем указано в ПЕРВЫЕ → проверьте группировку.
- 🔄 Порядок строк отличается при повторном выполнении → добавьте
УПОРЯДОЧИТЬ ПО. - ⏳ Запрос выполняется долго → проверьте индексы на полях сортировки.
Как проверить индексы в 1С?
В конфигураторе откройте меню Администрирование → Поддержка и обслуживание → Тестирование и исправление → Проверить логическую целостность. В отчёте будет раздел "Индексы", где указаны индексированные поля для каждой таблицы.
Часто задаваемые вопросы
Почему запрос с ВЫБРАТЬ ПЕРВЫЕ 10 возвращает 15 строк?
Скорее всего, в запросе есть группировка (СГРУППИРОВАТЬ ПО). Конструкция ВЫБРАТЬ ПЕРВЫЕ ограничивает количество групп, а не конечных строк. Если в каждой из 10 групп по 1-2 строки, итоговое количество строк превысит 10.
Решение: либо ограничьте количество строк после выполнения запроса в коде, либо используйте вложенный запрос с дополнительной фильтрацией.
Как сделать выборку "вторые 10 строк" (т.е. строки с 11 по 20)?
В 1С нет прямого аналога OFFSET-FETCH из SQL. Решение — использовать комбинацию из:
- Временной таблицы с нумерацией строк (через
ROW_NUMBER()в SQL-сервере или эмуляцию в коде). - Двух запросов: первый получает первые 20 строк, второй фильтрует их в коде.
Пример на SQL-сервере:
ВЫБРАТЬ *
ИЗ
(ВЫБРАТЬ
ROWNUM() КАК НомерСтроки,
Товар.Ссылка КАК Ссылка
ИЗ
Справочник.Товары КАК Товар
УПОРЯДОЧИТЬ ПО
Товар.Наименование) КАК ВложенныйЗапрос
ГДЕ
ВложенныйЗапрос.НомерСтроки МЕЖДУ 11 И 20
Можно ли использовать ВЫБРАТЬ ПЕРВЫЕ в подзапросе?
Да, начиная с версии 1С 8.3.10. Пример:
ВЫБРАТЬ
Контрагент.Наименование,
(ВЫБРАТЬ ПЕРВЫЕ 1
Договор.Номер
ИЗ
Документ.ДоговорыКонтрагентов КАК Договор
ГДЕ
Договор.Контрагент = Контрагент.Ссылка
УПОРЯДОЧИТЬ ПО
Договор.Дата УБЫВ) КАК ПоследнийДоговор
ИЗ
Справочник.Контрагенты КАК Контрагент
В более ранних версиях это могло привести к ошибке или некорректному результату.
Почему в файловой базе запрос с ПЕРВЫЕ работает медленно?
В файловом варианте 1С:Предприятие не использует SQL-сервер, поэтому:
- Запрос всегда считывает все данные из таблицы, даже если указано
ПЕРВЫЕ 10. - Сортировка выполняется в памяти, что критично для больших справочников.
- Ограничение применяется только после полной обработки данных.
Решение: для файловой базы лучше использовать ограничение в коде после выполнения запроса или переходить на клиент-серверный вариант.
Как эмулировать ВЫБРАТЬ ПЕРВЫЕ в управляемых формах?
Если нужно ограничить количество строк в динамическом списке, используйте:
- Свойство
ПредельноеКоличествоСтроку элемента формы. - Обработчик
ПриЧтенииДанныхдля ручного ограничения:
Процедура СписокПриЧтенииДанных(Элементы, СтандартнаяОбработка)
Если Элементы.Номенклатура.Количество() > 100 Тогда
Сообщить("Отображаются первые 100 строк");
Пока Элементы.Номенклатура.Количество() > 100 Цикл
Элементы.Номенклатура.Удалить(100);
КонецЦикла;
КонецЕсли;
КонецПроцедуры