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