Часто возникает задача проверить корректность заполнения документов. Рассмотрим ситуацию, когда в документе "Договор" есть несколько реквизитов в шапке для указания обучающихся (например, Обучающийся1, Обучающийся2, ... Обучающийся15), а также табличная часть "Список", в которой эти же обучающиеся должны быть перечислены. Наша цель — найти все договоры, где есть несоответствия между этими двумя списками.
Проанализируем два подхода к решению этой задачи: простой, но менее производительный, основанный на циклах, и оптимальный, использующий язык запросов 1С.
Этот метод интуитивно понятен и хорошо подходит, если вы только начинаете работать с 1С или если объем данных невелик. Идея заключается в том, чтобы для каждого документа получить два списка обучающихся (один из шапки, другой из табличной части) и сравнить их между собой.
Шаг 1. Перебор документов и сбор данных из шапки
Сначала организуем перебор всех документов "Договор". Внутри цикла для каждого документа мы будем собирать ссылки на обучающихся из реквизитов шапки. Вместо того чтобы писать 15 одинаковых блоков кода для каждого реквизита ОбучающийсяN, воспользуемся удобной возможностью 1С — доступом к реквизиту по его имени в виде строки. Это делает код компактным и легко масштабируемым.
// Создаем выборку всех документов нужного вида
Выборка = Документы.Договор.Выбрать();
// Начинаем перебор документов
Пока Выборка.Следующий() Цикл
// Для каждого документа создаем новый список значений
СписокСлушателейИзШапки = новый СписокЗначений;
// В цикле от 1 до 15 получаем доступ к реквизитам Обучающийся1, Обучающийся2 и т.д.
Для Н = 1 по 15 Цикл
// Формируем имя реквизита динамически
ИмяРеквизита = "Обучающийся" + Н;
// Получаем значение реквизита
Слушатель = Выборка[ИмяРеквизита];
// Проверяем, что реквизит заполнен
Если Не Слушатель.Пустая() тогда
// Добавляем непустую ссылку в наш список
СписокСлушателейИзШапки.Добавить(Слушатель);
КонецЕсли;
КонецЦикла;
// ... дальнейшая обработка ...
КонецЦикла;
Шаг 2. Сбор данных из табличной части
Теперь, имея список слушателей из шапки, нам нужно получить аналогичный список из табличной части "Список" текущего документа. Это делается простым перебором строк табличной части.
СписокСлушателейИзТЧ = новый СписокЗначений;
Для каждого СтрокаТЧ из Выборка.Список Цикл
// Добавляем в список ссылку на слушателя из строки
СписокСлушателейИзТЧ.Добавить(СтрокаТЧ.КарточкаСлушателя);
КонецЦикла;
Шаг 3. Сравнение двух списков и формирование результата
У нас есть два объекта СписокЗначений. Теперь нужно найти элементы, которые есть в одном списке, но отсутствуют в другом. Для этого используем метод НайтиПоЗначению(). Если элемент не найден, метод возвращает Неопределено.
Сравнение нужно проводить в обе стороны:
Итоговый код функции:
Объединим все шаги в одну функцию, которая будет возвращать массив структур с информацией о найденных расхождениях.
Функция НайтиНесоответствияДоговоров()
МассивДоговоров = новый Массив();
Выборка = Документы.Договор.Выбрать();
Пока Выборка.Следующий() Цикл
// Шаг 1: Собираем слушателей из реквизитов шапки
СписокСлушателейИзШапки = новый СписокЗначений;
Для Н = 1 по 15 Цикл
Слушатель = Выборка["Обучающийся" + Н];
Если Не Слушатель.Пустая() тогда
СписокСлушателейИзШапки.Добавить(Слушатель);
КонецЕсли;
КонецЦикла;
// Пропускаем документы, где в шапке нет слушателей
Если СписокСлушателейИзШапки.Количество() = 0 тогда
Продолжить;
КонецЕсли;
// Шаг 2: Собираем слушателей из табличной части
СписокСлушателейИзТЧ = новый СписокЗначений;
Для каждого СтрТЧ из Выборка.Список Цикл
СписокСлушателейИзТЧ.Добавить(СтрТЧ.КарточкаСлушателя);
КонецЦикла;
// Шаг 3.1: Ищем тех, кто есть в ТЧ, но нет в шапке
Для каждого ЭлементТЧ из СписокСлушателейИзТЧ цикл
Если СписокСлушателейИзШапки.НайтиПоЗначению(ЭлементТЧ.Значение) = неопределено тогда
СтруктураДоговора = новый Структура;
СтруктураДоговора.Вставить("ДоговорНомер", Выборка.Номер);
СтруктураДоговора.Вставить("ДоговорДата", Выборка.Дата);
СтруктураДоговора.Вставить("СлушательВСписке", "отсутствует");
СтруктураДоговора.Вставить("СлушательВТаблице", ЭлементТЧ.Значение);
МассивДоговоров.Добавить(СтруктураДоговора);
КонецЕсли;
КонецЦикла;
// Шаг 3.2: Ищем тех, кто есть в шапке, но нет в ТЧ
Для каждого ЭлементШапки из СписокСлушателейИзШапки цикл
Если СписокСлушателейИзТЧ.НайтиПоЗначению(ЭлементШапки.Значение) = неопределено тогда
СтруктураДоговора = новый Структура;
СтруктураДоговора.Вставить("ДоговорНомер", Выборка.Номер);
СтруктураДоговора.Вставить("ДоговорДата", Выборка.Дата);
СтруктураДоговора.Вставить("СлушательВСписке", ЭлементШапки.Значение);
СтруктураДоговора.Вставить("СлушательВТаблице", "отсутствует");
МассивДоговоров.Добавить(СтруктураДоговора);
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат МассивДоговоров;
КонецФункции
Важно: Этот метод прост в реализации, но может работать медленно на больших базах данных, так как вся обработка происходит на стороне сервера 1С, а не на стороне СУБД.
Язык запросов 1С — мощный инструмент для извлечения и обработки данных. Решение задачи с помощью запроса будет работать значительно быстрее, так как все вычисления по отбору и сравнению будут выполнены на сервере баз данных.
Логика запроса будет следующей:
Обучающийся1 ... Обучающийся15 в одну временную таблицу с помощью оператора ОБЪЕДИНИТЬ ВСЕ. У таблицы будет две колонки: Ссылка на документ и Обучающийся.ЛЕВОЕ СОЕДИНЕНИЕ, чтобы найти записи из первой таблицы (шапка), для которых нет соответствия во второй (табличная часть).Разберем текст запроса по шагам:
Шаг 1. Создаем временную таблицу ВТ_СлушателиШапки
Мы последовательно выбираем непустые значения из каждого реквизита ОбучающийсяN и объединяем их в одну таблицу.
ВЫБРАТЬ
Договор.Ссылка,
Договор.Обучающийся1 КАК Обучающийся
ПОМЕСТИТЬ ВТ_СлушателиШапки
ИЗ
Документ.Договор КАК Договор
ГДЕ
Договор.Обучающийся1 <> &ПустаяСсылкаНаСлушателя
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Договор.Ссылка,
Договор.Обучающийся2
ИЗ
Документ.Договор КАК Договор
ГДЕ
Договор.Обучающийся2 <> &ПустаяСсылкаНаСлушателя
// ... и так далее для всех 15 реквизитов ...
Шаг 2. Выполняем сравнение
Теперь, когда у нас есть "плоский" список слушателей из шапки, соединяем его с реальной табличной частью документа. Условие ГДЕ СписокТЧ.Ссылка ЕСТЬ NULL оставит нам только те записи из шапки, для которых не нашлось пары в табличной части.
ВЫБРАТЬ
СлушателиШапки.Ссылка КАК ДокументДоговор,
СлушателиШапки.Обучающийся КАК СлушательОтсутствующийВТЧ
ИЗ
ВТ_СлушателиШапки КАК СлушателиШапки
ЛЕВОЕ СОЕДИНЕНИЕ Документ.Договор.Список КАК СписокТЧ
ПО СлушателиШапки.Ссылка = СписокТЧ.Ссылка
И СлушателиШапки.Обучающийся = СписокТЧ.КарточкаСлушателя
ГДЕ
СписокТЧ.Ссылка ЕСТЬ NULL
Этот запрос найдет слушателей, которые есть в шапке, но отсутствуют в ТЧ. Чтобы найти обратную ситуацию (слушатели есть в ТЧ, но нет в шапке), нужно построить аналогичный запрос, поменяв таблицы местами в ЛЕВОМ СОЕДИНЕНИИ.
Важное архитектурное замечание: Сама структура документа с 15 отдельными реквизитами для обучающихся является неоптимальной. В будущем такую структуру рекомендуется переработать, перенеся всех обучающихся в единую табличную часть. Это упростит все операции с данными, устранит искусственное ограничение на количество слушателей и сделает подобные проверки тривиальными.