Разработка и администрирование систем на платформе 1С:Предприятие 7.7 часто преподносит задачи, которые в более современных версиях (например, в 1С 8.3) решаются буквально в несколько кликов с помощью консоли запросов. Одной из таких классических задач является поиск, выборка и группировка документов по общему текстовому реквизиту, расположенному в табличной части (ТЧ) документа (например, поиск всех документов, где в табличной части фигурирует определенный ИНН, адрес или контрагент).
В этой статье мы подробно и по шагам разберем, как решить эту задачу оптимальными способами. Мы проанализируем как стандартные механизмы платформы 1С 7.7, так и альтернативные профессиональные решения, которые позволят вам добиться максимальной скорости работы даже на очень больших базах данных.
Представим ситуацию: у нас есть документ (например, РасходнаяНакладная). В шапке этого документа содержится стандартный реквизит НомерДок, а в табличной части присутствует реквизит ИНН (тип данных — строка). Нам необходимо получить список всех номеров документов, сгруппированных по уникальным значениям ИНН. Иными словами, система должна вывести нам ИНН, а под ним — список всех документов, в которых данный ИНН встречается в строках.
Выясним, какими путями мы можем пойти для реализации этой задачи.
---
Вопреки распространенному мнению, встроенный механизм запросов 1С 7.7 вполне успешно справляется с табличными частями документов. Главное — правильно настроить последовательность группировок в тексте запроса. Если мы укажем реквизит табличной части первой группировкой, а сам документ — второй, платформа автоматически выполнит необходимую нам группировку и сортировку данных.
Рассмотрим подробнее пример реализации такого запроса:
// Создаем объект Запрос
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(ВыборкаПоИНН)
|Период с ВыбНачПериода по ВыбКонПериода;
|ТекДок = Документ.РасходнаяНакладная.ТекущийДокумент;
|ИНН = Документ.РасходнаяНакладная.ИНН; // Обращаемся к реквизиту табличной части
|Группировка ИНН без упорядочивания; // Первая группировка по ИНН
|Группировка ТекДок; // Вторая группировка по самому документу
|"//}}ЗАПРОС;
// Выполняем запрос и обрабатываем результаты
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
Пока Запрос.Группировка(1) = 1 Цикл
// На этом уровне мы получаем уникальный ИНН
Сообщить("ИНН: " + СокрЛП(Запрос.ИНН));
Пока Запрос.Группировка(2) = 1 Цикл
// На этом уровне получаем документы, содержащие данный ИНН
Сообщить(" Документ: " + Запрос.ТекДок.ПредставлениеВида() + " № " + Запрос.ТекДок.НомерДок);
КонецЦикла;
КонецЦикла;
КонецЕсли;
Преимущества метода:
Недостатки метода:
---
Если выборка документов по реквизиту табличной части требуется пользователям регулярно, а скорость выполнения операции критически важна, мы рекомендуем использовать встроенный штатный механизм индексации — Графы отбора. Этот способ позволяет мгновенно выбирать документы по значению реквизита строки ТЧ.
Разберем этот процесс по шагам:
ОтборПоИНН).ИНН для нужного вида документа (в нашем случае — Документ.РасходнаяНакладная.ИНН).После создания графы отбора мы можем мгновенно выбирать нужные документы программно, используя метод ВыбратьПоЗначению:
Выборка = СоздатьОбъект("Документ.РасходнаяНакладная");
ИскомыйИНН = "7701020304"; // Пример ИНН для поиска
// Выполняем быстрый поиск по индексу графы отбора
Выборка.ВыбратьПоЗначению(НачДата, КонДата, "ОтборПоИНН", ИскомыйИНН);
Пока Выборка.ПолучитьДокумент() = 1 Цикл
Сообщить("Найден документ: " + Выборка.ТекущийДокумент() + " от " + Выборка.ДатаДок);
КонецЦикла;
Преимущества метода:
Недостатки метода:
---
В ходе обсуждения на форуме участники предлагали выгрузить данные всех документов в ТаблицуЗначений. Автор темы справедливо выразил опасение, что при обработке большого массива документов ТЗ получится слишком «тяжелой» и перегрузит оперативную память системы.
Однако этот подход можно существенно оптимизировать! Нам не нужно выгружать строки табличной части целиком. Мы можем использовать метод ВыгрузитьТабличнуюЧасть() с передачей конкретного списка выгружаемых колонок, чтобы экономить память.
Посмотрим на пример такой оптимизированной реализации:
// Создаем общую таблицу значений, где будем аккумулировать результаты
ГлавнаяТЗ = СоздатьОбъект("ТаблицаЗначений");
ГлавнаяТЗ.НоваяКолонка("ИНН", "Строка");
ГлавнаяТЗ.НоваяКолонка("Документ", "Документ");
// Создаем объект для выборки документов
Док = СоздатьОбъект("Документ.РасходнаяНакладная");
Док.ВыбратьДокументы(ВыбНачПериода, ВыбКонПериода);
// Создаем структуру колонок для выгрузки (только нужная нам колонка)
СписокКолонок = "ИНН";
Пока Док.ПолучитьДокумент() = 1 Цикл
// Создаем временную ТЗ для строк текущего документа
ВремТЗ = СоздатьОбъект("ТаблицаЗначений");
// Выгружаем из ТЧ только колонку "ИНН" — это работает очень быстро
Док.ВыгрузитьТабличнуюЧасть(ВремТЗ, СписокКолонок);
// Заполняем колонку "Документ" ссылкой на текущий документ
ВремТЗ.НоваяКолонка("Документ");
ВремТЗ.Заполнить(Док.ТекущийДокумент(),,, "Документ");
// Переносим строки в главную таблицу значений
ВремТЗ.ВыбратьСтроки();
Пока ВремТЗ.ПолучитьСтроку() = 1 Цикл
ГлавнаяТЗ.НоваяСтрока();
ГлавнаяТЗ.ИНН = ВремТЗ.ИНН;
ГлавнаяТЗ.Документ = ВремТЗ.Документ;
КонецЦикла;
КонецЦикла;
// Сортируем общую таблицу по ИНН для группировки данных
ГлавнаяТЗ.Сортировать("ИНН");
// Выводим сгруппированный результат пользователю
ТекущийИНН = "";
ГлавнаяТЗ.ВыбратьСтроки();
Пока ГлавнаяТЗ.ПолучитьСтроку() = 1 Цикл
Если ГлавнаяТЗ.ИНН <> ТекущийИНН Тогда
ТекущийИНН = ГлавнаяТЗ.ИНН;
Сообщить("ИНН: " + ТекущийИНН);
КонецЕсли;
Сообщить(" Документ: " + ГлавнаяТЗ.Документ);
КонецЦикла;
Преимущества метода:
Недостатки метода:
---
Для крупных баз данных (особенно работающих под управлением MS SQL Server или локальных баз DBF с использованием библиотеки 1SQLite) самым профессиональным и технологичным решением является использование прямых SQL-запросов через внешнюю компоненту 1С++ (класс ODBCRecordset).
База данных 1С 7.7 имеет простую и открытую структуру таблиц:
_1SJOURN — хранит общий журнал документов (включая идентификаторы документов IDDOC, их даты и номера).DT* (Document Table) — хранят строки табличных частей документов соответствующих видов.С помощью прямого запроса мы можем соединить таблицу строк документа с журналом документов на уровне СУБД. Это обеспечит колоссальную скорость работы:
// Создаем объект для прямого 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С++ на рабочих станциях или сервере.---
Мы проанализировали четыре различных способа решения поставленной задачи. Какой же из них выбрать в реальной практике?