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