Как найти и сгруппировать документы по общему реквизиту табличной части в 1С 7.7?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Разработка и администрирование систем на платформе 1С:Предприятие 7.7 часто преподносит задачи, которые в более современных версиях (например, в 1С 8.3) решаются буквально в несколько кликов с помощью консоли запросов. Одной из таких классических задач является поиск, выборка и группировка документов по общему текстовому реквизиту, расположенному в табличной части (ТЧ) документа (например, поиск всех документов, где в табличной части фигурирует определенный ИНН, адрес или контрагент).

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

Постановка задачи

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

Выясним, какими путями мы можем пойти для реализации этой задачи.

---

Вариант 1. Встроенный язык запросов 1С 7.7 («Черный запрос»)

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

Рассмотрим подробнее пример реализации такого запроса:


// Создаем объект Запрос
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = 
"//{{ЗАПРОС(ВыборкаПоИНН)
|Период с ВыбНачПериода по ВыбКонПериода;
|ТекДок = Документ.РасходнаяНакладная.ТекущийДокумент;
|ИНН = Документ.РасходнаяНакладная.ИНН; // Обращаемся к реквизиту табличной части
|Группировка ИНН без упорядочивания;    // Первая группировка по ИНН
|Группировка ТекДок;                   // Вторая группировка по самому документу
|"//}}ЗАПРОС;

// Выполняем запрос и обрабатываем результаты
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
    Пока Запрос.Группировка(1) = 1 Цикл
        // На этом уровне мы получаем уникальный ИНН
        Сообщить("ИНН: " + СокрЛП(Запрос.ИНН)); 
        
        Пока Запрос.Группировка(2) = 1 Цикл
            // На этом уровне получаем документы, содержащие данный ИНН
            Сообщить("   Документ: " + Запрос.ТекДок.ПредставлениеВида() + " № " + Запрос.ТекДок.НомерДок);
        КонецЦикла;
    КонецЦикла;
КонецЕсли;

Преимущества метода:

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

Недостатки метода:

  1. На файловых базах данных (DBF) большого объема такой запрос может выполняться медленно, так как платформа вынуждена последовательно перебирать файлы строк документов.

---

Вариант 2. Использование механизма «Графы отбора»

Если выборка документов по реквизиту табличной части требуется пользователям регулярно, а скорость выполнения операции критически важна, мы рекомендуем использовать встроенный штатный механизм индексации — Графы отбора. Этот способ позволяет мгновенно выбирать документы по значению реквизита строки ТЧ.

Разберем этот процесс по шагам:

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

После создания графы отбора мы можем мгновенно выбирать нужные документы программно, используя метод ВыбратьПоЗначению:


Выборка = СоздатьОбъект("Документ.РасходнаяНакладная");
ИскомыйИНН = "7701020304"; // Пример ИНН для поиска

// Выполняем быстрый поиск по индексу графы отбора
Выборка.ВыбратьПоЗначению(НачДата, КонДата, "ОтборПоИНН", ИскомыйИНН); 

Пока Выборка.ПолучитьДокумент() = 1 Цикл
    Сообщить("Найден документ: " + Выборка.ТекущийДокумент() + " от " + Выборка.ДатаДок);
КонецЦикла;

Преимущества метода:

  1. Максимальная скорость поиска среди всех штатных методов платформы. Поиск происходит практически мгновенно благодаря индексам СУБД.
  2. Возможность использовать эту графу отбора в интерактивных журналах документов для пользователей.

Недостатки метода:

  1. Требуется внесение изменений в структуру метаданных конфигурации.
  2. Увеличивается физический размер базы данных за счет создания дополнительных индексных файлов.

---

Вариант 3. Оптимизированный обход через Таблицу Значений (ТЗ)

В ходе обсуждения на форуме участники предлагали выгрузить данные всех документов в ТаблицуЗначений. Автор темы справедливо выразил опасение, что при обработке большого массива документов ТЗ получится слишком «тяжелой» и перегрузит оперативную память системы.

Однако этот подход можно существенно оптимизировать! Нам не нужно выгружать строки табличной части целиком. Мы можем использовать метод ВыгрузитьТабличнуюЧасть() с передачей конкретного списка выгружаемых колонок, чтобы экономить память.

Посмотрим на пример такой оптимизированной реализации:


// Создаем общую таблицу значений, где будем аккумулировать результаты
ГлавнаяТЗ = СоздатьОбъект("ТаблицаЗначений");
ГлавнаяТЗ.НоваяКолонка("ИНН", "Строка");
ГлавнаяТЗ.НоваяКолонка("Документ", "Документ");

// Создаем объект для выборки документов
Док = СоздатьОбъект("Документ.РасходнаяНакладная");
Док.ВыбратьДокументы(ВыбНачПериода, ВыбКонПериода);

// Создаем структуру колонок для выгрузки (только нужная нам колонка)
СписокКолонок = "ИНН";

Пока Док.ПолучитьДокумент() = 1 Цикл
    // Создаем временную ТЗ для строк текущего документа
    ВремТЗ = СоздатьОбъект("ТаблицаЗначений");
    
    // Выгружаем из ТЧ только колонку "ИНН" — это работает очень быстро
    Док.ВыгрузитьТабличнуюЧасть(ВремТЗ, СписокКолонок);
    
    // Заполняем колонку "Документ" ссылкой на текущий документ
    ВремТЗ.НоваяКолонка("Документ");
    ВремТЗ.Заполнить(Док.ТекущийДокумент(),,, "Документ");
    
    // Переносим строки в главную таблицу значений
    ВремТЗ.ВыбратьСтроки();
    Пока ВремТЗ.ПолучитьСтроку() = 1 Цикл
        ГлавнаяТЗ.НоваяСтрока();
        ГлавнаяТЗ.ИНН = ВремТЗ.ИНН;
        ГлавнаяТЗ.Документ = ВремТЗ.Документ;
    КонецЦикла;
КонецЦикла;

// Сортируем общую таблицу по ИНН для группировки данных
ГлавнаяТЗ.Сортировать("ИНН");

// Выводим сгруппированный результат пользователю
ТекущийИНН = "";
ГлавнаяТЗ.ВыбратьСтроки();
Пока ГлавнаяТЗ.ПолучитьСтроку() = 1 Цикл
    Если ГлавнаяТЗ.ИНН <> ТекущийИНН Тогда
        ТекущийИНН = ГлавнаяТЗ.ИНН;
        Сообщить("ИНН: " + ТекущийИНН);
    КонецЕсли;
    Сообщить("   Документ: " + ГлавнаяТЗ.Документ);
КонецЦикла;

Преимущества метода:

  1. Не требует изменения конфигурации.
  2. Память расходуется экономно, так как мы не храним лишние реквизиты строк ТЧ (такие как Номенклатура, Количество, Сумма и т.д.).

Недостатки метода:

  1. При обработке сотен тысяч документов за большой период времени скорость работы алгоритма все равно уступит первому и второму вариантам из-за накладных расходов на создание объектов в цикле.

---

Вариант 4. Прямые SQL-запросы через ВК «1С++» (Для продвинутых разработчиков)

Для крупных баз данных (особенно работающих под управлением MS SQL Server или локальных баз DBF с использованием библиотеки 1SQLite) самым профессиональным и технологичным решением является использование прямых SQL-запросов через внешнюю компоненту 1С++ (класс ODBCRecordset).

База данных 1С 7.7 имеет простую и открытую структуру таблиц:

С помощью прямого запроса мы можем соединить таблицу строк документа с журналом документов на уровне СУБД. Это обеспечит колоссальную скорость работы:


// Создаем объект для прямого SQL-запроса (требуется установленная ВК 1С++)
ЗапросSQL = СоздатьОбъект("ODBCRecordset");

// Формируем текст SQL-запроса с метаименами 1С++
ТекстSQL = "
|SELECT 
|    Journ.DOCNO as [НомерДок],
|    DocTable.SPxxxx as [ИНН] -- SPxxxx - физическое имя реквизита ИНН в базе данных
|FROM 
|    $ДокументСтроки.РасходнаяНакладная AS DocTable
|INNER JOIN 
|    _1SJOURN AS Journ ON Journ.IDDOC = DocTable.IDDOC
|ORDER BY 
|    DocTable.SPxxxx
|";

// Выполняем SQL-запрос и выгружаем результат в Таблицу Значений
ТЗРезультат = СоздатьОбъект("ТаблицаЗначений");
ТЗРезультат = ЗапросSQL.ВыполнитьИнструкцию(ТекстSQL);

// Далее обрабатываем полученную ТЗ по аналогии с Вариантом 3

Преимущества метода:

  1. Абсолютный лидер по скорости. Операция на миллионах строк в базе данных займет считанные миллисекунды.
  2. Не блокирует базу данных и не нагружает сервер приложений 1С.
  3. Не требует изменения метаданных в конфигураторе.

Недостатки метода:

  1. Требует наличия и регистрации внешней компоненты 1С++ на рабочих станциях или сервере.
  2. Требует от программиста базовых знаний синтаксиса SQL и понимания внутренней структуры хранения таблиц 1С 7.7.

---

Резюме и рекомендации

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

← На главную