При разработке алгоритмов заполнения документов, списания запасов или распределения затрат в 1С часто возникает задача приоритетной обработки определенных данных. Например, нам необходимо, чтобы при получении остатков по регистру конкретная номенклатура (или статья затрат) всегда оказывалась на первой строчке в результирующей таблице запроса, чтобы алгоритм списания обработал ее в первую очередь. В этой статье мы подробно разберем несколько способов решения этой задачи, проанализируем частые ошибки разработчиков и выясним, как эти механизмы работают "под капотом" СУБД.
Самый лаконичный способ поднять строку с определенным значением наверх — это сортировка по булеву полю, в котором проверяется соответствие нашей записи заданному параметру. Давайте посмотрим на пример реализации такого запроса:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Код КАК Код,
Номенклатура.Ссылка = &ПриоритетнаяНоменклатура КАК ЭтоПриоритет
ИЗ
Справочник.Номенклатура КАК Номенклатура
УПОРЯДОЧИТЬ ПО
ЭтоПриоритет УБЫВ
Важное предупреждение: обратите внимание на направление сортировки! Распространенная ошибка начинающих разработчиков — использовать сортировку по возрастанию (ВОЗР). В СУБД логическое значение Ложь эквивалентно числу 0, а значение Истина — числу 1. Если вы укажете направление ВОЗР, то сначала пойдут все строки со значением Ложь (все остальные элементы), а нужная строка со значением Истина окажется в самом низу выборки. Чтобы нужная строка гарантированно поднялась наверх, сортировать булево поле нужно строго по убыванию (УБЫВ).
Чтобы избежать путаницы с направлениями сортировки булевых полей и сделать код максимально понятным для любого программиста, который будет сопровождать вашу конфигурацию, лучше использовать явное определение приоритетов через конструкцию ВЫБОР. Разберем по шагам этот вариант:
ВЫБОР присваиваем приоритетному элементу числовой индекс 0, а всем остальным элементам — индекс 1.УПОРЯДОЧИТЬ ПО указываем сортировку по нашему вычисляемому полю по возрастанию (ВОЗР).Проанализируем пример кода с такой реализацией:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Код КАК Код,
ВЫБОР
КОГДА Номенклатура.Ссылка = &ПриоритетнаяНоменклатура ТОГДА 0
ИНАЧЕ 1
КОНЕЦ КАК ПорядокСортировки
ИЗ
Справочник.Номенклатура КАК Номенклатура
УПОРЯДОЧИТЬ ПО
ПорядокСортировки ВОЗР,
Код ВОЗР
В данном примере приоритетный элемент получит значение поля ПорядокСортировки равное 0 и за счет сортировки ВОЗР окажется самым первым. Все остальные элементы получат индекс 1 и будут отсортированы ниже, упорядочиваясь между собой уже по дополнительному полю (в данном случае — по коду номенклатуры).
Иногда разработчики предлагают использовать объединение двух разных запросов. В первом запросе выбирается только приоритетный элемент, а во втором запросе — все остальные элементы (с исключением приоритетного через оператор НЕ или <>). Пример такой схемы:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Код КАК Код
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка = &ПриоритетнаяНоменклатура
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Код КАК Код
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка <> &ПриоритетнаяНоменклатура
Внимание: данный способ без явной сортировки опасен! У разработчиков часто возникает ложное ощущение порядка: кажется, что СУБД всегда возвращает строки именно в той последовательности, в которой они объединены в тексте запроса. На файловой базе или на небольших таблицах это действительно может совпадать. Однако, согласно стандарту SQL, СУБД (будь то MS SQL, PostgreSQL или Oracle) не гарантирует порядок строк в результате, если в запросе отсутствует явная секция УПОРЯДОЧИТЬ ПО. Оптимизатор СУБД может построить параллельный план выполнения или прочитать данные из индексов так, что строки из второй части объединения вернутся раньше первой.
Если вы всё же решили использовать ОБЪЕДИНИТЬ ВСЕ, обязательно добавьте синтетическое поле порядка в каждую часть запроса и выполните сортировку в самом конце:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Код КАК Код,
0 КАК Порядок
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка = &ПриоритетнаяНоменклатура
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Код КАК Код,
1 КАК Порядок
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Ссылка <> &ПриоритетнаяНоменклатура
УПОРЯДОЧИТЬ ПО
Порядок ВОЗР
Этот вариант абсолютно надежен, но делает текст запроса избыточно сложным и длинным по сравнению с использованием конструкции ВЫБОР.
Давайте выясним, как платформа 1С транслирует эти конструкции в SQL-запросы для СУБД и как это влияет на скорость работы системы. Выражение Ссылка = &ПриоритетнаяНоменклатура преобразуется платформой в стандартную SQL-конструкцию CASE WHEN ... THEN 1 ELSE 0 END (так как во многих СУБД нет отдельного логического типа данных в таблицах, используются типы BIT или TINYINT).
С точки зрения производительности нужно учитывать объем обрабатываемых данных:
CASE WHEN в процессе сортировки ничтожно малы. СУБД мгновенно выполнит эту операцию в оперативной памяти. Для задач списания затрат по конкретной номенклатуре или партионного учета этот способ абсолютно безопасен.УПОРЯДОЧИТЬ ПО лишает СУБД возможности эффективно использовать индексы для сортировки. СУБД будет вынуждена произвести физическую сортировку всей выборки в памяти или во временной базе (например, TempDB в MS SQL). Если вам действительно нужно обработать гигантский массив данных, правильной стратегией будет сначала максимально ограничить выборку (например, отобрать нужные записи во временную таблицу), и только в финальном коротком запросе производить сортировку с приоритетом.