При разработке на платформе 1С часто возникает ситуация, когда необходимо получить данные из табличной части документа, но не все сразу, а только одну строку, которая является первой по порядку или удовлетворяет определенному условию. Сложность заключается в том, что стандартный оператор ВЫБРАТЬ ПЕРВЫЕ 1 работает для всего результата запроса целиком, а нам нужно получить "первую" строку для каждого документа в отдельности. В этой статье мы разберем, как правильно реализовать такую выборку на примере процесса согласования документов.
Рассмотрим конкретный пример. У нас есть документ Заявка, у которого имеется табличная часть Согласование. В этой таблице перечислены отделы, которые должны согласовать заявку, и их статусы (например, "Согласовано", "На согласовании", "Ожидает").
Нам необходимо определить, в каком отделе сейчас находится заявка. То есть нам нужно найти первую строку в табличной части, где статус еще не равен "Согласовано". Если мы напишем простой запрос с условием по статусу, система вернет нам все строки, где согласование еще не пройдено. Например, если заявку должны согласовать Отдел 2, Отдел 3 и Отдел 4, и Отдел 1 уже согласовал, то простой запрос вернет три строки. Нам же нужна только одна — Отдел 2.
Рассмотрим пример неэффективного запроса:
ВЫБРАТЬ
ЗаявкаСогласование.Ссылка КАК Ссылка,
ЗаявкаСогласование.Отдел КАК Отдел
ИЗ
Документ.Заявка.Согласование КАК ЗаявкаСогласование
ГДЕ
ЗаявкаСогласование.Статус = &СтатусНаСогласовании
Такой запрос выдаст "лишние" данные. Чтобы "схлопнуть" результат до одной строки на каждый документ, необходимо применить методику, схожую с получением среза последних в регистрах сведений.
Проанализируем ситуацию: как компьютер может понять, какая строка является "первой"? В системе 1С у каждой строки табличной части есть системный реквизит НомерСтроки. Именно на него мы и будем опираться. Логика решения будет состоять из двух этапов.
Этап 1. Поиск минимального номера строки. Нам нужно отобрать все строки, подходящие под условие, сгруппировать их по документу (поле Ссылка) и найти минимальный номер строки для каждой группы.
Этап 2. Получение данных этой строки. Мы соединим полученную временную таблицу с исходной табличной частью по двум полям: Ссылка и НомерСтроки. Это позволит нам вытащить Отдел и любые другие реквизиты именно той строки, которую мы определили как первую.
Разберем реализацию по шагам:
// Шаг 1: Выбираем минимальные номера строк во временную таблицу
ВЫБРАТЬ
ЗаявкаСогласование.Ссылка КАК Ссылка,
МИНИМУМ(ЗаявкаСогласование.НомерСтроки) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_МинимальныеСтроки
ИЗ
Документ.Заявка.Согласование КАК ЗаявкаСогласование
ГДЕ
ЗаявкаСогласование.Статус = &СтатусНаСогласовании
И ЗаявкаСогласование.Ссылка.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
ЗаявкаСогласование.Ссылка
ИНДЕКСИРОВАТЬ ПО
Ссылка,
НомерСтроки
;
// Шаг 2: Соединяем с ТЧ для получения данных конкретного отдела
ВЫБРАТЬ
ВТ_МинимальныеСтроки.Ссылка КАК ДокументСсылка,
ЗаявкаСогласование.Отдел КАК ТекущийОтдел,
ЗаявкаСогласование.Ссылка.Дата КАК ДатаДокумента,
ЗаявкаСогласование.Ссылка.Номер КАК НомерДокумента
ИЗ
ВТ_МинимальныеСтроки КАК ВТ_МинимальныеСтроки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Заявка.Согласование КАК ЗаявкаСогласование
ПО ВТ_МинимальныеСтроки.Ссылка = ЗаявкаСогласование.Ссылка
И ВТ_МинимальныеСтроки.НомерСтроки = ЗаявкаСогласование.НомерСтроки
Обратите внимание на использование ИНДЕКСИРОВАТЬ ПО во временной таблице. Это критически важно для производительности, особенно если в выборку попадает большое количество документов.
Существует альтернативный способ решения задачи без использования временных таблиц. Он может быть удобен для небольших отчетов или динамических списков, хотя традиционно считается чуть менее производительным на огромных объемах данных. Мы выясним причину: СУБД придется выполнять подзапрос для каждой проверки условия.
Здесь мы используем конструкцию В (ВЫБРАТЬ МИНИМУМ...) прямо в секции ГДЕ основного запроса.
ВЫБРАТЬ
ЗаявкаСогласование.Ссылка КАК Ссылка,
ЗаявкаСогласование.Отдел КАК Отдел,
ЗаявкаСогласование.Ссылка.Дата КАК Дата,
ЗаявкаСогласование.Ссылка.Номер КАК Номер
ИЗ
Документ.Заявка.Согласование КАК ЗаявкаСогласование
ГДЕ
ЗаявкаСогласование.Статус = &СтатусНаСогласовании
И ЗаявкаСогласование.НомерСтроки В (
ВЫБРАТЬ
МИНИМУМ(ТЧ_Вложенная.НомерСтроки)
ИЗ
Документ.Заявка.Согласование КАК ТЧ_Вложенная
ГДЕ
ТЧ_Вложенная.Ссылка = ЗаявкаСогласование.Ссылка
И ТЧ_Вложенная.Статус = &СтатусНаСогласовании
)
В данном случае условие ТЧ_Вложенная.Ссылка = ЗаявкаСогласование.Ссылка внутри подзапроса связывает внутреннюю выборку с текущей строкой внешнего запроса, обеспечивая поиск "первой" строки именно для текущего документа.
НомерСтроки. Однако, если ваша бизнес-логика подразумевает "первый по дате" или "первый по приоритету", просто замените МИНИМУМ(НомерСтроки) на МИНИМУМ(ВашеПолеДаты) или МАКСИМУМ(Приоритет).НомерСтроки в 1С — это порядковый номер в конкретном экземпляре документа. Если строки удалялись, номера могут идти не подряд (например, 1, 3, 5), но функция МИНИМУМ все равно корректно найдет ту строку, которая фактически находится выше остальных.ВЫБОР КОГДА. Посмотрим на ситуацию трезво: ВЫБОР помогает пометить строку или изменить значение в колонке, но он не умеет уменьшать количество строк в результате. Чтобы избавиться от лишних отделов, группировка или подзапрос обязательны.Ссылка.Проведен = ИСТИНА, как показано в примерах выше.Посмотрим на финальный результат: использование метода с временной таблицей (Метод 1) является наиболее гибким и масштабируемым решением. Оно позволяет не только найти "текущий" отдел на согласовании, но и легко расширить запрос, добавив информацию о предыдущих или последующих этапах, просто изменив условия группировки.