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