Соответствие 1С — коллекция пар «ключ — значение», в которой ключом может быть почти что угодно: ссылка на справочник, дата, число, строка. Значение по ключу находится без перебора всех элементов, поэтому соответствие берут, когда нужно быстро сопоставить одно с другим: артикул из файла с номенклатурой, контрагента с суммой долга. Ниже — основы работы с соответствием: создание и заполнение, разница между Получить() и квадратными скобками, какие ключи работают, а какие нет, обход через КлючИЗначение и главный практический приём: соответствие как кэш вместо запроса в цикле, с кодом для замера.
От структуры соответствие отличается тремя вещами. Ключ не обязан быть строкой. К элементу обращаются только через метод или квадратные скобки — точка не работает. Отсутствующий ключ при чтении не вызывает ошибку, а даёт Неопределено. Объект «Соответствие» есть в любой конфигурации на платформе 8.3 и доступен и на клиенте, и на сервере.
Где встречается в реальном коде:
ПрочитатьJSON(Чтение, Истина) вернёт соответствие вместо структуры — это нужно, когда ключи JSON не годятся в имена переменных;ОбщегоНазначения.ЗначенияРеквизитовОбъектов() возвращает соответствие «ссылка → структура реквизитов».Цены = Новый Соответствие;
Цены.Вставить("A-100", 12.5); // ключа не было - элемент добавится
Цены.Вставить("A-100", 14); // ключ уже есть - значение заменится, ошибки нет
Цены["B-200"] = 30; // то же, что Вставить: добавит или заменит
// Ключ - дата, значение - другая коллекция
ПродажиПоДням = Новый Соответствие;
ПродажиПоДням.Вставить('20260115', Новый Массив);
Цены.Удалить("B-200"); // удалить элемент по ключу
Всего = Цены.Количество(); // сколько элементов
Цены.Очистить(); // удалить всё
Конструктор без параметров создаёт пустое соответствие. Метод Вставить(Ключ, Значение) добавляет элемент, а если такой ключ уже есть — молча перезаписывает значение. Квадратные скобки слева от знака «равно» делают то же самое. Чтобы сохранить первое значение, проверьте ключ перед вставкой — как в примере с кэшем ниже.
Цены = Новый Соответствие;
Цены.Вставить("A-100", 12.5);
Цена1 = Цены.Получить("A-100"); // 12.5
Цена2 = Цены["A-100"]; // 12.5 - то же самое
Цена3 = Цены.Получить("Z-999"); // Неопределено, ошибки нет
Цена4 = Цены["Z-999"]; // тоже Неопределено
// Цена5 = Цены.Артикул; // ошибка: через точку к соответствию не обращаются
При чтении Получить() и скобки ведут себя одинаково: вернут значение или Неопределено, если ключа нет. Разница в том, что скобками и читают, и пишут, а метод только читает. В длинном выражении Получить() сразу показывает, что коллекция не меняется, скобки короче — выберите один стиль на модуль.
Главная ловушка — Неопределено означает сразу две вещи: «ключа нет» и «ключ есть, но значение пустое». Отдельного метода для проверки наличия ключа, как Свойство() у структуры, у соответствия нет. Самый простой выход — не класть Неопределено в значения: для «множеств» пишите Истина, для числовых итогов — 0, для ссылок — ссылку или пустую ссылку.
| Действие | Соответствие | ФиксированноеСоответствие |
|---|---|---|
| Добавить или заменить | Вставить(Ключ, Значение), С[Ключ] = Значение | нельзя |
| Прочитать | Получить(Ключ), С[Ключ] | Получить(Ключ), С[Ключ] |
| Удалить элемент | Удалить(Ключ) | нельзя |
| Очистить | Очистить() | нельзя |
| Число элементов | Количество() | Количество() |
| Обход | Для Каждого … Из … Цикл | Для Каждого … Из … Цикл |
Соответствие сравнивает ключи так же, как оператор «=». Отсюда правила:
"Болт" и "болт" — разные ключи. Пробелы по краям тоже учитываются. Ключи из внешних файлов приводите к одному виду: ВРег(СокрЛП(Значение)).'20260115' и '20260115093000' — разные ключи. Для группировки по дням приводите дату через НачалоДня(), по месяцам — через НачалоМесяца().Поэтому в ключ кладут значения примитивных типов и ссылки — то, что не меняется после создания. Если тип заранее неизвестен, проверьте его до вставки — приёмы есть в статье тип значения 1С. Когда ключ составной, например товар плюс склад, вложите одно соответствие в другое:
// Остатки[Номенклатура][Склад] = Количество
Процедура ДобавитьОстаток(Остатки, Номенклатура, Склад, Количество)
ПоСкладам = Остатки.Получить(Номенклатура);
Если ПоСкладам = Неопределено Тогда
ПоСкладам = Новый Соответствие;
Остатки.Вставить(Номенклатура, ПоСкладам);
КонецЕсли;
Было = ПоСкладам.Получить(Склад);
ПоСкладам.Вставить(Склад, ?(Было = Неопределено, 0, Было) + Количество);
КонецПроцедуры
Внутреннее соответствие хранится по ссылке: после Остатки.Вставить(Номенклатура, ПоСкладам) все изменения ПоСкладам видны через Остатки, вставлять его повторно не нужно.
Долги = Новый Соответствие;
Долги.Вставить("ООО Альфа", 15000);
Долги.Вставить("ООО Бета", 0);
Долги.Вставить("ИП Гамма", 4200);
Для Каждого КлючИЗначение Из Долги Цикл
Сообщить(КлючИЗначение.Ключ + ": " + Строка(КлючИЗначение.Значение));
КонецЦикла;
// Удаление по условию: сначала собрать ключи, потом удалить
КУдалению = Новый Массив;
Для Каждого Элемент Из Долги Цикл
Если Элемент.Значение = 0 Тогда
КУдалению.Добавить(Элемент.Ключ);
КонецЕсли;
КонецЦикла;
Для Каждого Ключ Из КУдалению Цикл
Долги.Удалить(Ключ);
КонецЦикла;
Каждый элемент при обходе — объект типа КлючИЗначение с двумя свойствами: Ключ и Значение. Имя переменной цикла любое, КлючИЗначение — просто привычное.
Порядок обхода документацией не обещан. На практике элементы часто идут в порядке добавления, но строить на этом логику не стоит. Если порядок нужен, переложите пары в таблицу значений с колонками «Ключ» и «Значение» и отсортируйте её.
Удалять элементы того же соответствия внутри Для Каждого не надо — коллекция меняется во время обхода, результат непредсказуем. Соберите ключи в массив и удалите после цикла, как во второй половине примера.
Типичная картина в обработке загрузки: из Excel пришло несколько тысяч строк с артикулами, и для каждой строки код ищет номенклатуру:
// Так делать не надо: по запросу к базе на каждую строку файла
Для Каждого СтрокаФайла Из ДанныеФайла Цикл
СтрокаФайла.Номенклатура = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", СтрокаФайла.Артикул);
КонецЦикла;
НайтиПоРеквизиту() — это запрос к базе. Сколько строк в файле, столько обращений к СУБД. В файловой базе на локальном диске это терпимо, в клиент-серверной время растёт вместе с числом строк. Та же беда с чтением реквизита через точку в цикле: СтрокаФайла.Номенклатура.ЕдиницаИзмерения тоже идёт в базу, если данных нет в кэше платформы.
Решение — один запрос на весь список, результат в соответствие, дальше поиск в памяти:
// Серверный код: общий модуль с флажком «Сервер» или модуль объекта обработки.
// Платформа 8.3. Справочник Номенклатура с реквизитом Артикул есть
// в УТ 11, БП 3.0, КА 2 и ERP 2; в другой конфигурации замените имена.
// Приводит артикул к виду, в котором он лежит ключом в кэше.
// Число из Excel переводим в строку без разделителя групп: "12345", а не "12 345".
Функция КлючАртикула(Артикул) Экспорт
Если ТипЗнч(Артикул) = Тип("Число") Тогда
Возврат Формат(Артикул, "ЧГ=0");
КонецЕсли;
Возврат ВРег(СокрЛП(Артикул));
КонецФункции
// Возвращает соответствие: ключ артикула (Строка) -> СправочникСсылка.Номенклатура.
// Все артикулы ищутся одним запросом.
Функция НоменклатураПоАртикулам(Артикулы) Экспорт
Кэш = Новый Соответствие;
Если Артикулы.Количество() = 0 Тогда
Возврат Кэш;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Артикул В (&Артикулы)
| И Номенклатура.Артикул <> """"
| И НЕ Номенклатура.ПометкаУдаления
|
|УПОРЯДОЧИТЬ ПО
| Номенклатура.Код";
Запрос.УстановитьПараметр("Артикулы", Артикулы);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Ключ = КлючАртикула(Выборка.Артикул);
// Дубли артикула в справочнике: оставляем первый элемент по коду
Если Кэш.Получить(Ключ) = Неопределено Тогда
Кэш.Вставить(Ключ, Выборка.Ссылка);
КонецЕсли;
КонецЦикла;
Возврат Кэш;
КонецФункции
// Заполняет колонку Номенклатура в таблице загрузки.
// ДанныеФайла - ТаблицаЗначений с колонками Артикул (Строка или Число) и Номенклатура.
// Возвращает количество строк, для которых номенклатура не найдена.
Функция СопоставитьНоменклатуру(ДанныеФайла) Экспорт
// В запрос уходит тот же вид артикула, по которому потом ищем в кэше.
// Пустые артикулы не ищем: иначе они сопоставятся со случайным элементом или группой.
Уникальные = Новый Соответствие;
Для Каждого СтрокаФайла Из ДанныеФайла Цикл
Ключ = КлючАртикула(СтрокаФайла.Артикул);
Если Ключ <> "" Тогда
Уникальные.Вставить(Ключ, Истина);
КонецЕсли;
КонецЦикла;
Артикулы = Новый Массив;
Для Каждого Элемент Из Уникальные Цикл
Артикулы.Добавить(Элемент.Ключ);
КонецЦикла;
Кэш = НоменклатураПоАртикулам(Артикулы);
НеНайдено = 0;
Для Каждого СтрокаФайла Из ДанныеФайла Цикл
// Пустого ключа в кэше нет: Получить("") вернёт Неопределено
Найдено = Кэш.Получить(КлючАртикула(СтрокаФайла.Артикул));
Если Найдено = Неопределено Тогда
НеНайдено = НеНайдено + 1;
Иначе
СтрокаФайла.Номенклатура = Найдено;
КонецЕсли;
КонецЦикла;
Возврат НеНайдено;
КонецФункции
// Замер: запрос в цикле против кэша. Запускайте на копии базы и на одном файле.
Процедура СравнитьСпособы(ДанныеФайла) Экспорт
Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
Для Каждого СтрокаФайла Из ДанныеФайла Цикл
СтрокаФайла.Номенклатура = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", СтрокаФайла.Артикул);
КонецЦикла;
ВремяВЦикле = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
СопоставитьНоменклатуру(ДанныеФайла);
ВремяСКэшем = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Сообщить("Строк: " + XMLСтрока(ДанныеФайла.Количество())
+ "; запрос в цикле, мс: " + XMLСтрока(ВремяВЦикле)
+ "; соответствие, мс: " + XMLСтрока(ВремяСКэшем));
КонецПроцедуры
КлючАртикула() вызывается при подготовке параметра, при заполнении кэша и при поиске. Запрос сравнивает строки без учёта регистра и находит «a-100» по «A-100», а соответствие — с учётом. Без единой функции нормализации часть строк окажется «не найдена», хотя запрос их вернул.Если Кэш.Получить(Ключ) = Неопределено — защита от дублей артикула. Благодаря УПОРЯДОЧИТЬ ПО Код выбор первого элемента предсказуем: при повторном запуске загрузка сопоставит строки так же.Артикул <> "" в запросе — страховка на случай, если функцию вызовут с «сырым» массивом. Для десятков тысяч артикулов передайте таблицу значений и соедините её со справочником через временную таблицу.Запрос, ни менеджера Справочники. Из формы вызывайте эти функции через процедуру с директивой &НаСервере или через серверный общий модуль.Процедура СравнитьСпособы() прогоняет оба варианта по одной таблице и выводит время через ТекущаяУниверсальнаяДатаВМиллисекундах() (есть с версии 8.2.17). Запускайте её на копии рабочей базы с реальным файлом: на десятке строк разницы не видно. Первый прогон прогревает кэш СУБД — повторите сравнение два-три раза. Во втором варианте обращение к базе одно, остальное — поиск в памяти.
Тот же приём работает для реквизитов: вместо Ссылка.Реквизит в цикле соберите ссылки в массив, одним запросом получите нужные поля и сложите в соответствие «ссылка → структура». В конфигурациях на БСП это делает готовая функция ОбщегоНазначения.ЗначенияРеквизитовОбъектов(Ссылки, "Реквизит1, Реквизит2"). Про саму структуру и её методы — в статье структура 1С.
// Сумма оплат по контрагентам: данные уже в памяти, база не нужна
Итоги = Новый Соответствие;
Для Каждого СтрокаОплаты Из Оплаты Цикл
Было = Итоги.Получить(СтрокаОплаты.Контрагент);
Итоги.Вставить(СтрокаОплаты.Контрагент, ?(Было = Неопределено, 0, Было) + СтрокаОплаты.Сумма);
КонецЦикла;
// Уникальные значения массива: повторная вставка того же ключа ничего не добавляет
Уникальные = Новый Соответствие;
Для Каждого Код Из КодыИзФайла Цикл
Уникальные.Вставить(Код, Истина);
КонецЦикла;
БезПовторов = Новый Массив;
Для Каждого Элемент Из Уникальные Цикл
БезПовторов.Добавить(Элемент.Ключ);
КонецЦикла;
Для нового контрагента Получить() вернёт Неопределено, и ?(Было = Неопределено, 0, Было) превратит его в ноль. Если данные уже лежат в таблице значений и нужна только сумма, короче вызвать Свернуть("Контрагент", "Сумма"). Соответствие выгоднее, когда итоги собираются из разных источников или потом по ним часто ищут.
Настройки = Новый Соответствие;
Настройки.Вставить("НДС", 20);
Фикс = Новый ФиксированноеСоответствие(Настройки); // снимок только для чтения
Ставка = Фикс.Получить("НДС"); // читать можно
Копия = Новый Соответствие(Фикс); // изменяемая копия
Копия.Вставить("НДС", 22);
ФиксированноеСоответствие — версия только для чтения: Получить(), скобки, Количество() и обход есть, а Вставить, Удалить и Очистить — нет. Его возвращают из общих модулей с повторным использованием возвращаемых значений: результат такого модуля кэшируется, и изменяемую коллекцию вызывающий код мог бы испортить для всех следующих вызовов. Фиксированное соответствие может быть и типом параметра сеанса.
Обычное соответствие можно передать параметром серверной процедуры и получить обратно, если ключи и значения тоже передаются между клиентом и сервером: примитивы, ссылки, массивы, структуры, другие соответствия. Таблицу значений внутри соответствия на тонкий клиент не передать.
Цены.Артикул вместо Цены["Артикул"] даёт ошибку «Поле объекта не обнаружено (Артикул)». Точка работает у структуры, у соответствия — только скобки и методы.Кэш.Получить(Новый Структура("Товар, Склад", Товар, Склад)) всегда вернёт Неопределено: новая структура не равна той, что вставили раньше. Используйте вложенные соответствия.Получить(Ключ) = Неопределено при пустых значениях. Ключ есть, значение Неопределено, а код считает, что ключа нет, и перезаписывает элемент. Не храните Неопределено в значениях.Фикс.Вставить("НДС", 22) — ошибка «Метод объекта не обнаружен (Вставить)». Сделайте копию: Новый Соответствие(Фикс)."", условие В (&Артикулы) находит все элементы без артикула и группы, и строка молча получает случайный элемент. Отсекайте пустые ключи до запроса.Формат(Артикул, "ЧГ=0").Структура — для известного набора полей: ключ-строка по правилам имени переменной, обращение через точку, отсутствующий ключ — ошибка. Соответствие — для поиска по данным: ключ любого типа, только скобки и методы, отсутствующий ключ даёт Неопределено.
Сравнить Получить(Ключ) с Неопределено. Это надёжно, если вы сами не кладёте Неопределено в значения; для «множеств» храните Истина.
Нет: методов сортировки нет, порядок обхода не гарантирован. Переложите пары в таблицу значений, вызовите Сортировать() и обходите таблицу.
Поиск по ключу в соответствии не перебирает элементы. У таблицы значений поиск перебирает строки, пока не добавлен индекс через Индексы.Добавить(). Для поиска по одному полю проще соответствие, для отбора по нескольким колонкам удобнее таблица с индексом и НайтиСтроки().
Если обработка загрузки или отчёт тормозят на больших объёмах, частая причина — обращения к базе в цикле, и лечится это пакетным запросом и кэшем в соответствии. Такую доработку можно заказать в рамках услуги разработка отчётов и обработок 1С, а если медленно работает вся база — посмотрите раздел «1С тормозит». Или разместите задачу на площадке: оценка бесплатная, ответ — в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.