При разработке на платформе 1С:Предприятие мы часто сталкиваемся с различиями в поведении файлового и клиент-серверного вариантов работы базы данных. Одна из самых неочевидных и коварных проблем возникает при работе с временными таблицами и порядком строк в них. Рассмотрим ситуацию, когда запрос, идеально работающий на SQL-сервере, выдает несортированный результат в файловой версии, и разберем, как гарантировать правильный порядок данных.
Рассмотрим типичный сценарий. Разработчик пишет запрос, в котором данные помещаются во временную таблицу. Для ускорения последующей обработки (например, соединений) используется конструкция ИНДЕКСИРОВАТЬ ПО. При отладке на серверной базе (MS SQL) разработчик видит, что данные в этой временной таблице лежат строго по порядку индекса.
Однако, стоит запустить тот же код на файловой базе (или иногда на PostgreSQL, где для оптимизации может потребоваться генератор скрипта для индексации таблиц), как порядок строк становится хаотичным (обычно — в порядке вставки). Возникает вопрос: почему конструкция, которая, казалось бы, упорядочивает данные, перестает работать?
Давайте разберем пример запроса, вызывающего вопросы, который удобно отлаживать в инструменте вроде Консоли запросов:
ВЫБРАТЬ
Номенклатура,
Количество
ПОМЕСТИТЬ ВТ_Товары
ИЗ
Документ.РеализацияТоваровУслуг.Товары
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
ВЫБРАТЬ
*
ИЗ
ВТ_Товары
Многие ожидают, что итоговая выборка будет отсортирована по полю Номенклатура. Но в файловой базе этого не происходит. Давайте выясним причины и найдем решения.
Чтобы понять корень проблемы, нам нужно заглянуть "под капот" систем управления базами данных. Ключевое различие кроется в том, как именно реализованы индексы. Чтобы глубже вникнуть в детали, можно проанализировать SQL сервер глазами 1С-ника с помощью специальных инструментов.
1. Поведение MS SQL Server (Кластерные индексы)
В большинстве случаев, когда платформа 1С создает индекс для временной таблицы на MS SQL Server, она создает так называемый кластерный индекс (Clustered Index). Особенность кластерного индекса в том, что он определяет физический порядок хранения данных на диске. Сама таблица фактически превращается в B-дерево, отсортированное по ключу индекса. Конечно, для стабильной и быстрой работы сервера важна его своевременная настройка регламентных операций MS SQL SERVER.
Когда вы делаете простую выборку ВЫБРАТЬ * ИЗ ВТ_Товары, SQL Server читает страницы данных последовательно. Поскольку данные физически упорядочены индексом, результат возвращается отсортированным. Это побочный эффект архитектуры, а не гарантия стандарта SQL.
2. Поведение Файловой СУБД (1С Native)
Движок файловой базы данных 1С устроен иначе. При создании индекса (конструкция ИНДЕКСИРОВАТЬ ПО) создается отдельная структура данных (дерево ссылок), которая помогает быстро искать записи. Однако сами записи в таблице данных остаются лежать в том порядке, в котором они были записаны (обычно это heap или append-only структура).
Когда вы делаете выборку всех записей без явного указания сортировки, движок просто читает таблицу подряд, игнорируя индекс, так как это быстрее, чем "прыгать" по индексу. В результате вы получаете данные в порядке вставки, то есть неотсортированными.
Мы должны запомнить фундаментальное правило работы с базами данных, которое игнорируют многие начинающие программисты 1С:
Порядок следования записей в выборке не гарантируется, если явно не указана конструкция УПОРЯДОЧИТЬ ПО.
Любая сортировка, которую вы видите без этой конструкции (например, после индексирования на SQL) — это случайность или особенность конкретной реализации СУБД, на которую нельзя полагаться в промышленном коде.
Самый надежный и правильный способ получить отсортированные данные — явно запросить сортировку в момент получения данных из временной таблицы. Это работает одинаково стабильно на любой платформе: файловой, MS SQL, PostgreSQL или IBM DB2.
Давайте скорректируем наш подход. Мы создаем временную таблицу, индексируем её (для скорости соединений, а не для сортировки!), а затем при выборке указываем порядок.
// Шаг 1: Помещаем данные во временную таблицу
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
Товары.Количество КАК Количество
ПОМЕСТИТЬ ВТ_Товары
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
ИНДЕКСИРОВАТЬ ПО
Номенклатура // Индекс нужен для оптимизации будущих соединений
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Выбираем данные с явной сортировкой
ВЫБРАТЬ
Т.Номенклатура,
Т.Количество
ИЗ
ВТ_Товары КАК Т
УПОРЯДОЧИТЬ ПО
Т.Номенклатура // Вот это гарантирует порядок!
Этот метод является единственно верным с точки зрения стандартов языка запросов 1С.
Иногда возникают задачи, когда нам жизненно необходимо, чтобы данные хранились во временной таблице уже в определенном порядке. Это может быть нужно для специфических алгоритмов, например, для расчета нарастающего итога, распределения резервов по приоритетам или списания по ФИФО, когда мы планируем обходить выборку программно.
Хотя стандарт SQL говорит, что таблицы — это неупорядоченные множества, в 1С существует прием, позволяющий "заставить" систему записать данные в нужном порядке. Для этого используется комбинация ВЫБРАТЬ ПЕРВЫЕ и УПОРЯДОЧИТЬ ПО при помещении данных.
Рассмотрим, как это выглядит:
ВЫБРАТЬ ПЕРВЫЕ 999999999 // Берем заведомо большее число, чем есть записей
Товары.Номенклатура,
Товары.Количество
ПОМЕСТИТЬ ВТ_СортированныеТовары
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК Товары
УПОРЯДОЧИТЬ ПО
Товары.Номенклатура
Как это работает?
УПОРЯДОЧИТЬ ПО заставляет СУБД отсортировать входящий поток данных перед выполнением оператора вставки.ПЕРВЫЕ N необходима, так как во многих диалектах SQL (включая T-SQL) сортировка в подзапросах или при вставке игнорируется, если не указано ограничение количества записей (TOP).Важные предостережения по использованию метода №2:
УПОРЯДОЧИТЬ ПО, если вы хотите 100% гарантии.Давайте резюмируем разницу, чтобы избежать путаницы в будущем.
Используйте, когда эта временная таблица будет участвовать в СОЕДИНЕНИЕ с другими таблицами или на нее будут накладываться условия ГДЕ. Индекс ускоряет поиск пар строк и фильтрацию. Это особенно важно, когда вы проводите замер производительности и поиск узких мест в системе. Он не предназначен для сортировки вывода пользователя.
Используйте всегда, когда важен визуальный порядок строк в отчете или логический порядок обработки (например, сначала старые заказы, потом новые). Это команда для презентации данных.
Если вы уже выгрузили результат запроса в объект ТаблицаЗначений, вы можете отсортировать его методами встроенного языка, не нагружая сервер БД повторными запросами.
Результат = Запрос.Выполнить().Выгрузить();
Результат.Сортировать("Номенклатура Возр");
Предположим, нам нужно распределить остатки товара по заказам покупателей исходя из приоритета (даты заказа). Посмотрим на правильную реализацию с использованием менеджера временных таблиц.
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Этап 1: Собираем заказы
Запрос.Текст =
"ВЫБРАТЬ
| Заказ.Ссылка КАК Ссылка,
| Заказ.Дата КАК Дата,
| Заказ.Приоритет КАК Приоритет
|ПОМЕСТИТЬ ВТ_ОчередьЗаказов
|ИЗ
| Документ.ЗаказПокупателя КАК Заказ
|ГДЕ
| Заказ.Проведен
|// Здесь ИНДЕКСИРОВАТЬ ПО не обязательно, если дальше нет соединений
|";
Запрос.Выполнить();
// Этап 2: Получаем данные для обработки в строго определенном порядке
Запрос.Текст =
"ВЫБРАТЬ
| Т.Ссылка,
| Т.Дата,
| Т.Приоритет
|ИЗ
| ВТ_ОчередьЗаказов КАК Т
|
|УПОРЯДОЧИТЬ ПО
| Т.Приоритет УБЫВ,
| Т.Дата ВОЗР";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Здесь мы гарантированно обрабатываем заказы от важных к обычным,
// и от старых к новым
КонецЦикла;
Полученные в итоге упорядоченные данные можно использовать для дальнейшей обработки или вывода в сложные отчеты.
Проблема отсутствия сортировки в файловой базе при использовании ИНДЕКСИРОВАТЬ ПО — это нормальное поведение платформы. Файловый движок не обязан перестраивать физический порядок данных при создании индекса.
Чтобы ваш код работал предсказуемо и одинаково на любых платформах (File, MS SQL, PostgreSQL), всегда явно указывайте УПОРЯДОЧИТЬ ПО в тот момент, когда вы извлекаете данные для окончательной обработки или вывода пользователю. Не полагайтесь на неявные эффекты оптимизаторов СУБД.