Как в отчете СКД убрать полностью отгруженные заказы с помощью параметра Да/Нет?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке отчетов по продажам и заказам в системе 1С:Предприятие 8 одной из классических задач является фильтрация данных. Пользователям часто требуется видеть только актуальную информацию — например, исключить из отчета те заказы покупателей, по которым обязательства полностью выполнены (то есть количество заказанного товара совпадает с количеством отгруженного). При этом на форме отчета должен присутствовать удобный флаг-переключатель, управляющий этим поведением (например, ОтсевОтгруженных со значениями Да/Нет).

В этой статье мы подробно разберем, как решить эту задачу средствами Системы компоновки данных (СКД), проанализируем неочевидные ошибки в исходных запросах, снижающие производительность базы данных, и рассмотрим три альтернативных варианта реализации фильтрации — от простого изменения запроса до профессиональной настройки встроенных механизмов СКД.

Анализ исходного запроса и выявление критических ошибок

Прежде чем переходить к реализации фильтрации, давайте детально проанализируем запрос, который часто используют начинающие разработчики. Рассмотрим его структуру:


ВЫБРАТЬ
    СчетНаОплатуПокупателюТовары.Номенклатура КАК НоменклатураЗаказано,
    СчетНаОплатуПокупателю.Ссылка КАК СчетНаОплатуКлиенту,
    СчетНаОплатуПокупателюТовары.Номенклатура.Артикул КАК АртикулЗаказаннойНоменклатуры,
    СУММА(СчетНаОплатуПокупателюТовары.Количество) КАК КоличествоЗаказано,
    СчетНаОплатуПокупателю.Контрагент.Ссылка КАК Клиент
ПОМЕСТИТЬ Вт_СчетНаОплатуПокупателю
ИЗ
    Документ.СчетНаОплатуПокупателю КАК СчетНаОплатуПокупателю
        ПОЛНОЕ СОЕДИНЕНИЕ Документ.СчетНаОплатуПокупателю.Товары КАК СчетНаОплатуПокупателюТовары
        ПО СчетНаОплатуПокупателю.Ссылка = СчетНаОплатуПокупателюТовары.Ссылка
ГДЕ
    СчетНаОплатуПокупателю.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
    И СчетНаОплатуПокупателю.ПометкаУдаления = ЛОЖЬ
    И СчетНаОплатуПокупателю.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
    СчетНаОплатуПокупателюТовары.Номенклатура,
    СчетНаОплатуПокупателю.Ссылка,
    СчетНаОплатуПокупателюТовары.Номенклатура.Артикул,
    СчетНаОплатуПокупателю.Контрагент.Ссылка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    РеализацияТоваровУслугТовары.Номенклатура КАК НоменклатураРеализовано,
    СУММА(РеализацияТоваровУслугТовары.Количество) КАК КоличествоРеализовано,
    РеализацияТоваровУслуг.СчетНаОплатуПокупателю КАК РеализованоСчетНаОплатуПокупателю
ПОМЕСТИТЬ Вт_РеализованоКлиенту
ИЗ
    Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
        ПО РеализацияТоваровУслуг.Ссылка = РеализацияТоваровУслугТовары.Ссылка
СГРУППИРОВАТЬ ПО
    РеализацияТоваровУслуг.СчетНаОплатуПокупателю,
    РеализацияТоваровУслугТовары.Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Вт_СчетНаОплатуПокупателю.СчетНаОплатуКлиенту КАК СчетНаОплатуКлиенту,
    Вт_СчетНаОплатуПокупателю.НоменклатураЗаказано КАК НоменклатураЗаказано,
    Вт_СчетНаОплатуПокупателю.АртикулЗаказаннойНоменклатуры КАК АртикулЗаказаннойНоменклатуры,
    Вт_СчетНаОплатуПокупателю.КоличествоЗаказано КАК КоличествоЗаказано,
    Вт_СчетНаОплатуПокупателю.Клиент КАК Клиент,
    Вт_РеализованоКлиенту.НоменклатураРеализовано КАК НоменклатураРеализовано,
    ВЫБОР КОГДА Вт_РеализованоКлиенту.КоличествоРеализовано ЕСТЬ NULL ТОГДА 0 ИНАЧЕ Вт_РеализованоКлиенту.КоличествоРеализовано КОНЕЦ КАК КоличествоРеализовано,
    Вт_СчетНаОплатуПокупателю.КоличествоЗаказано - Вт_РеализованоКлиенту.КоличествоРеализовано КАК Остаток
ИЗ
    Вт_СчетНаОплатуПокупателю КАК Вт_СчетНаОплатуПокупателю
        ЛЕВОЕ СОЕДИНЕНИЕ Вт_РеализованоКлиенту КАК Вт_РеализованоКлиенту
        ПО Вт_СчетНаОплатуПокупателю.НоменклатураЗаказано.Ссылка = Вт_РеализованоКлиенту.НоменклатураРеализовано.Ссылка
        И Вт_СчетНаОплатуПокупателю.СчетНаОплатуКлиенту.Ссылка = Вт_РеализованоКлиенту.РеализованоСчетНаОплатуПокупателю.Ссылка

В этом коде содержатся две критические архитектурные ошибки, которые могут привести к неверным результатам или зависанию базы данных при росте объема документов.

Ошибка 1. Потеря данных из-за NULL при расчете остатка.
Обратите внимание на строку расчета остатка в последнем пакете запроса:
Вт_СчетНаОплатуПокупателю.КоличествоЗаказано - Вт_РеализованоКлиенту.КоличествоРеализовано КАК Остаток.
Поскольку используется ЛЕВОЕ СОЕДИНЕНИЕ, для тех заказов, по которым еще не было оформлено ни одной реализации, поле КоличествоРеализовано вернет значение NULL. В SQL-подобных языках любая математическая операция с NULL (например, 10 - NULL) дает в результате NULL. Из-за этого у всех неотгруженных заказов поле «Остаток» в отчете окажется пустым, хотя должно быть равно заказанному количеству. Чтобы избежать этого, необходимо всегда использовать функцию ЕСТЬNULL().

Ошибка 2. Неоптимальное соединение таблиц (ПОЛНОЕ СОЕДИНЕНИЕ).
В первом пакете используется ПОЛНОЕ СОЕДИНЕНИЕ шапки документа и его табличной части Товары. Строки табличной части физически не могут существовать без ссылки на документ-владелец. Использование полного соединения здесь избыточно, замедляет работу СУБД и должно быть заменено на стандартное ВНУТРЕННЕЕ СОЕДИНЕНИЕ или обращение к табличной части напрямую.

Решение №1. Внедрение фильтрации непосредственно в текст запроса

Самый быстрый способ решить задачу «в лоб» — добавить условие отсева прямо в секцию ГДЕ финального запроса пакета. Для этого мы введем булевый параметр &УбратьРеализованные (или &ОтсевОтгруженных).

Рассмотрим стандартный, но не самый производительный вариант условия:


ГДЕ ВЫБОР КОГДА &УбратьРеализованные ТОГДА Вт_СчетНаОплатуПокупателю.КоличествоЗаказано <> ЕСТЬNULL(Вт_РеализованоКлиенту.КоличествоРеализовано, 0) ИНАЧЕ ИСТИНА КОНЕЦ

Почему этот вариант плох? Использование конструкции ВЫБОР КОГДА внутри секции ГДЕ усложняет построение плана запроса для СУБД (например, MS SQL Server). База данных не может эффективно применить индексы, что приводит к полному сканированию таблиц (Table Scan) при работе с большими объемами данных.

Оптимальный вариант на уровне запроса:
Перепишем это условие с помощью классической булевой логики (оператора ИЛИ). Этот вариант обрабатывается СУБД в разы быстрее:


ГДЕ НЕ &УбратьРеализованные
    ИЛИ Вт_СчетНаОплатуПокупателю.КоличествоЗаказано <> ЕСТЬNULL(Вт_РеализованоКлиенту.КоличествоРеализовано, 0)

Давайте разберем, как работает эта логика:

  1. Если параметр &УбратьРеализованные равен Ложь (пользователь хочет видеть все заказы), то выражение НЕ &УбратьРеализованные превращается в ИСТИНА. Условие после оператора ИЛИ даже не проверяется, и в отчет попадают абсолютно все записи.
  2. Если параметр &УбратьРеализованные равен Истина (пользователь хочет скрыть закрытые заказы), то первая часть условия (НЕ &УбратьРеализованные) превращается в ЛОЖЬ. СУБД начинает проверять вторую часть условия: КоличествоЗаказано <> КоличествоРеализовано. В результате в отчет попадут только те строки, где остаток не равен нулю.

Решение №2. Использование стандартных настроек отбора СКД (Рекомендуемое)

В платформе 1С принято разделять логику получения данных (запрос) и логику их представления (фильтры, сортировки, группировки). Навязывание условий непосредственно в коде запроса лишает отчет гибкости. Более красивым и правильным решением является использование штатного механизма отборов СКД.

Рассмотрим по шагам, как это настроить:

  1. В схеме компоновки данных (СКД) на вкладке «Наборы данных» скорректируем расчет поля Остаток с учетом исправления ошибки NULL:
    
    Вт_СчетНаОплатуПокупателю.КоличествоЗаказано - ЕСТЬNULL(Вт_РеализованоКлиенту.КоличествоРеализовано, 0) КАК Остаток
    
  2. Перейдем на вкладку «Настройки» в нижней части конструктора СКД.
  3. Выберем вкладку «Отбор» в настройках отчета.
  4. Добавим новое поле отбора: выбрать поле Остаток, вид сравнения установить в значение Не равно (или Больше), в качестве значения укажем 0.
  5. Нажмем на кнопку «Свойства элемента пользовательских настроек» (иконка в виде шестеренки или колонки настроек в зависимости от версии платформы).
  6. В открывшемся окне установим флаг «Включать в пользовательские настройки» и выберем режим отображения «Быстрый доступ».

При таком подходе на форме отчета у пользователя автоматически появится удобный флажок Остаток Не равно 0. Пользователь сможет самостоятельно включать и отключать его. При этом СКД самостоятельно и максимально оптимизировано модифицирует итоговый SQL-запрос перед отправкой его на сервер СУБД.

Решение №3. Использование расширения языка запросов СКД (Фигурные скобки)

Если нам необходимо, чтобы фильтрация происходила на уровне базы данных, но при этом мы не хотим перегружать запрос жесткими параметрами, на помощь приходит расширение языка запросов СКД. Мы можем заключить наше условие в фигурные скобки {} в секции ГДЕ.

Рассмотрим пример реализации:


ВЫБРАТЬ
    Вт_СчетНаОплатуПокупателю.СчетНаОплатуКлиенту КАК СчетНаОплатуКлиенту,
    Вт_СчетНаОплатуПокупателю.КоличествоЗаказано КАК КоличествоЗаказано,
    ЕСТЬNULL(Вт_РеализованоКлиенту.КоличествоРеализовано, 0) КАК КоличествоРеализовано,
    Вт_СчетНаОплатуПокупателю.КоличествоЗаказано - ЕСТЬNULL(Вт_РеализованоКлиенту.КоличествоРеализовано, 0) КАК Остаток
ИЗ
    Вт_СчетНаОплатуПокупателю КАК Вт_СчетНаОплатуПокупателю
    ЛЕВОЕ СОЕДИНЕНИЕ Вт_РеализованоКлиенту КАК Вт_РеализованоКлиенту
    ПО Вт_СчетНаОплатуПокупателю.НоменклатураЗаказано = Вт_РеализованоКлиенту.НоменклатураРеализовано
{ГДЕ
    (Вт_СчетНаОплатуПокупателю.КоличествоЗаказано - ЕСТЬNULL(Вт_РеализованоКлиенту.КоличествоРеализовано, 0)) КАК Остаток}

Как это работает? Описание условия внутри фигурных скобок указывает системе компоновки данных, что этот фильтр является необязательным (динамическим). Если пользователь в настройках отчета (на форме) устанавливает отбор по полю Остаток, СКД автоматически «раскомментирует» этот участок кода и подставит его в итоговый запрос к СУБД. Если отбор отключен — запрос останется чистым, без лишних вычислений в секции ГДЕ, что гарантирует максимальную производительность системы.

Резюме и рекомендации

Подводя итоги, выделим основные правила реализации подобных задач в 1С:

← На главную