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