Как правильно использовать "ВЫБРАТЬ ПЕРВЫЕ" в запросах 1С: порядок выполнения с "ГДЕ" и "УПОРЯДОЧИТЬ ПО"

Программист 1С v8.3 (Управляемые формы)
← На главную

При написании запросов в 1С часто возникает вопрос о последовательности выполнения его частей. Особенно это касается конструкций ВЫБРАТЬ ПЕРВЫЕ и условия ГДЕ. Что отработает сначала: фильтрация записей или отбор первых N строк? От правильного понимания этого механизма зависит не только производительность, но и корректность получаемых данных. Рассмотрим эту тему подробно, а также разберем нюансы поведения запросов в файловом и клиент-серверном режимах.

Логический порядок выполнения операторов в запросе

Чтобы понять, что происходит "под капотом", важно знать логическую последовательность, по которой система обрабатывает запрос. Эта последовательность близка к стандарту SQL и выглядит следующим образом:

  1. ИЗ (FROM): Сначала определяется источник данных — таблицы, из которых будет производиться выборка.
  2. СОЕДИНЕНИЕ (JOIN): Если в запросе есть соединения, они выполняются на этом шаге, формируя единый набор данных.
  3. ГДЕ (WHERE): К полученному набору применяется фильтрация. Все строки, не удовлетворяющие условиям ГДЕ, отбрасываются. Это ключевой момент: фильтрация происходит до отбора первых записей.
  4. СГРУППИРОВАТЬ ПО (GROUP BY): Если есть группировка, данные объединяются в группы.
  5. ИМЕЮЩИЕ (HAVING): Применяется фильтрация к сгруппированным данным.
  6. ВЫБРАТЬ (SELECT): Формируется итоговый список полей для вывода.
  7. УПОРЯДОЧИТЬ ПО (ORDER BY): Весь отфильтрованный и сформированный результат сортируется в указанном порядке.
  8. ПЕРВЫЕ (TOP): И только на самом последнем шаге из отсортированного результата выбирается указанное количество первых записей.

Таким образом, на первоначальный вопрос автора "что сначала отработает: Выбрать Первые или Где?" ответ однозначен: сначала ГДЕ, потом ПЕРВЫЕ. Система сначала отберет все записи, подходящие под условие, а уже затем из них возьмет первые несколько.

Критическая роль "УПОРЯДОЧИТЬ ПО"

Из описанной выше последовательности виден еще один важный аспект: ПЕРВЫЕ применяется к отсортированному набору данных. Если вы не укажете секцию УПОРЯДОЧИТЬ ПО, порядок записей в результате не гарантирован. СУБД вернет вам "какие-то" первые N записей, и этот результат может меняться от запуска к запуску.

Использование ВЫБРАТЬ ПЕРВЫЕ без УПОРЯДОЧИТЬ ПО — это почти всегда ошибка в логике, если вам важен конкретный результат (например, самые новые документы или товары с наибольшей ценой).

Посмотрим на пример. Задача: получить последний по номеру документ реализации за определенную дату.

Правильный запрос:


Запрос.Текст = 
"ВЫБРАТЬ ПЕРВЫЕ 1
|   РеализацияТоваровУслуг.Ссылка
|ИЗ
|   Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
|ГДЕ
|   РеализацияТоваровУслуг.Дата МЕЖДУ &НачалоДня И &КонецДня
|   И РеализацияТоваровУслуг.Проведен
|
|УПОРЯДОЧИТЬ ПО
|   РеализацияТоваровУслуг.Номер УБЫВ";

Здесь мы сначала отбираем все проведенные документы за день (ГДЕ), затем сортируем их по номеру в порядке убывания (УПОРЯДОЧИТЬ ПО ... УБЫВ), и только после этого берем самую первую запись из отсортированного списка (ПЕРВЫЕ 1), которая и будет документом с самым большим номером.

Различия в поведении файловой и клиент-серверной баз

Самые интересные нюансы начинаются, когда мы переходим к более сложным запросам, например, с объединениями, и начинаем работать в клиент-серверном режиме (например, на MS SQL Server). В обсуждении на форуме был поднят именно такой случай, который приводил к неожиданным результатам.

Рассмотрим проблемный запрос из обсуждения:


ВЫБРАТЬ ПЕРВЫЕ 1
    РеализацияТоваровУслуг.Номер КАК Номер
ИЗ
    Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ГДЕ
    РеализацияТоваровУслуг.Дата >= НАЧАЛОПЕРИОДА(&ДатаДок, ДЕНЬ)
    И РеализацияТоваровУслуг.Дата <= КОНЕЦПЕРИОДА(&ДатаДок, ДЕНЬ)

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ ПЕРВЫЕ 1
    ПередачаТоваровМеждуОрганизациями.Номер
ИЗ
    Документ.ПередачаТоваровМеждуОрганизациями КАК ПередачаТоваровМеждуОрганизациями
ГДЕ
    ПередачаТоваровМеждуОрганизациями.Дата >= НАЧАЛОПЕРИОДА(&ДатаДок, ДЕНЬ)
    И ПередачаТоваровМеждуОрганизациями.Дата <= КОНЕЦПЕРИОДА(&ДатаДок, ДЕНЬ)

УПОРЯДОЧИТЬ ПО
    Номер УБЫВ

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

Реальность:

Вывод: поведение файловой базы может быть "удобным", но оно не соответствует стандарту SQL. Всегда пишите запросы так, чтобы они корректно работали в более строгой среде клиент-серверного варианта. Нельзя полагаться на сортировку внутри частей запроса до объединения.

Какой вариант запроса лучше: простой или с временной таблицей?

В первом сообщении автор предложил два варианта решения своей задачи.

Вариант 1: Простой запрос


ТекстЗапроса = "ВЫБРАТЬ ПЕРВЫЕ " + КоличествоВыборки + "
|    ВыборкаЭлементов.Ссылка КАК Ссылка
|ИЗ
|    " + ИмяТаблицы + " КАК ВыборкаЭлементов
|ГДЕ
|    НЕ ВыборкаЭлементов.ПометкаУдаления";

Этот вариант подходит для простых случаев. Он компактен и понятен. Но для получения предсказуемого результата ему не хватает УПОРЯДОЧИТЬ ПО.

Вариант 2: С использованием временной таблицы


ТекстЗапроса = "ВЫБРАТЬ
|    ВыборкаЭлементов.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_Элементы
|ИЗ
|    " + ИмяТаблицы + " КАК ВыборкаЭлементов
|ГДЕ
|    НЕ ВыборкаЭлементов.ПометкаУдаления
|;
|
|////////////////////////////////////////////////////////////­////////////////////
|ВЫБРАТЬ ПЕРВЫЕ " + КоличествоВыборки + "
|    ВТ.Ссылка КАК Ссылка
|ИЗ
|    ВТ_Элементы КАК ВТ
|УПОРЯДОЧИТЬ ПО
|    ВТ.Ссылка.Наименование"; // Добавим сортировку для предсказуемости

Этот подход является более универсальным, надежным и часто более читаемым для сложных задач:

Рекомендация: для простых выборок можно использовать первый вариант (не забывая про сортировку). Для сложных, многоэтапных запросов или для обеспечения кроссплатформенности лучше сразу использовать временные таблицы.

Практический совет: как правильно формировать текст запроса

В обсуждении было сделано очень важное замечание по стилю кода. Формирование текста запроса путем конкатенации (сложения) строк — плохая практика.

Плохой пример:


ТекстЗапроса = "ВЫБРАТЬ ПЕРВЫЕ " + КоличествоВыборки + " | ... | ИЗ | "+ВидОбъекта+"."+ИмяМетаданных;

Такой код сложно читать, отлаживать (его нельзя просто скопировать в консоль запросов), и он потенциально небезопасен. Вместо этого используйте параметры и шаблоны.

Хороший пример:


// Текст запроса лучше хранить как шаблон
// Имя таблицы можно подставить через СтрЗаменить, а количество - через параметр
ТекстЗапроса = "
|ВЫБРАТЬ ПЕРВЫЕ &КоличествоВыборки
|    ВыборкаЭлементов.Ссылка
|ИЗ
|    #ИмяТаблицы КАК ВыборкаЭлементов
|ГДЕ
|    НЕ ВыборкаЭлементов.ПометкаУдаления
|УПОРЯДОЧИТЬ ПО
|    ВыборкаЭлементов.Наименование";

ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "#ИмяТаблицы", "Справочник.Номенклатура");

Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапроса;
Запрос.УстановитьПараметр("КоличествоВыборки", 10);

Результат = Запрос.Выполнить();

Этот подход делает код чистым, понятным и удобным для поддержки и отладки.

← На главную