При написании сложных условий в конструкции ВЫБОР КОГДА ... ТОГДА языка запросов 1С разработчики нередко сталкиваются с ситуацией: достаточно поменять порядок условий местами, как запрос начинает возвращать совершенно другие данные. Кажется, что логика нарушена или платформа ведет себя непредсказуемо. Разберем по шагам, почему так происходит, как работают приоритеты булевых операций и какие нюансы СУБД важно учитывать.
Рассмотрим исходное выражение, которое проверяет документы в регистре накопления:
ВЫБОР
КОГДА СебестоимостьТоваров.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
И ВЫРАЗИТЬ(СебестоимостьТоваров.Регистратор КАК Документ.РеализацияТоваровУслуг).Согласован
ИЛИ СебестоимостьТоваров.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ
Автор запроса ожидал получить документы, которые являются либо согласованной реализацией, либо перемещением товаров. Однако при попытке перенести проверку реквизита в конец выражения логика полностью изменилась:
// Измененный порядок:
СебестоимостьТоваров.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
ИЛИ СебестоимостьТоваров.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
И ВЫРАЗИТЬ(СебестоимостьТоваров.Регистратор КАК Документ.РеализацияТоваровУслуг).Согласован
Выясним причину такого поведения: дело заключается в математическом приоритете логических связок и особенностях трехзначной логики SQL.
В реляционных базах данных и языке запросов 1С действует строгая иерархия выполнения логических операторов. Посмотрим на стандартный порядок убывания приоритета:
() — имеют наивысший приоритет и выполняются в первую очередь.НЕ.=, <>, >, <, а также проверка типа ССЫЛКА).И (конъюнкция) — логическое умножение.ИЛИ (дизъюнкция) — логическое сложение, имеет самый низкий приоритет.Поскольку операция И имеет более высокий приоритет, чем ИЛИ (аналогично умножению и сложению в арифметике), СУБД группирует условия по-разному в зависимости от их взаимного расположения.
Проанализируем математический эквивалент с расстановкой неявных скобок:
В первом варианте мы имели структуру (A И B) ИЛИ C:
(СебестоимостьТоваров.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
И ВЫРАЗИТЬ(СебестоимостьТоваров.Регистратор КАК Документ.РеализацияТоваровУслуг).Согласован)
ИЛИ СебестоимостьТоваров.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
Здесь логика ясна: строка отбирается, если это согласованная реализация, либо если это перемещение товаров.
После перестановки условий получилось выражение вида A ИЛИ (C И B):
СебестоимостьТоваров.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
ИЛИ (СебестоимостьТоваров.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
И ВЫРАЗИТЬ(СебестоимостьТоваров.Регистратор КАК Документ.РеализацияТоваровУслуг).Согласован)
В этом случае смысл условия кардинально исказился: отбирается любая реализация (даже несогласованная) либо документ, который одновременно является перемещением и при этом имеет признак согласованной реализации.
Помимо приоритета операторов, существует скрытый фактор — трехзначная логика реляционных СУБД (ИСТИНА, ЛОЖЬ, NULL).
Конструкция ВЫРАЗИТЬ(Поле КАК Документ...).Реквизит транслируется платформой 1С в неявное левое соединение (LEFT JOIN) с таблицей документа. Если строка регистра сформирована документом «Перемещение товаров», то поля из таблицы «Реализация товаров и услуг» будут содержать значение NULL.
По правилам трехзначной логики:
ИСТИНА И NULL возвращает NULL.ЛОЖЬ И NULL возвращает ЛОЖЬ.ЛОЖЬ ИЛИ NULL возвращает NULL.В конструкции ВЫБОР КОГДА ... ТОГДА переход в ветку ТОГДА происходит только при строгом значении ИСТИНА. Значения ЛОЖЬ и NULL одинаково отправляют выполнение в ветку ИНАЧЕ. Поэтому для строк с перемещением второе выражение превращается в ЛОЖЬ ИЛИ (ИСТИНА И NULL), что дает NULL и приводит к неверному результату.
Кроме того, оптимизатор СУБД не гарантирует сокращенное (ленивое) вычисление условий внутри единого булева выражения: порядок физической проверки предикатов определяется планом запроса.
Если требуется объединить несколько логических условий в одну ветку, всегда явно указывайте границы операций круглыми скобками:
ВЫБОР
КОГДА (СебестоимостьТоваров.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
И ВЫРАЗИТЬ(СебестоимостьТоваров.Регистратор КАК Документ.РеализацияТоваровУслуг).Согласован)
ИЛИ СебестоимостьТоваров.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ
В стандарте языка запросов 1С ветки оператора ВЫБОР вычисляются строго последовательно сверху вниз. Переход к следующему блоку КОГДА происходит только тогда, когда предыдущий вернул ЛОЖЬ или NULL. Это полностью исключает проблемы приоритета операций и защищает от неожиданного поведения при неявных соединениях:
ВЫБОР
КОГДА СебестоимостьТоваров.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
И ВЫРАЗИТЬ(СебестоимостьТоваров.Регистратор КАК Документ.РеализацияТоваровУслуг).Согласован
ТОГДА ИСТИНА
КОГДА СебестоимостьТоваров.Регистратор ССЫЛКА Документ.ПеремещениеТоваров
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ
Если обращение к реквизитам через точку неизбежно, обезопасьте логику явной обработкой неопределенных значений. Оберните разыменование реквизита в функцию ЕСТЬNULL, указав значение по умолчанию:
ЕСТЬNULL(ВЫРАЗИТЬ(СебестоимостьТоваров.Регистратор КАК Документ.РеализацияТоваровУслуг).Согласован, ЛОЖЬ)
Такой подход гарантирует, что реквизит всегда будет иметь четкий булев тип (ИСТИНА или ЛОЖЬ), предотвращая непредвиденные сбои логики при обработке строк других типов документов.