Всем привет!
Сегодня будет необычный кейс - будут представлены решения трех задач.
Задача 1
Задача 1 - поиск документов по номенклатуре (в другой формулировке - поиск документов по контрагенту, позже решил подобную задачу поиск документов по сотруднику).
Разные способы решения - можно пропустить
- Есть стандартный и очевидный способ - используя запрос, описать все заранее известные документы с отбором по номенклатуре.
- Второй - теперь также уже стандартный способ - генерировать запрос динамически в цикле, при этом нужно обходить все метаданные документов, находить документы с реквизитом с типом значения СправочникСсылка Номенклатура, сравнивать значение реквизита с искомой номенклатурой. Процедура поиска становится длительной с технической точки зрения.
Встает задача - как ускорить поиск. Решил оптимизировать решение так - сначала запускаю в конфигураторе по номенклатуре (контрагенту или сотруднику) "Поиск ссылок на объект" - копирую полученный результат в табличный документ обработки - далее запрос формирую динамически во время обхода строк табличного документа (рис. 1, 2, 3 - в ленте). Подходит для ОФ и УФ.
Рисунки 1, 2, 3 - наглядный пример ускорения алгоритма поиска
Небольшой код, который позволяет динамически формировать запрос по строкам табличного документа
Текст = "";
Для К = 1 По Макет.ВысотаТаблицы Цикл
Стр = СокрЛП(Макет.Область(К,1,К,1).Текст);
МасПодстрок = РазложитьСтрокуВМассивПодстрок(Стр, ".");
Если МасПодстрок.Получить(0) <> "Документ" Тогда
Продолжить;
КонецЕсли;
Док = МасПодстрок.Получить(1);
Если МасПодстрок.Получить(2) = "Реквизит" Тогда
РеквизитТЧ = МасПодстрок.Получить(3);
Попытка
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Первые 1
| """ + Док + """ КАК ТипДокумента,
| Ссылка КАК Ссылка,
| ""реквизит: " + РеквизитТЧ + """ КАК ТабЧасть
|ИЗ
| Документ." + Док + "
|ГДЕ
| " + РеквизитТЧ + " = &Сотрудник
|И
| Ссылка.Дата МЕЖДУ &НачПериода И &КонПериода";
Запрос.УстановитьПараметр("Сотрудник", Объект.Сотрудник);
Запрос.УстановитьПараметр("НачПериода", Объект.НачПериода);
Запрос.УстановитьПараметр("КонПериода", Объект.КонПериода);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Выборка = Неопределено;
Исключение
Продолжить;
КонецПопытки;
Текст = Текст + "ВЫБРАТЬ
| """ + Док + """ КАК ТипДокумента,
| Ссылка КАК Ссылка,
| ""реквизит: " + РеквизитТЧ + """ КАК ТабЧасть
|ИЗ
| Документ." + Док + "
|ГДЕ
| " + РеквизитТЧ + " = &Сотрудник
|И
| Ссылка.Дата МЕЖДУ &НачПериода И &КонПериода
|ОБЪЕДИНИТЬ ВСЕ
|";
КонецЕсли;
Если МасПодстрок.Получить(2) = "ТабличнаяЧасть" Тогда
ТабЧасть = МасПодстрок.Получить(3);
РеквизитТЧ = МасПодстрок.Получить(5);
ИмяТаблицы = Док + ТабЧасть;
Попытка
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Первые 1
| """ + Док + """ КАК ТипДокумента,
| " + ИмяТаблицы + ".Ссылка КАК Ссылка,
| ""табл.часть: " + ТабЧасть + """ КАК ТабЧасть
|ИЗ
| Документ." + Док + "." + ТабЧасть + " КАК " + ИмяТаблицы + "
|ГДЕ
| " + ИмяТаблицы + "." + РеквизитТЧ + " = &Сотрудник
|И
| " + ИмяТаблицы + ".Ссылка.Дата МЕЖДУ &НачПериода И &КонПериода";
Запрос.УстановитьПараметр("Сотрудник", Объект.Сотрудник);
Запрос.УстановитьПараметр("НачПериода", Объект.НачПериода);
Запрос.УстановитьПараметр("КонПериода", Объект.КонПериода);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Выборка = Неопределено;
Исключение
Продолжить;
КонецПопытки;
Текст = Текст + "ВЫБРАТЬ
| """ + Док + """ КАК ТипДокумента,
| " + ИмяТаблицы + ".Ссылка КАК Ссылка,
| ""табл.часть: " + ТабЧасть + """ КАК ТабЧасть
|ИЗ
| Документ." + Док + "." + ТабЧасть + " КАК " + ИмяТаблицы + "
|ГДЕ
| " + ИмяТаблицы + "." + РеквизитТЧ + " = &Сотрудник
|И
| " + ИмяТаблицы + ".Ссылка.Дата МЕЖДУ &НачПериода И &КонПериода
|ОБЪЕДИНИТЬ ВСЕ
|";
КонецЕсли;
КонецЦикла;
Текст = Лев(Текст, СтрДлина(Текст) - 16);
Текст = Текст + "
|УПОРЯДОЧИТЬ ПО
| ТипДокумента,
| Ссылка
|АВТОУПОРЯДОЧИВАНИЕ";
Запрос = Новый Запрос;
Запрос.Текст = Текст;
Запрос.УстановитьПараметр("Сотрудник", Объект.Сотрудник);
Запрос.УстановитьПараметр("НачПериода", Объект.НачПериода);
Запрос.УстановитьПараметр("КонПериода", Объект.КонПериода);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Объект.СписокДокументов.Очистить();
Пока Выборка.Следующий() Цикл
Если ЗначениеЗаполнено(Выборка.Ссылка) Тогда
Стр = Объект.СписокДокументов.Добавить();
Стр.Документ = Выборка.Ссылка;
Стр.ТипДокумента = Выборка.ТипДокумента;
Стр.ТабЧасть = Выборка.ТабЧасть;
КонецЕсли;
КонецЦикла;
Задача 2
Задача 2 - имеется динамический список номенклатуры - только для ОФ. Нужно на форме списка реализовать два отбора: отбор по остаткам - то есть показывать "только в наличии", и отбор по брендам (или по производителям, у кого как).
Небольшой анализ, рисунок и очевидный способ решения - можно пропустить
Было бы проще, когда Бренд являлся реквизитом справочника номенклатуры. Но он является одним из свойств характеристики номенклатуры. Да и в целом, свойств очень много, и уже реализован отбор по свойствам: отбор задействует стандартное поле "Ссылка" справочника номенклатура и программно задается как "Ссылка В Списке (Ссылка1, Ссылка2, и т.д.)" - рис. 4.
Дополнительный поиск остатков номенклатуры для динамического списка в конечном счете также начинает использовать стандартное поле "Ссылка". И тут начинается эквилибристика: при смене подгруппы номенклатуры или при смене свойства в одно и тоже поле "ссылка" нужно задавать новый список ссылок с учетом имеющегося отбора по свойствам или с учетом остатков соответственно.
Есть первый способ - очевидный - задать три переменные - три массива или три списка значений, которые будут хранить соответственно получаемые списки "по остаткам" (первый список), список с отбором свойств (второй список), и результирующий список (третий список) - пересечение первых двух списков. Я решил, что создание массивов - это разовое решение и в целом тупиковый путь, потому что не масштабируется. При изменении требований клиентов придется переписывать алгоритм, возможно создавать новые массивы (новые списки).
Другой способ - чтобы не запутаться в уже имеющемся коде и с перспективой развития функционала - я добавил в справочник Номенклатура еще один реквизит "Ссылка2", записываю его равным значению Ссылка. Когда накладываю дополнительный отбор по остаткам номенклатуры, то задействую отбор по полю Ссылка2, не сбивая отбор по полю Ссылка.
Небольшой код для записи поля Ссылка2
Процедура ПередЗаписью(Отказ)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
...
//доработка+
Если НЕ Отказ И НЕ ЭтоГруппа Тогда
Если ЭтоНовый() Тогда
СсылкаНового = ЭтотОбъект.ПолучитьСсылкуНового();
Если НЕ ЗначениеЗаполнено(СсылкаНового) Тогда
СсылкаНового = Справочники.Номенклатура.ПолучитьСсылку();
КонецЕсли;
Если НЕ Отказ И НЕ ЗначениеЗаполнено(ЭтотОбъект.ПолучитьСсылкуНового()) Тогда
ЭтотОбъект.УстановитьСсылкуНового(СсылкаНового);
КонецЕсли;
Ссылка2 = ЭтотОбъект.ПолучитьСсылкуНового();
Иначе
Ссылка2 = Ссылка;
КонецЕсли;
КонецЕсли;
КонецПроцедуры // ПередЗаписью()
Задача 3
Имеется Файловая база 38 Гб - доработанная УТ 10.3 (рис. 5, 6).
Когда она перестанет открываться - неизвестно. Еженедельный анализ размеров таблиц показывает, что таблица "Товары" документа "РеализацияТоваровУслуг" растет быстрее всех - это первая строка сверху на рисунке 6. Задача 3 - спасти базу.
Разные очевидные способы решения, которые не подходят - можно пропустить
1) Первый способ - очевидный - перейти на клиент-серверный режим работы.
Но есть проблемы: нет сисадмина, который развернет серверную СУБД и будет ее обслуживать - ну ладно, на первый раз попросил знакомого развернуть пустую СУБД.
Вторая проблема - развернув копию рабочей базы в ПостгреSQL, начались проблемы вызовов клиентских процедур на стороне сервера - скажем так, встроенные доработки перестают работать при переходе на новый серверный режим.
Один из самых известных и распространенных примеров - встроенная подсистема версионирования перестает запускать функцию Хеш() на сервере - поскольку ява-компонента "видна только на клиенте".
Этот способ не подходит только тем, что нужно предварительно отладить и протестировать все имеющиеся нетиповые доработки - а таких в базе очень много. Время на тестирование несопоставимо больше времени жизни базы.
2) Второй способ - сделать типовую свертку. Проблема в том, что как таковой "типовой" свертки не бывает. Каждая работа по свертке индивидуальна - в этой базе нужно учесть НДС, учет по сериям, учет марок, учесть размер базы - такую большую базу я еще не сворачивал - сколько времени это займет и не обвалится ли процесс на ошибке через N часов?
Для меня понятно, что свертка нужна фрагментарная и кусочная - не только в плане периодов, но и в плане документов и регистров. Чего я также еще ни разу не делал.
Третий способ - как временный способ - и как часть процесса будущей свертки - очистить таблицу Товары в документах РеализацияТоваровУслуг (за определенный период) с запретом на перепроведение этих документов.
Реализовать этот способ возможно благодаря тому, что режим удаления движений задан не "автоматический", а как "не удалять автоматически" - рис.7 - то есть перед проведением документа сначала программно вызываются процедуры удаления движений. А значит можно поставить "заглушку".
Вот это можно использовать: для 2017 года можно написать обход этих процедур (рис. 8, 9).
Далее я проверил, чтобы в процедурах ПередЗаписью() и ПриЗаписи() стояла проверка на режим обмена данных загрузка - это позволит при удалении таблицы Товары записывать документ Реализация товаров и услуг в режиме ОбменДанными.Загрузка = Истина (рис.10, 11) - чтобы не выполнялись никакие другие проверки.
Перепроведение документов в этом периоде 2017 года также запрещено стандартным механизмом "Датой запрета редактирования".
Еженедельно таблица товаров реализаций росла на 36 Мб, при таком темпе таблица достигла бы лимита в 4Гб через 2-2,5 месяца. Таким способом получилось удалить товары за период 2017-2019 включительно, освободить 1Гб этой таблицы, отсрочить переполнение таблицы почти на 7 мес.
Таблица по регистру накопления НДС-начисления (итоговая) растет с темпом 1Мб в неделю, поэтому не критично для ситуации в целом. Таким способом можно начать первый этап свертки больших файловых баз, работа в которых происходит в режиме 24х7.
В дальнейшем я развил мысль удаления табличных частей у документов - и реализовал свертку подокументно. О чем описал в статье Свертка базы УТ 10.3. Новая концепция.
Рис. 7.
Рис. 8.
Рис. 9.
Рис. 10.
Рис. 11.
Небольшой код, который позволит удалить Товары из Реализации
Выборка = Документы.РеализацияТоваровУслуг.Выбрать(,Дата(2018,1,1));
Пока Выборка.Следующий() Цикл
Док = Выборка.Ссылка.ПолучитьОбъект();
Док.ОбменДанными.Загрузка = Истина;
Док.Товары.Очистить();
Док.Записать(); //по умолчанию режим записи = Запись
КонецЦикла;
На этом все. Всем добра!
С пользой для клиентов, Рустем