При разработке отчетов и обработок в среде «1С:Предприятие 8» программисты часто сталкиваются с задачей: получить список документов за определенный период и одновременно вывести содержимое их табличной части. При этом критически важно вывести даже те документы, у которых эта табличная часть совершенно пуста.
Начинающие разработчики часто совершают логическую ошибку в конструкции запроса, из-за которой документы с пустыми табличными частями просто исчезают из итоговой выборки. В этой статье мы подробно разберем физику этого процесса, выясним причину ошибки и рассмотрим три эффективных способа решения задачи: от классического левого соединения до профессиональной оптимизации с использованием временных таблиц и механизмов выборки вложенных таблиц.
Проанализируем типичную ситуацию. Нам нужно выбрать документы РеализацияТоваровУслуг за определенный период и получить данные их табличной части НомераИзделий. Если мы попытаемся связать их напрямую, используя левое или полное соединение, но при этом установим условия фильтрации в секции ГДЕ на поля табличной части, то документы с пустыми строками исчезнут.
Рассмотрим, почему это происходит. Когда мы выполняем левое соединение (ЛЕВОЕ СОЕДИНЕНИЕ) таблицы документов (слева) с таблицей их табличной части (справа), для документов, у которых табличная часть пуста, СУБД возвращает значения NULL во всех полях правой таблицы.
Если в секции ГДЕ мы пишем условие фильтрации по дате или пометке удаления, обращаясь к правой таблице:
ГДЕ
РеализацияТоваровУслугНомераИзделий.Ссылка.Дата МЕЖДУ &ДатаНач И &ДатаКон
И РеализацияТоваровУслугНомераИзделий.Ссылка.ПометкаУдаления = ЛОЖЬ
СУБД подставляет значение NULL в левую часть этого сравнения. Любое сравнение значения NULL с конкретной датой или булевым значением в SQL всегда возвращает результат «Ложь» (или «Неизвестно»). В итоге строки с пустыми табличными частями полностью отсекаются из результирующей выборки. Наш запрос фактически превращается во внутреннее соединение (ВНУТРЕННЕЕ СОЕДИНЕНИЕ), уничтожая весь смысл левого соединения.
Самое простое и быстрое решение — перенести условия фильтрации на левую (основную) таблицу документов и правильно составить связи. Выясним, как это реализовать на практике.
Разберем по шагам правильный текст запроса:
ВЫБРАТЬ
Реализация.Ссылка КАК ДокРеализации,
ЕСТЬNULL(НомераИзделий.Номенклатура, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Номенклатура,
ЕСТЬNULL(НомераИзделий.НомерИзделия, "") КАК НомерИзделия
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ReplicaТоваровУслуг.НомераИзделий КАК НомераИзделий
ПО Реализация.Ссылка = НомераИзделий.Ссылка
ГДЕ
Реализация.Дата МЕЖДУ &ДатаНач И &ДатаКон
И НЕ Реализация.ПометкаУдаления
Проанализируем ключевые моменты этого запроса:
Документ.РеализацияТоваровУслуг. СУБД гарантированно вернет все документы, подходящие под условия секции ГДЕ.Реализация.Дата и Реализация.ПометкаУдаления). Так как в этих полях гарантированно нет значений NULL, фильтрация отработает корректно.ЕСТЬNULL. Если табличная часть пуста, вместо системного значения NULL в результат запроса вернется пустая ссылка справочника номенклатуры или пустая строка для номера изделия. Это убережет ваш дальнейший код от ошибок типов данных.Рассмотренный выше Способ 1 отлично работает на небольших и средних базах данных. Однако, если в вашей информационной базе миллионы документов «Реализация товаров и услуг», а период выборки составляет всего один день, прямой запрос может работать медленно.
СУБД при выполнении прямого левого соединения физической таблицы документов с огромной физической таблицей табличной части может построить неоптимальный план выполнения (например, сканирование всей таблицы ТЧ вместо точечного поиска). Чтобы избежать проблем с производительностью, применим профессиональный стандарт разработки 1С — использование временных таблиц.
Рассмотрим этот эффективный алгоритм:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Реализация.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_Документы
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
|ГДЕ
| Реализация.Дата МЕЖДУ &ДатаНач И &ДатаКон
| И НЕ Реализация.ПометкаУдаления
|
|ИНДЕКСИРОВАТЬ ПО
| Ссылка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Документы.Ссылка КАК ДокРеализации,
| ЕСТЬNULL(НомераИзделий.Номенклатура, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Номенклатура,
| ЕСТЬNULL(НомераИзделий.НомерИзделия, """") КАК НомерИзделия
|ИЗ
| ВТ_Документы КАК ВТ_Документы
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.НомераИзделий КАК НомераИзделий
| ПО ВТ_Документы.Ссылка = НомераИзделий.Ссылка";
Запрос.УстановитьПараметр("ДатаНач", НачалоДня(ДатаНачала));
Запрос.УстановитьПараметр("ДатаКон", КонецДня(ДатаОкончания));
РезультатЗапроса = Запрос.Выполнить();
Выясним преимущества такого подхода:
ИНДЕКСИРОВАТЬ ПО Ссылка. Это создает временный индекс СУБД, который в разы ускорит последующее соединение.Существует еще один изящный способ, который позволяет полностью отказаться от явного прописывания левых соединений в тексте запроса. Платформа «1С:Предприятие 8» умеет самостоятельно выбирать табличные части документов в виде вложенных таблиц.
Посмотрим на пример реализации такого запроса:
ВЫБРАТЬ
Реализация.Ссылка КАК ДокРеализации,
Реализация.НомераИзделий.(
Номенклатура КАК Номенклатура,
НомерИзделия КАК НомерИзделия
) КАК ТЧ_НомераИзделий
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
Реализация.Date МЕЖДУ &ДатаНач И &ДатаКон
И НЕ Реализация.ПометкаУдаления
Проанализируем особенности работы этого механизма:
ТЧ_НомераИзделий в результате запроса будет содержать объект типа РезультатЗапроса (или ТаблицаЗначений при выгрузке). Если табличная часть у документа была пуста, это поле просто вернет пустую таблицу из 0 строк, но сам документ из выборки не пропадет.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).Разбирая исходный некорректный запрос автора, нельзя обойти стороной еще одну серьезную ошибку оптимизации. В тексте присутствовало обращение: РеализацияТоваровУслугНомераИзделий.Ссылка.Дата.
Помните: обращение «через точку» от полей табличной части к реквизитам шапки документа (ТабличнаяЧасть.Ссылка.ИмяРеквизита) — это грубое нарушение стандартов разработки 1С. На уровне СУБД такое обращение заставляет платформу создавать дополнительное неявное соединение таблиц SQL. На больших объемах данных это гарантированно приводит к резкому падению производительности и лишней нагрузке на сервер.
Всегда обращайтесь к реквизитам шапки непосредственно через основную таблицу документа (например, Реализация.Дата), как это показано в правильных примерах выше.
Мы рассмотрели, как правильно выбирать документы вместе с их табличными частями, избегая досадной потери данных при пустых таблицах. Для простых задач используйте классическое левое соединение с фильтрацией строго по левой таблице (Способ 1). В высоконагруженных системах и при работе с большими периодами времени всегда отдавайте предпочтение временным таблицам с индексацией (Способ 2). А если вам необходимо получить иерархическую структуру данных без дублирования шапки документа — смело используйте вложенные таблицы выборки (Способ 3).