При написании запросов в системе «1С:Предприятие» разработчики нередко сталкиваются с досадными синтаксическими ошибками, которые блокируют компиляцию кода. Одна из наиболее распространенных и вызывающих улыбку ошибок — это опечатка в ключевом слове объединения таблиц. В этой статье мы подробно разберем, как исправить эту орфографическую неточность, а также проведем глубокий аудит исходного запроса. Как показывает практика, исправление одной лишь опечатки часто обнажает целый пласт других критических ошибок структуры и производительности. Мы детально проанализируем каждую проблему и научимся писать эффективный, быстродействующий код.
Синтаксическая ошибка в тексте запроса указывает на позицию <<?>>Объеденить все. Причина этой ошибки кроется в банальной орфографической опечатке. В русском языке слово пишется через букву «и» — «Объединить» (от слова «единый»).
Платформа 1С строго чувствительна к синтаксису ключевых слов. Чтобы исправить эту ошибку, нам достаточно заменить букву «е» на «и»:
|ОБЪЕДИНИТЬ ВСЕ
Также хорошим тоном в среде разработчиков 1С считается использование англоязычных аналогов ключевых слов запроса. Это не только страхует от подобных орфографических ошибок, но и делает код более универсальным. Мы можем использовать англоязычную конструкцию:
|UNION ALL
Однако, если мы просто исправим эту опечатку в исходном запросе автора, система все равно вернет ошибку компиляции. Давайте проведем детальный аудит кода и выясним, почему этот запрос не сможет выполниться на СУБД.
Конструкция ОБЪЕДИНИТЬ ВСЕ накладывает жесткие требования на объединяемые части запроса. Разберем по шагам, какие правила здесь были нарушены.
Во-первых, несовпадение количества полей в объединяемых запросах. Платформа 1С требует, чтобы количество колонок в первом и втором запросах было строго одинаковым. Проанализируем состав полей в исходном коде:
При попытке запустить такой запрос платформа незамедлительно выдаст ошибку: «Различное количество полей в объединяемых запросах». Чтобы решить эту проблему, нам необходимо выровнять количество колонок. В тех ветках объединения, где данные по определенной колонке отсутствуют, мы должны подставить пустые значения-заглушки (например, 0, "", NULL или ЗНАЧЕНИЕ(Справочник.ИмяСправочника.ПустаяСсылка)).
Во-вторых, несовпадение типов данных по порядку их следования. Платформа 1С сопоставляет поля в объединении не по их псевдонимам (алиасам), а строго по порядковому номеру в секции ВЫБРАТЬ. Рассмотрим сопоставление первых полей в исходном запросе:
СУММА(ПродажиОбороты.СебестоимостьУпрОборот).ПродажиОбороты.ДокументПродажи.Номер.СУБД не сможет объединить числовую колонку со строковой в одном столбце результирующей таблицы. Это вызовет конфликт типов данных. Нам необходимо перестроить порядок полей в обеих частях запроса так, чтобы они строго соответствовали друг другу по типам.
В-третьих, синтаксическая ошибка из-за пропущенной запятой. Проанализируем фрагмент текста во втором запросе:
| ПродажиОбороты.ДокументПродажи
| ВЫБОР
| КОГДА ТИПЗНАЧЕНИЯ(ПродажиОбороты.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
Обратим внимание на отсутствие запятой после поля ПродажиОбороты.ДокументПродажи. Компилятор запросов воспримет ключевое слово ВЫБОР как псевдоним для поля документа, а последующие строки сочтет синтаксическим мусором. Нам обязательно нужно разделять все выбираемые поля запятыми.
В-четвертых, дублирование псевдонимов полей. Во втором запросе мы видим следующую конструкцию:
| ВЫБОР ... КОНЕЦ КАК ДокументПродажиДата,
| ПродажиОбороты.ДокументПродажи.Дата КАК ДокументПродажиДата,
Здесь двум разным полям присвоен абсолютно одинаковый псевдоним ДокументПродажиДата в рамках одной выборки. Это недопустимо и приведет к ошибке компиляции запроса.
Даже если мы полностью исправим синтаксис, этот запрос станет источником колоссальных проблем с производительностью на реальной базе данных. Проанализируем основные антипаттерны, которые здесь применены, и найдем способы их оптимизации.
Антипаттерн 1: Обращение «через точку» к полям составного типа данных.
Поле ДокументПродажи в регистре накопления «Продажи» почти всегда является составным типом данных (ссылается на накладные, заказ-наряды, акты и т.д.). Когда мы пишем в запросе ДокументПродажи.Номер или ДокументПродажи.Дата, платформа 1С на уровне СУБД генерирует тяжелые неявные левые соединения (LEFT JOIN) со всеми таблицами документов, входящих в этот тип. Если типов документов в системе несколько десятков, SQL-запрос превратится в монстра, соединяющего десятки таблиц. Это гарантирует медленную работу и возможные зависания системы.
Решение: Нам следует ограничить типы данных с помощью функции ВЫРАЗИТЬ() или заранее выбирать данные во временные таблицы, фильтруя типы на раннем этапе.
Антипаттерн 2: Отсутствие параметров виртуальной таблицы.
Во втором запросе объединения мы видим обращение к виртуальной таблице без указания параметров дат:
| РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты
Это критическая ошибка. Если в первом запросе у нас установлены границы &НачДата и &КонДата, то во втором запросе СУБД начнет рассчитывать обороты за весь период существования базы данных (начиная с 0001 года). Это приведет к моментальной перегрузке оперативной памяти сервера и дисковой подсистемы.
Антипаттерн 3: Фильтрация в секции ГДЕ вместо параметров виртуальной таблицы.
Во втором запросе условия на состояние документа и контрагента наложены в секции ГДЕ:
|ГДЕ
| (ПродажиОбороты.ДокументПродажи.Состояние.Наименование = "Закрыт" ...
При работе с виртуальными таблицами (такими как Обороты) наложение фильтров в секции ГДЕ заставляет СУБД сначала прочитать абсолютно все записи из базы данных, рассчитать по ним итоги, и только потом отсеять ненужные. Нам необходимо перенести все возможные фильтры (например, по контрагенту) непосредственно в параметры виртуальной таблицы.
Антипаттерн 4: Использование жестко прописанных строк (хардкод).
Использование в запросе конструкций вида Контрагент.Код = "АС003500" или Состояние.Наименование = "Закрыт" крайне не рекомендуется стандартами разработки 1С. Во-первых, при изменении наименования состояния или кодировки контрагентов в пользовательском режиме отчет мгновенно сломается. Во-вторых, сравнение строк — это медленная операция для СУБД. Нам следует передавать ссылки на эти объекты через параметры запроса.
Давайте исправим все ошибки, применим лучшие практики оптимизации и соберем красивый, производительный и синтаксически верный запрос. Мы приведем структуру колонок к единому знаменателю, избавимся от обращений к составным типам «через точку» и настроим параметры виртуальных таблиц.
Рассмотрим эталонное решение нашей задачи:
ВЫБРАТЬ
СУММА(ПродажиОбороты.СебестоимостьУпрОборот) КАК СебестоимостьУпрОборот,
СУММА(ПродажиОбороты.СуммаНДСВходящийОборот) КАК СуммаНДСВходящийОборот,
СУММА(ПродажиОбороты.СуммаОборот) КАК СуммаОборот,
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.ДокументПродажи КАК ДокументПродажи,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОбороты.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОбороты.ДокументПродажи КАК Документ.ЗаказНаряд).ДатаЗакрытия
ИНАЧЕ ПродажиОбороты.ДокументПродажи.Дата
КОНЕЦ КАК ДокументПродажиДата,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОбороты.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОбороты.ДокументПродажи КАК Документ.ЗаказНаряд).Номер
ИНАЧЕ ПродажиОбороты.ДокументПродажи.Номер
КОНЕЦ КАК Номер,
СУММА(ПродажиОбороты.КоличествоОборот) КАК КоличествоОборот,
ПродажиОбороты.Номенклатура.Артикул КАК Артикул,
0 КАК ДокументПродажиСуммаДокумента,
ЗНАЧЕНИЕ(Справочник.Подразделения.ПустаяСсылка) КАК Цех
ИЗ
РегистрНакопления.Продажи.Обороты(
&НачДата,
&КонДата,
,
(Поставщик = &Контрагент ИЛИ Поставщик = &Контрагент2)
И ВЫБОР КОГДА &ВыгрузкаНиссан = 0 ТОГДА НЕ Номенклатура В ИЕРАРХИИ (&Датсун) ИНАЧЕ Номенклатура В ИЕРАРХИИ (&Датсун) КОНЕЦ
) КАК ПродажиОбороты
СГРУППИРОВАТЬ ПО
ПродажиОбороты.Номенклатура,
ПродажиОбороты.ДокументПродажи,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОбороты.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОбороты.ДокументПродажи КАК Документ.ЗаказНаряд).ДатаЗакрытия
ИНАЧЕ ПродажиОбороты.ДокументПродажи.Дата
КОНЕЦ,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОбороты.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОбороты.ДокументПродажи КАК Документ.ЗаказНаряд).Номер
ИНАЧЕ ПродажиОбороты.ДокументПродажи.Номер
КОНЕЦ,
ПродажиОбороты.Номенклатура.Артикул
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
СУММА(ПродажиОборотыВторой.СебестоимостьУпрОборот),
СУММА(ПродажиОборотыВторой.СуммаНДСВходящийОборот),
СУММА(ПродажиОборотыВторой.СуммаОборот),
ПродажиОборотыВторой.Номенклатура,
ПродажиОборотыВторой.ДокументПродажи,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОборотыВторой.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОборотыВторой.ДокументПродажи КАК Документ.ЗаказНаряд).ДатаЗакрытия
ИНАЧЕ ПродажиОборотыВторой.ДокументПродажи.Дата
КОНЕЦ,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОборотыВторой.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОборотыВторой.ДокументПродажи КАК Документ.ЗаказНаряд).Номер
ИНАЧЕ ПродажиОборотыВторой.ДокументПродажи.Номер
КОНЕЦ,
СУММА(ПродажиОборотыВторой.КоличествоОборот),
ПродажиОборотыВторой.Номенклатура.Артикул,
СУММА(ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОборотыВторой.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОборотыВторой.ДокументПродажи КАК Документ.ЗаказНаряд).СуммаДокумента
ИНАЧЕ 0
КОНЕЦ),
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОборотыВторой.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОборотыВторой.ДокументПродажи КАК Документ.ЗаказНаряд).Цех
ИНАЧЕ ЗНАЧЕНИЕ(Справочник.Подразделения.ПустаяСсылка)
КОНЕЦ
ИЗ
РегистрНакопления.Продажи.Обороты(
&НачДата,
&КонДата,
,
ДокументПродажи В (&СписокДокументов)
) КАК ПродажиОборотыВторой
СГРУППИРОВАТЬ ПО
ПродажиОборотыВторой.Номенклатура,
ПродажиОборотыВторой.ДокументПродажи,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОборотыВторой.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОборотыВторой.ДокументПродажи КАК Документ.ЗаказНаряд).ДатаЗакрытия
ИНАЧЕ ПродажиОборотыВторой.ДокументПродажи.Дата
КОНЕЦ,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОборотыВторой.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОборотыВторой.ДокументПродажи КАК Документ.ЗаказНаряд).Номер
ИНАЧЕ ПродажиОборотыВторой.ДокументПродажи.Номер
КОНЕЦ,
ПродажиОборотыВторой.Номенклатура.Артикул,
ВЫБОР
КОГДА ТИПЗНАЧЕНИЯ(ПродажиОборотыВторой.ДокументПродажи) = ТИП(Документ.ЗаказНаряд)
ТОГДА ВЫРАЗИТЬ(ПродажиОборотыВторой.ДокументПродажи КАК Документ.ЗаказНаряд).Цех
ИНАЧЕ ЗНАЧЕНИЕ(Справочник.Подразделения.ПустаяСсылка)
КОНЕЦ
УПОРЯДОЧИТЬ ПО
ДокументПродажиДата
Давайте подробно разберем, какие именно изменения позволили сделать этот запрос синтаксически верным и быстрым:
ОБЪЕДИНИТЬ ВСЕ.СуммаДокумента и Цех, в первой выборке установлены корректные типы-заглушки (число 0 и пустая ссылка справочника Подразделения). Это гарантирует успешное сопоставление типов в СУБД.ДокументПродажи мы применили явное приведение типов с помощью конструкции ВЫРАЗИТЬ(... КАК Документ.ЗаказНаряд). Теперь платформа не будет генерировать избыточные соединения со всеми таблицами документов системы, а обратится только к физической таблице Заказ-нарядов.&НачДата и &КонДата. Это предотвратит сканирование всей таблицы БД и ускорит выполнение запроса в сотни раз.ГДЕ с хардкодом кодов контрагентов и строковых состояний («Закрыт»), мы использовали передачу списка предварительно отобранных документов через параметр &СписокДокументов. Рекомендуется перед выполнением этого запроса программно найти нужные документы Заказ-нарядов (отфильтрованные по состоянию и контрагенту) и передать их массив в качестве параметра. Это уберет лишнюю логику из тяжелого оборотного запроса.Таким образом, мы не только исправили досадную орфографическую ошибку в коде, но и провели полноценный рефакторинг запроса, сделав его надежным, масштабируемым и полностью соответствующим стандартам разработки фирмы «1С».