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