В процессе разработки часто возникает задача анализа актуальных данных по всем типам документов в системе. Например, нам может потребоваться найти самый свежий проведённый документ каждого вида, чтобы проверить дату последнего занесения данных или проанализировать активность пользователей. На первый взгляд задача кажется простой, но при её реализации программным кодом возникает множество нюансов: от синтаксических ошибок в сложных запросах до проблем с производительностью базы данных. В этой статье мы подробно разберем, как правильно составить такой запрос, как автоматизировать процесс через метаданные и каких ошибок следует избегать.
Поскольку видов документов в конфигурации может быть несколько сотен, писать запрос вручную для каждого — неэффективно. Правильным подходом будет программный обход коллекции метаданных и автоматическая сборка текста запроса. Рассмотрим этот процесс по шагам.
Во-первых, нам необходимо перебрать все документы, представленные в конфигурации. При этом важно фильтровать те объекты, которые в принципе поддерживают проведение. Если мы попытаемся проверить условие Проведен = ИСТИНА для документа, у которого свойство Проведение установлено в значение Запретить, система выдаст ошибку.
Разберем пример кода, который формирует текст запроса:
ТекстЗапроса = "";
ПервыйДокумент = Истина;
Для Каждого МетаДокумент Из Метаданные.Документы Цикл
// Проверяем, разрешено ли проведение для данного вида документа
Если МетаДокумент.Проведение <> Метаданные.СвойстваОбъектов.Проведение.Разрешить Тогда
Продолжить;
КонецЕсли;
Если Не ПервыйДокумент Тогда
ТекстЗапроса = ТекстЗапроса + "
|ОБЪЕДИНИТЬ ВСЕ
|";
КонецЕсли;
ТекстЗапроса = ТекстЗапроса + "
|ВЫБРАТЬ ПЕРВЫЕ 1
| Док.Ссылка КАК Ссылка,
| Док.Дата КАК Дата,
| Док.МоментВремени КАК МоментВремени
|ИЗ
| Документ." + МетаДокумент.Имя + " КАК Док
|ГДЕ
| Док.Проведен = ИСТИНА
|УПОРЯДОЧИТЬ ПО
| Дата УБЫВ,
| МоментВремени УБЫВ";
ПервыйДокумент = Ложь;
КонецЦикла;
Запрос = Новый Запрос(ТекстЗапроса);
Результат = Запрос.Выполнить().Выгрузить();
В данном примере мы используем конструкцию ВЫБРАТЬ ПЕРВЫЕ 1 для каждой таблицы. Это позволяет СУБД максимально быстро найти нужную запись, используя индекс по дате.
При использовании оператора ОБЪЕДИНИТЬ ВСЕ часто возникают ошибки, связанные с сортировкой. Важно помнить правило: если вы используете УПОРЯДОЧИТЬ ПО внутри объединяемых частей запроса, вы обязаны использовать ВЫБРАТЬ ПЕРВЫЕ. Кроме того, как было отмечено в ходе обсуждения, СУБД требует, чтобы поля, по которым идет сортировка, присутствовали в списке выбора каждой части объединения.
Проанализируем типичную ошибку: если мы выберем только Ссылка, но попробуем упорядочить по Дата, запрос может не выполниться в некоторых версиях платформы или СУБД при объединении таблиц. Поэтому всегда включайте Дата и МоментВремени в список полей, если планируете по ним сортировать.
Если нам нужно получить только ссылки на документы без лишних полей в итоговой выборке, можно обернуть каждую часть во вложенный запрос. Это делает структуру более громоздкой, но иногда помогает обойти ограничения конструктора или специфическое поведение SQL-планировщика.
Рассмотрим пример структуры такого подзапроса:
ВЫБРАТЬ
ПОДЗАПРОС.Ссылка
ИЗ (
ВЫБРАТЬ ПЕРВЫЕ 1
Т.Ссылка КАК Ссылка,
Т.Дата КАК Дата
ИЗ
Документ.РеализацияТоваровУслуг КАК Т
ГДЕ
Т.Проведен = ИСТИНА
УПОРЯДОЧИТЬ ПО
Дата УБЫВ
) КАК ПОДЗАПРОС
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ПОДЗАПРОС.Ссылка
ИЗ (
ВЫБРАТЬ ПЕРВЫЕ 1
Т.Ссылка КАК Ссылка,
Т.Дата КАК Дата
ИЗ
Документ.ПоступлениеТоваровУслуг КАК Т
ГДЕ
Т.Проведен = ИСТИНА
УПОРЯДОЧИТЬ ПО
Дата УБЫВ
) КАК ПОДЗАПРОС
Если в вашей конфигурации предусмотрен общий журнал документов (например, ЖурналДокументов.Полный), то задачу можно решить значительно элегантнее и быстрее. Журнал — это одна физическая таблица в базе данных, которая уже содержит ссылки на все входящие в него виды документов, их даты и состояние проведения.
Вместо того чтобы опрашивать 200 отдельных таблиц, мы можем обратиться к одной. Выясним, как это сделать:
МАКСИМУМ(МоментВремени).Однако стоит учитывать, что в журнале нет прямого способа выбрать "по одному первому" для каждого типа простым GROUP BY, если нам нужна именно Ссылка. В таком случае всё равно придется использовать соединение или временные таблицы, но скорость работы с одной таблицей журнала будет выше, чем с сотней разрозненных таблиц документов.
При работе с большими базами данных следует учитывать следующие моменты:
ОБЪЕДИНИТЬ ВСЕ может привести к ошибке "Stack Overflow" на стороне SQL-сервера или крайне долгой компиляции плана запроса. В этом случае рекомендуем выполнять запрос "пачками" по 30-50 документов.Дата в 1С индексируется по умолчанию. Условие ГДЕ Проведен = ИСТИНА УПОРЯДОЧИТЬ ПО Дата УБЫВ является "индекс-френдли", то есть СУБД просто возьмет первую запись из конца индекса, не сканируя всю таблицу. Это делает запрос очень быстрым даже для таблиц с миллионами записей.МоментВремени в паре с Дата при сортировке. Это гарантирует, что если в одну секунду было введено несколько документов, вы получите действительно последний из них (по позиции в реестре), а не случайный.Подводя итог, можно сказать, что наиболее универсальным способом является динамическое построение запроса через Метаданные.Документы с обязательной проверкой свойства Проведение и соблюдением правил сортировки в объединениях.