Как избежать дублирования строк и ошибки ключевых полей при левом соединении в динамическом списке

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

При разработке интерфейсов на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью вывести в список документов или справочников дополнительную информацию из связанных таблиц. Казалось бы, стандартное решение — использовать произвольный запрос с оператором ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN). Однако на практике это часто приводит к неприятной ошибке: «Дублирование ключевых полей». Программа просто отказывается открывать форму списка, если одному объекту в основной таблице соответствует несколько записей в присоединенной.

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

Выясняем причину: почему «размножаются» строки

Для начала проанализируем ситуацию, описанную автором темы. В форму списка документов выводится запрос, где основная таблица (например, Документ.РеализацияТоваровУслуг) соединяется с таблицей подчиненных документов (например, Документ.СчетФактураВыданный) по реквизиту ДокументОснование.

Логика ЛЕВОЕ СОЕДИНЕНИЕ в языке SQL (и в языке запросов 1С) такова: для каждой строки из левой таблицы ищутся соответствия в правой таблице. Если на одно основание в правой таблице по ошибке или по логике бизнес-процесса введено два и более документа, СУБД обязана вернуть столько же строк, сколько найдено соответствий. Это не «полное соединение», а классическое поведение левого соединения в ситуации «один ко многим».

Проблема динамического списка заключается в том, что для корректного отображения данных, работы навигации и пагинации платформе требуется уникальный ключ для каждой строки. Обычно таким ключом является Ссылка основного документа. Если запрос возвращает две строки с одинаковой ссылкой, платформа 1С фиксирует дублирование ключевых полей и прерывает работу с ошибкой.

Решение №1: Использование группировки и агрегатных функций

Если нам нужно просто показать наличие связанного документа или вывести какой-то один из них (например, самый первый или самый свежий), самым простым и эффективным способом будет использование предложения СГРУППИРОВАТЬ ПО. Рассмотрим подробнее, как изменить запрос динамического списка.

Вместо прямого соединения всех полей, мы выберем только нужные нам данные и применим функцию МАКСИМУМ к ссылке связанного документа. Разберем по шагам:

  1. Откройте настройки динамического списка и перейдите к редактированию текста запроса.
  2. Добавьте группировку по всем полям основной таблицы.
  3. Для поля из правой (присоединяемой) таблицы используйте агрегатную функцию.

Пример кода запроса:


ВЫБРАТЬ
    ОсновнаяТаблица.Ссылка КАК Ссылка,
    ОсновнаяТаблица.Дата КАК Дата,
    ОсновнаяТаблица.Номер КАК Номер,
    МАКСИМУМ(СвязанныйДокумент.Ссылка) КАК СсылкаНаСвязанныйДокумент
ИЗ
    Документ.РеализацияТоваровУслуг КАК ОсновнаяТаблица
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.СчетФактураВыданный КАК СвязанныйДокумент
        ПО СвязанныйДокумент.ДокументОснование = ОсновнаяТаблица.Ссылка
СГРУППИРОВАТЬ ПО
    ОсновнаяТаблица.Ссылка,
    ОсновнаяТаблица.Дата,
    ОсновнаяТаблица.Номер

В данном случае, даже если на одну реализацию выписано два счета-фактуры, функция МАКСИМУМ выберет только одну ссылку, и дублирование строк в списке исчезнет.

Решение №2: Соединение с вложенным запросом

Иногда использование СГРУППИРОВАТЬ ПО во всем запросе может быть неудобным или избыточным, особенно если полей в основной таблице очень много. В такой ситуации лучше сначала подготовить «правую» часть соединения, избавившись от дублей внутри нее. Проанализируем, как это реализовать через вложенный запрос.

Мы создаем подзапрос, который для каждого основания возвращает ровно одну запись, и уже этот результат соединяем с основной таблицей.

Рассмотрим пример реализации:


ВЫБРАТЬ
    Реализации.Ссылка,
    Реализации.Дата,
    ВложенныйЗапрос.СсылкаНаДокумент
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализации
        ЛЕВОЕ СОЕДИНЕНИЕ (
            ВЫБРАТЬ
                Связи.ДокументОснование КАК ДокументОснование,
                МАКСИМУМ(Связи.Ссылка) КАК СсылкаНаДокумент
            ИЗ
                Документ.СчетФактураВыданный КАК Связи
            СГРУППИРОВАТЬ ПО
                Связи.ДокументОснование
        ) КАК ВложенныйЗапрос
        ПО Реализации.Ссылка = ВложенныйЗапрос.ДокументОснование

Преимущество этого метода: Мы четко контролируем, что в правую часть соединения попадает уникальный набор ключей. Это гарантирует, что количество строк в итоговой выборке будет в точности соответствовать количеству строк в основной таблице РеализацияТоваровУслуг.

Решение №3: Коррелированный подзапрос в списке выбора

В современных версиях платформы 1С 8.3 в динамических списках часто (хотя и не во всех случаях, это зависит от сложности и версии платформы) можно использовать выбор значения прямо в секции ВЫБРАТЬ. Это позволяет избежать явного использования ЛЕВОЕ СОЕДИНЕНИЕ в структуре ИЗ.

Посмотрим на пример такой конструкции:


ВЫБРАТЬ
    Т.Ссылка,
    Т.Номер,
    Т.Дата,
    (ВЫБРАТЬ ПЕРВЫЕ 1 Подчиненный.Ссылка 
     ИЗ Документ.СчетФактураВыданный КАК Подчиненный 
     ГДЕ Подчиненный.ДокументОснование = Т.Ссылка) КАК СсылкаНаСвязанныйДокумент
ИЗ
    Документ.РеализацияТоваровУслуг КАК Т

Здесь для каждой строки основной таблицы выполняется микро-запрос, который возвращает ровно одно значение (благодаря оператору ПЕРВЫЕ 1). Если подчиненных документов нет, поле будет содержать NULL, что аналогично поведению левого соединения. Этот способ элегантен, но стоит помнить, что при огромных объемах данных он может нагружать СУБД сильнее, чем классическое соединение с предварительной группировкой.

Важные рекомендации по оптимизации

Работа с динамическими списками требует особого внимания к производительности. Рассмотрим несколько правил, которые помогут вашим спискам работать быстро:

  1. Используйте основную таблицу: Всегда указывайте свойство Основная таблица в настройках динамического списка. Это позволяет платформе оптимизировать запросы, использовать правильные индексы и корректно выполнять поиск/фильтрацию.
  2. Избегайте лишних соединений: Если данные, которые вы хотите вывести (например, номер или дата связанного документа), можно получить через точку от ссылки (СвязанныйДокумент.Дата), и вы уже обеспечили уникальность этой ссылки, то дополнительных соединений не требуется.
  3. Индексация: Убедитесь, что поле, по которому происходит соединение (в нашем примере это ДокументОснование), проиндексировано в конфигураторе. Без индекса соединение больших таблиц будет крайне медленным.
  4. Программный контроль: Как справедливо замечено в обсуждении на форуме, лучшая профилактика ошибки «дублирования ключевых полей» — это отсутствие самих дублей. Если бизнес-логика подразумевает строгую связь 1 к 1, добавьте в модули документов проверку, которая не позволит ввести второй документ-основание по ошибке.

Подведем итог. Проблема дублей при левом соединении — это не баг платформы, а стандартное поведение реляционных баз данных. Чтобы «заставить» соединение не плодить строки, мы должны либо сгруппировать результат, либо ограничить правую таблицу так, чтобы в ней осталась только одна запись на каждый ключ связи. Выбор конкретного метода (группировка, вложенный запрос или выбор в поле) зависит от сложности вашего запроса и требований к производительности системы.

← На главную