При разработке и отладке отчетов и обработок в режиме предприятия программисты часто используют встроенную консоль запросов. Однако при работе в тонком клиенте (в интерфейсе «Такси») можно столкнуться с крайне неприятной и опасной особенностью: встроенный конструктор запросов при редактировании условий связи таблиц самовольно удаляет группировочные скобки в логических выражениях. В результате логика выполнения запроса неявно меняется, что приводит к получению неверных данных в отчетах без вызова каких-либо ошибок выполнения. В этой статье мы подробно разберем, почему возникает эта проблема, как она влияет на результаты запросов и как обойти этот баг платформы.
Представим стандартную задачу, где нам необходимо связать две таблицы по сложному логическому условию. Изначально наше условие соединения выглядит следующим образом:
Условие1 И (Условие2 ИЛИ Условие3)
В этом выражении приоритет очевиден: СУБД сначала вычислит результат логического сложения во внутренних скобках (Условие2 ИЛИ Условие3), а затем соединит его по логическому «И» с первым условием. Если открыть этот запрос в конструкторе запросов в тонком клиенте (режиме предприятия), перейти на вкладку «Связи» и хотя бы один раз зайти в окно редактирования условия связи, а затем нажать «ОК», то конструктор перезапишет текст условия. На выходе мы получим выражение вида:
Условие1 И Условие2 ИЛИ Условие3
Давайте проанализируем, как изменился приоритет выполнения операций. В результирующем выражении логическое «И» имеет более высокий приоритет, чем «ИЛИ». СУБД сгруппирует условия так: (Условие1 И Условие2) ИЛИ Условие3. В итоге мы получаем абсолютно другую логику соединения данных, которая кардинально исказит конечную выборку.
Рассмотрим реальный пример кода, предоставленный одним из участников обсуждения. До открытия в конструкторе запросов в режиме предприятия соединение выглядело так:
ЛЕВОЕ СОЕДИНЕНИЕ Документ.УКЛЗ_ПутевойЛист КАК УКЛЗ_ПутевойЛист
ПО УКЛЗ_ПутевойЛист.Автомобиль = ВТРазницаДат.ТранспортноеСредство
И УКЛЗ_ПутевойЛист.Проведен = ИСТИНА
И (РАЗНОСТЬДАТ(ВТРазницаДат.ДатаУстановки, УКЛЗ_ПутевойЛист.ДатаОтправки, СЕКУНДА) = ВТРазницаДат.РазностьДат
ИЛИ РАЗНОСТЬДАТ(ВТРазницаДат.ДатаУстановки, УКЛЗ_ПутевойЛист.ДатаОтправки, СЕКУНДА) = -ВТРазницаДат.РазностьДат)
После открытия связи в тонком конструкторе запросов и его закрытия скобки на блоке ИЛИ бесследно исчезли, а все выражение связи было обернуто в единые общие скобки:
ЛЕВОЕ СОЕДИНЕНИЕ Документ.УКЛЗ_ПутевойЛист КАК УКЛЗ_ПутевойЛист
ПО (УКЛЗ_ПутевойЛист.Автомобиль = ВТРазницаДат.ТранспортноеСредство
И УКЛЗ_ПутевойЛист.Проведен = ИСТИНА
И РАЗНОСТЬДАТ(ВТРазницаДат.ДатаУстановки, УКЛЗ_ПутевойЛист.ДатаОтправки, СЕКУНДА) = ВТРазницаДат.РазностьДат
ИЛИ РАЗНОСТЬДАТ(ВТРазницаДат.ДатаУстановки, УКЛЗ_ПутевойЛист.ДатаОтправки, СЕКУНДА) = -ВТРазницаДат.РазностьДат)
Обратите внимание: скобки вокруг всей конструкции ПО (...) не спасают положение. Внутри этих скобок выражение УКЛЗ_ПутевойЛист.Автомобиль... И Проведен... И РАЗНОСТЬДАТ... сгруппировано по приоритету «И», а условие с отрицательной разностью дат присоединено через слабое «ИЛИ». В результате левое соединение сработает для любой записи, где разность дат равна отрицательному значению, даже если автомобиль не совпадает и документ путевого листа не проведен! Это критическая ошибка логики.
Выясним причину такого странного поведения платформы. Всё дело в кардинальном различии механизмов работы конструктора запросов в конфигураторе (толстом клиенте) и в пользовательском режиме предприятия (тонком клиенте):
И, визуально распределяются по разным строкам таблицы связей, сохраняя структуру.СхемаЗапроса (QuerySchema). При открытии текста запроса в тонком клиенте платформа парсит его в дерево объектов СхемаЗапроса, а при закрытии конструктора генерирует текст заново на основе этой объектной модели.Именно в механизмах трансляции текста в объектную модель СхемаЗапроса и обратно кроется баг. При обработке условий в секции ГДЕ (отборы) парсер платформы работает корректно: он умеет распознавать сложные вложенные конструкции с ИЛИ и при генерации итогового текста автоматически расставляет скобки. А вот для секции ПО (условия связей таблиц) разработчики платформы применили упрощенный алгоритм генерации. Текст связи хранится как единая строка, и при её синтаксическом разборе внутренние группировочные скобки ошибочно распознаются парсером как «избыточные» и безвозвратно удаляются.
Данная проблема стабильно воспроизводится на многих версиях платформы 1С:Предприятие 8.3 (включая популярные релизы 8.3.13, 8.3.15 и более свежие сборки).
Для того чтобы лично убедиться в наличии проблемы, мы можем проделать простой эксперимент. Возьмем синтетический запрос следующего вида:
ВЫБРАТЬ
"Текст1" КАК Поле1
ПОМЕСТИТЬ ВременнаяТаблица1
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
"Текст2" КАК Поле2
ПОМЕСТИТЬ ВременнаяТаблица2
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВременнаяТаблица1.Поле1 КАК Поле1,
ВременнаяТаблица2.Поле2 КАК Поле2
ИЗ
ВременнаяТаблица1 КАК ВременнаяТаблица1
ЛЕВОЕ СОЕДИНЕНИЕ ВременнаяТаблица2 КАК ВременнаяТаблица2
ПО &Условие1
И (&Условие2 ИЛИ &Условие3)
Выполним следующие шаги:
&Условие2 ИЛИ &Условие3 исчезнут, превратив логическое выражение в кашу.Что же делать разработчику в данной ситуации? Проанализируем возможные варианты решения проблемы.
Если вам часто приходится отлаживать сложные запросы непосредственно в режиме предприятия, откажитесь от стандартных консолей запросов с диска ИТС, которые вызывают платформенный тонкий конструктор. Используйте профессиональные альтернативные инструменты разработки (например, сторонние продвинутые консоли запросов или коммерческие подсистемы разработчика). Некоторые из них умеют перехватывать текст запроса перед вызовом конструктора и восстанавливать корректные скобки и комментарии программным путем.
С точки зрения стандартов разработки фирмы «1С» и обеспечения максимальной производительности СУБД, использование оператора ИЛИ в условиях соединения таблиц (секция ПО) является крайне нежелательным. Такие конструкции не позволяют СУБД эффективно использовать индексы таблиц, что приводит к полному сканированию данных (Table Scan / Index Scan), росту блокировок и критическому падению производительности информационной базы.
Рекомендуем переписать логику запроса одним из следующих способов:
Вариант А. Разделение запроса через оператор ОБЪЕДИНИТЬ ВСЕ
Вместо одного сложного соединения с условием ИЛИ, мы можем выполнить два простых и быстрых соединения с последующим объединением результатов. Рассмотрим, как будет выглядеть оптимизированный вариант нашего примера:
ВЫБРАТЬ
ВТРазницаДат.ТранспортноеСредство,
УКЛЗ_ПутевойЛист.Ссылка КАК ПутевойЛист
ИЗ
ВТРазницаДат КАК ВТРазницаДат
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.УКЛЗ_ПутевойЛист КАК УКЛЗ_ПутевойЛист
ПО УКЛЗ_ПутевойЛист.Автомобиль = ВТРазницаДат.ТранспортноеСредство
И УКЛЗ_ПутевойЛист.Проведен = ИСТИНА
И РАЗНОСТЬДАТ(ВТРазницаДат.ДатаУстановки, УКЛЗ_ПутевойЛист.ДатаОтправки, СЕКУНДА) = ВТРазницаДат.РазностьДат
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ВТРазницаДат.ТранспортноеСредство,
УКЛЗ_ПутевойЛист.Ссылка КАК ПутевойЛист
ИЗ
ВТРазницаДат КАК ВТРазницаДат
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.УКЛЗ_ПутевойЛист КАК УКЛЗ_ПутевойЛист
ПО УКЛЗ_ПутевойЛист.Автомобиль = ВТРазницаДат.ТранспортноеСредство
И УКЛЗ_ПутевойЛист.Проведен = ИСТИНА
И РАЗНОСТЬДАТ(ВТРазницаДат.ДатаУстановки, УКЛЗ_ПутевойЛист.ДатаОтправки, СЕКУНДА) = -ВТРазницаДат.РазностьДат
Этот запрос отработает на СУБД в десятки раз быстрее, так как планировщик сможет построить оптимальный план выполнения по индексам. И самое главное — в условиях соединения больше нет оператора ИЛИ, а значит, тонкий конструктор запросов физически не сможет испортить логику выражения при открытии.
Вариант Б. Предварительная подготовка данных во временных таблицах
Если условия соединения завязаны на сложные расчеты и разности дат, подготовьте уникальные ключи связи заранее во временной таблице. Сделайте так, чтобы финальное соединение происходило по максимально простым равенствам полей, например: ПО Таблица1.КлючСвязи = Таблица2.КлючСвязи. Это полностью обезопасит ваш код от багов парсера СхемаЗапроса.
Поскольку баг со скобками является специфической особенностью именно тонкого клиента и механизма СхемаЗапроса, в конфигураторе (толстом клиенте) этот баг не проявляется. Если вам жизненно необходимо написать сложное условие связи с круглыми скобками, делайте это исключительно в режиме конфигуратора. Постарайтесь выработать привычку не открывать такие запросы в режиме предприятия, чтобы случайно не сбросить корректные настройки логических приоритетов.
Проанализировав данную проблему, мы видим, что даже в платформенных механизмах 1С могут скрываться досадные ошибки. Будьте внимательны при работе в тонком конструкторе запросов и всегда перепроверяйте итоговый текст запроса, если в условиях соединения ваших таблиц используются сложные комбинации операторов И и ИЛИ.