Почему после копирования внешней обработки в 1С 8.3 запрос возвращает пустую выборку?

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

Каждый разработчик 1С рано или поздно сталкивается с ситуацией, которая на первый взгляд кажется абсолютно нелогичной и мистической. Представим картину: у нас есть отлично работающая внешняя обработка (файл Обработка.epf). В ней написан и отлажен запрос, который выбирает нужные данные и выводит их пользователю. Мы решаем сделать копию этой обработки — например, простым копированием файла в операционной системе или через меню «Файл — Сохранить как...» в Конфигураторе, назвав новый файл Обработка1.epf. Запускаем копию в той же самой базе данных, под тем же пользователем, в то же самое время — и... получаем пустую выборку! В оригинальной обработке всё работает, а в копии — нет.

В этой статье мы подробно разберем, почему возникает такое странное поведение платформы «1С:Предприятие 8.3», детально изучим внутренние механизмы работы с метаданными и типами данных, а также вскроем критические архитектурные ошибки в самом запросе, которые могут приводить к внезапному исчезновению данных.

---

Причина №1. Автоматическое приведение типов реквизитов к типу «Строка»

Как показывает практика и опыт автора темы, при копировании внешних обработок некоторые реквизиты могут самопроизвольно менять свой тип данных на Строка. Почему это происходит?

Давайте проанализируем механизм работы платформы 1С с типами данных во внешних файлах:

  1. Отсутствие сопоставления метаданных. Если внешняя обработка разрабатывалась в одной конфигурации (или другой версии базы данных), а затем была скопирована и запущена в базе, где метаданные слегка отличаются (например, отсутствует конкретный документ, справочник или его реквизиты), платформа не может сопоставить сложный ссылочный тип. Чтобы предотвратить аварийное завершение работы (Runtime Error), 1С автоматически преобразует неопределенный тип данных в безопасный примитивный тип по умолчанию — «Строка».
  2. Копирование через буфер обмена в Конфигураторе. Если вы копируете реквизиты обработки, элементы формы или части кода из одного окна Конфигуратора в другое, и целевая конфигурация базы данных еще не обновлена (не выполнено принятие изменений через F7), ссылки на типы могут сброситься. Платформа запишет их как обычные строки.
  3. Сбой кэша метаданных Конфигуратора. При сохранении копии файла через «Файл — Сохранить как...» без перезапуска сеанса Конфигуратора может произойти временная потеря контекста типов данных. Реквизит, который должен был содержать ссылку на документ (например, ДокументСсылка.ЗаказКлиента), превращается в пустую строку.

В результате в запросе вместо ожидаемого массива ссылок или конкретного объекта в параметр &СписокЗаказов (или аналогичный реквизит) передается обычное строковое значение. Естественно, запрос пытается сопоставить ссылки в базе со строковым значением, не находит совпадений и возвращает абсолютно пустую выборку.

---

Причина №2. Скрытая критическая ошибка в самом запросе (Декартово произведение)

Даже если мы исправим типы реквизитов, в коде автора темы кроется гораздо более опасная «мина замедленного действия». Рассмотрим исходный текст запроса, который привел к ошибке:


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

Проанализируем этот запрос детально. В секции ИЗ автор указывает две таблицы через запятую:

РегистрНакопления.ЗаказыКлиентов.Остатки(...) КАК ЗаказыКлиентовОстатки, РегистрНакопления.СвободныеОстатки.Остатки(...) КАК СвободныеОстаткиОстатки

Это классический антипаттерн — неявное декартово произведение (Cross Join) без условий связи!

При таком написании СУБД будет вынуждена перемножить каждую строку первой таблицы с каждой строкой второй таблицы. К чему это приводит на практике?

---

Причина №3. Неоптимальное наложение условий фильтрации в запросе

Обратим внимание на секцию ГДЕ в исходном запросе автора:

ГДЕ ЗаказыКлиентовОстатки.ЗаказКлиента В (&СписокЗаказов)

Это еще одна грубая архитектурная ошибка при работе с виртуальными таблицами в 1С. Условие наложено на уже готовую выборку виртуальной таблицы остатков, а не в ее параметрах.

Почему это плохо? Платформа 1С сначала заставит СУБД рассчитать остатки по абсолютно всем заказам, которые есть в базе данных, выгрузит этот огромный массив данных во временную память и только потом начнет отсекать лишнее по условию ГДЕ. Это приводит к колоссальной нагрузке на сервер и медленной работе системы.

Правильный подход — всегда передавать условия фильтрации непосредственно в параметры виртуальной таблицы: РегистрНакопления.ЗаказыКлиентов.Остатки(&Период, ЗаказКлиента В (&СписокЗаказов)). В этом случае СУБД рассчитает остатки только по нужным нам документам еще на этапе обращения к физическим таблицам базы данных.

---

Как правильно решить проблему? Пошаговая инструкция

Давайте объединим все наши выводы и исправим обе проблемы: и сброс типов реквизитов, и некорректно написанный запрос.

Шаг 1. Проверяем типы реквизитов во внешней обработке

Откроем нашу скопированную внешнюю обработку в Конфигураторе. Перейдем в окно свойств реквизитов (как на форме, так и в самой обработке). Убедимся, что реквизит, отвечающий за список заказов или текущую дату, имеет правильный тип (например, ДокументСсылка.ЗаказКлиента или Дата), а не сбросился в тип Строка. Если тип изменился, вручную установим правильное значение.

Шаг 2. Переписываем запрос по стандартам разработки 1С

Чтобы запрос работал корректно, быстро и не возвращал пустую выборку из-за отсутствия данных в одном из регистров, мы должны использовать Явное левое соединение (LEFT JOIN) и перенести все фильтры в параметры виртуальных таблиц.

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


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

Что изменилось в этом запросе и почему теперь он будет работать идеально?

  1. Использование ЛЕВОЕ СОЕДИНЕНИЕ вместо запятой. Теперь основной таблицей выступают остатки по заказам клиентов. Даже если таблица СвободныеОстатки будет абсолютно пустой, запрос все равно вернет все наши заказы, а в полях остатков на складах просто выведутся пустые значения (значения типа NULL).
  2. Обработка NULL значений через функцию ЕСТЬNULL. Чтобы в полях отчета не отображались пустые значения NULL, мы преобразуем их в числовой 0 с помощью конструкции ЕСТЬNULL(Таблица.Поле, 0).
  3. Оптимизация параметров виртуальной таблицы. Фильтр по списку заказов ЗаказКлиента В (&СписокЗаказов) перенесен внутрь круглых скобок параметров таблицы Остатки(...). Это гарантирует максимальную скорость работы запроса даже на огромных базах данных.

---

Дополнительный совет: Очистка кэша 1С

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

Для этого можно воспользоваться стандартным решением — удалить базу из списка запуска 1С и добавить её заново (при этом папка кэша создастся с нуля). Альтернативный вариант для администраторов — запустить скрипт очистки папок Local settings\Application data\1C\1Cv8 на компьютере пользователя.

Соблюдение этих простых правил — контроль типов реквизитов при копировании файлов и написание грамотных запросов с явными соединениями — навсегда избавит вас от «магических» проблем с пустыми выборками во внешних обработках!

← На главную