Как с помощью запроса 1С найти пропущенные подэлементы справочника, зная их общее количество?

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

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

Представим, что у нас есть следующая структура данных:

  1. Справочник "Элементы": Содержит основные элементы. У каждого элемента есть реквизит, например, КоличествоПодэлементов, числового типа, который указывает, сколько подэлементов должно быть у данного элемента.
  2. Регистр сведений "ПодэлементыЭлементов": Содержит фактические записи о существующих подэлементах. Измерениями регистра являются Элемент (ссылка на справочник "Элементы") и Подэлемент (число, представляющее собой порядковый номер подэлемента).

Например, справочник указывает, что у элемента 33 должно быть 3 подэлемента, а у элемента 51 — 4 подэлемента. Фактические данные из регистра могут выглядеть так:

Элемент | Подэлемент
--------|-----------
33      | 1
33      | 3
51      | 2
51      | 3
51      | 4

В этом случае мы видим, что для элемента 33 пропущен подэлемент с номером 2, а для элемента 51 – подэлемент с номером 1. Именно такие пропуски нам и нужно найти.

Подход к решению проблемы: Общая стратегия

Основная идея, которую мы будем развивать, заключается в формировании "эталонной" или "полной" таблицы, которая будет содержать все ожидаемые комбинации "Элемент - ПорядковыйНомерПодэлемента" для каждого элемента справочника. Затем мы будем сравнивать эту эталонную таблицу с фактическими данными из нашего регистра сведений. Записи, которые есть в эталонной таблице, но отсутствуют в регистре сведений, и будут являться пропущенными подэлементами.

Для реализации этой стратегии нам потребуется выполнить следующие шаги:

  1. Определить структуру справочника и регистра сведений, которую мы будем использовать.
  2. Сформировать последовательность чисел (генератор чисел), которая будет представлять все возможные порядковые номера подэлементов (например, от 1 до максимально возможного количества подэлементов, указанного в любом элементе справочника).
  3. Соединить эту последовательность чисел со справочником "Элементы", чтобы получить ту самую "эталонную" таблицу всех ожидаемых комбинаций "Элемент - ПорядковыйНомерПодэлемента", с учетом реквизита КоличествоПодэлементов.
  4. Выполнить левое соединение (ЛЕВОЕ СОЕДИНЕНИЕ) эталонной таблицы с регистром сведений "ПодэлементыЭлементов".
  5. Отфильтровать результаты соединения, оставив только те строки, для которых соответствующие данные из регистра сведений отсутствуют (то есть, равны NULL).

Давайте рассмотрим каждый шаг подробнее.

Шаг 1: Понимание структуры данных

Для начала, нам нужно четко представлять, с какими объектами мы работаем:

  1. Справочник "Элементы": Назовем его Справочник.МоиЭлементы. У него есть реквизит КоличествоПодэлементов (число). Например, если это элемент с кодом 33, то его реквизит КоличествоПодэлементов равен 3.
  2. Регистр сведений "ПодэлементыЭлементов": Назовем его РегистрСведений.ПодэлементыМоихЭлементов. Он имеет измерения:
    • Элемент (тип СправочникСсылка.МоиЭлементы)
    • Подэлемент (тип Число)

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

Шаг 2: Генерация последовательности чисел в запросе

Одним из самых интересных и важных моментов в этой задаче является создание временной таблицы, которая содержит последовательность чисел от 1 до некоторого максимального значения. Эта таблица будет служить "генератором" порядковых номеров подэлементов. В 1С есть несколько способов это сделать:

Метод 1: Программное формирование ТаблицыЗначений и передача в запрос (менее "чистый запрос")

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

Пример кода на 1С для формирования такой таблицы:


// Определяем максимальное количество подэлементов среди всех элементов
// Это можно получить отдельным запросом
ЗапросМакс = Новый Запрос;
ЗапросМакс.Текст = 
    "ВЫБРАТЬ МАКСИМУМ(МоиЭлементы.КоличествоПодэлементов) КАК МаксКоличество
    |ИЗ
    |    Справочник.МоиЭлементы КАК МоиЭлементы";
РезультатМакс = ЗапросМакс.Выполнить().Выбрать();
МаксимальноеКоличество = 0;
Если РезультатМакс.Следующий() Тогда
    МаксимальноеКоличество = РезультатМакс.МаксКоличество;
КонецЕсли;

// Формируем таблицу значений с последовательностью чисел
ТаблицаЧисел = Новый ТаблицаЗначений;
ТаблицаЧисел.Колонки.Добавить("Номер");
Для Инд = 1 По МаксимальноеКоличество Цикл
    НоваяСтрока = ТаблицаЧисел.Добавить();
    НоваяСтрока.Номер = Инд;
КонецЦикла;

// Передаем ТаблицуЧисел в запрос как параметр
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    Генератор.Номер КАК НомерПодэлемента
    |ИЗ
    |    &ТаблицаЧисел КАК Генератор"; // Использование переданной таблицы

Запрос.УстановитьПараметр("ТаблицаЧисел", ТаблицаЧисел);

Метод 2: Эмуляция генерации последовательности внутри запроса 1С (чистый запрос)

Этот метод более сложен, но позволяет полностью реализовать логику генерации чисел внутри самого запроса 1С, что делает его более универсальным и переносимым. Мы будем использовать технику перекрестного соединения (ПЕРЕКРЕСТНОЕ СОЕДИНЕНИЕ) небольшой временной таблицы самой с собой для создания достаточного количества строк, из которых затем сгенерируем последовательность чисел.

Для примера, мы создадим временную таблицу с цифрами от 0 до 9, а затем перекрестным соединением получим числа от 1 до 100 (или больше, если нужно, используя больше соединений).

Сначала создадим временную таблицу с цифрами:


ВЫБРАТЬ
    Число.Значение КАК Значение
ПОМЕСТИТЬ ВТ_ЧислаОт0До9
ИЗ
    (ВЫБРАТЬ 0 КАК Значение
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9) КАК Число
;

Теперь, используя эту таблицу, мы можем сгенерировать последовательность. Для чисел от 1 до 100 нам понадобится два перекрестных соединения:


////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ЕД.Значение + ДЕС.Значение * 10 + 1 КАК НомерПодэлемента
ПОМЕСТИТЬ ВТ_ГенераторЧисел
ИЗ
    ВТ_ЧислаОт0До9 КАК ЕД // Единицы
        ПЕРЕКРЕСТНОЕ СОЕДИНЕНИЕ ВТ_ЧислаОт0До9 КАК ДЕС // Десятки
ГДЕ
    ЕД.Значение + ДЕС.Значение * 10 + 1 <= &МаксимальноеКоличествоПодэлементовВсего // Важно для оптимизации
;

Здесь &МаксимальноеКоличествоПодэлементовВсего – это параметр, который мы должны передать в запрос, содержащий максимальное значение реквизита КоличествоПодэлементов среди всех элементов справочника. Это позволит ограничить количество генерируемых чисел и избежать избыточной обработки.

Шаг 3: Создание "эталонной" таблицы ожидаемых подэлементов

После того как у нас есть генератор чисел (временная таблица ВТ_ГенераторЧисел), мы можем объединить его со справочником Справочник.МоиЭлементы. Это позволит нам получить все возможные комбинации элемента справочника и его ожидаемого подэлемента, учитывая КоличествоПодэлементов каждого элемента.

Для этого мы выполним внутреннее соединение (ВНУТРЕННЕЕ СОЕДИНЕНИЕ) между справочником и генератором чисел, с условием, что номер генерируемого подэлемента не превышает КоличествоПодэлементов для данного элемента.


////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Элементы.Ссылка КАК Элемент,
    Генератор.НомерПодэлемента КАК ОжидаемыйПодэлемент
ПОМЕСТИТЬ ВТ_ЭталонныеПодэлементы
ИЗ
    Справочник.МоиЭлементы КАК Элементы
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ГенераторЧисел КАК Генератор
        ПО Генератор.НомерПодэлемента <= Элементы.КоличествоПодэлементов
;

Эта временная таблица ВТ_ЭталонныеПодэлементы теперь содержит полный список всех подэлементов, которые *должны* существовать для каждого элемента справочника, в соответствии с его реквизитом КоличествоПодэлементов.

Шаг 4: Идентификация пропущенных подэлементов

Теперь, когда у нас есть эталонный список, мы можем сравнить его с фактическими данными из регистра сведений РегистрСведений.ПодэлементыМоихЭлементов. Для этого мы используем левое соединение (ЛЕВОЕ СОЕДИНЕНИЕ).

Левое соединение позволяет нам получить все записи из левой таблицы (нашей ВТ_ЭталонныеПодэлементы) и соответствующие записи из правой таблицы (регистра сведений). Если для какой-либо записи из левой таблицы не находится соответствующей записи в правой, то поля из правой таблицы будут заполнены значением NULL.

Именно эти строки с NULL-значениями из регистра сведений нас и интересуют, так как они указывают на пропущенные подэлементы.


////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Эталон.Элемент,
    Эталон.ОжидаемыйПодэлемент КАК ПропущенныйПодэлемент
ИЗ
    ВТ_ЭталонныеПодэлементы КАК Эталон
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПодэлементыМоихЭлементов КАК ФактическиеПодэлементы
        ПО Эталон.Элемент = ФактическиеПодэлементы.Элемент
        И Эталон.ОжидаемыйПодэлемент = ФактическиеПодэлементы.Подэлемент
ГДЕ
    ФактическиеПодэлементы.Элемент ЕСТЬ NULL
;

В условии ГДЕ ФактическиеПодэлементы.Элемент ЕСТЬ NULL мы отбираем только те строки, для которых левое соединение не нашло соответствия в регистре сведений. Это означает, что для данной пары "Элемент - ОжидаемыйПодэлемент" нет записи в регистре, а следовательно, этот подэлемент пропущен.

Шаг 5: Полный запрос для поиска пропущенных подэлементов

Теперь мы объединим все части в один цельный запрос. Для примера, предположим, что максимальное количество подэлементов у любого элемента не превышает 100 (что позволит нам использовать наш генератор на основе двух перекрестных соединений цифр).


// Шаг 1: Создаем временную таблицу с цифрами от 0 до 9
ВЫБРАТЬ
    Число.Значение КАК Значение
ПОМЕСТИТЬ ВТ_ЧислаОт0До9
ИЗ
    (ВЫБРАТЬ 0 КАК Значение
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8
     ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9) КАК Число
;

// Шаг 2: Генерируем последовательность чисел от 1 до 100 (или до &МаксимальноеКоличествоПодэлементовВсего)
// Для примера, МаксимальноеКоличествоПодэлементовВсего возьмем 100.
// В реальной задаче это значение должно быть получено запросом МАКСИМУМ() из Справочник.МоиЭлементы
ВЫБРАТЬ
    ЕД.Значение + ДЕС.Значение * 10 + 1 КАК НомерПодэлемента
ПОМЕСТИТЬ ВТ_ГенераторЧисел
ИЗ
    ВТ_ЧислаОт0До9 КАК ЕД
        ПЕРЕКРЕСТНОЕ СОЕДИНЕНИЕ ВТ_ЧислаОт0До9 КАК ДЕС
ГДЕ
    ЕД.Значение + ДЕС.Значение * 10 + 1 <= 100 // Замените 100 на фактическое &МаксимальноеКоличествоПодэлементовВсего
;

// Шаг 3: Создаем "эталонную" таблицу всех ожидаемых подэлементов
ВЫБРАТЬ
    Элементы.Ссылка КАК Элемент,
    Генератор.НомерПодэлемента КАК ОжидаемыйПодэлемент
ПОМЕСТИТЬ ВТ_ЭталонныеПодэлементы
ИЗ
    Справочник.МоиЭлементы КАК Элементы
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ГенераторЧисел КАК Генератор
        ПО Генератор.НомерПодэлемента <= Элементы.КоличествоПодэлементов
;

// Шаг 4: Находим пропущенные подэлементы
ВЫБРАТЬ
    Эталон.Элемент,
    Эталон.ОжидаемыйПодэлемент КАК ПропущенныйПодэлемент
ИЗ
    ВТ_ЭталонныеПодэлементы КАК Эталон
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПодэлементыМоихЭлементов КАК ФактическиеПодэлементы
        ПО Эталон.Элемент = ФактическиеПодэлементы.Элемент
        И Эталон.ОжидаемыйПодэлемент = ФактическиеПодэлементы.Подэлемент
ГДЕ
    ФактическиеПодэлементы.Элемент ЕСТЬ NULL
;

Оптимизация и важные нюансы

  1. Определение &МаксимальноеКоличествоПодэлементовВсего: Чтобы генератор чисел не создавал слишком много ненужных строк, крайне важно сначала определить максимальное значение реквизита КоличествоПодэлементов среди всех элементов справочника. Это можно сделать отдельным простым запросом ВЫБРАТЬ МАКСИМУМ(КоличествоПодэлементов) ИЗ Справочник.МоиЭлементы и передать его как параметр в запрос генератора.
  2. Производительность генератора чисел: Метод перекрестного соединения может быть ресурсоемким, если вам нужно сгенерировать очень большое количество чисел (например, сотни тысяч). Для получения чисел до 100 требуется два перекрестных соединения таблицы из 10 строк (10*10 = 100). Для чисел до 1000 — три соединения (10*10*10 = 1000). Если необходимо генерировать намного больше чисел, стоит рассмотреть более продвинутые методы генерации или программное формирование ТаблицыЗначений.
  3. Индексы: Убедитесь, что измерения регистра сведений (Элемент и Подэлемент) проиндексированы. Это существенно ускорит выполнение левого соединения, особенно на больших объемах данных.
  4. Конфигурация: Представленный подход универсален и работает в любой конфигурации 1С на платформе 8.3 (и более новых), использующей язык запросов 1С.

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

← На главную