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