Почему реквизит ЭтоГруппа (_Folder) в базе данных 1С хранится в инвертированном виде?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Каждый разработчик, который хоть раз сталкивался с необходимостью написания прямых запросов к базе данных «1С:Предприятие» или занимался интеграцией 1С со сторонними системами на уровне СУБД, обращал внимание на странную особенность. Логика хранения большинства булевых реквизитов кажется абсолютно прозрачной: признак проведения документа Проведен (поле _Posted) принимает значение TRUE (или 0x01), если документ проведен, а признак пометки на удаление ПометкаУдаления (поле _Marked) равен TRUE, если объект помечен на удаление. Однако, когда дело доходит до иерархических справочников и реквизита ЭтоГруппа (поле _Folder), логика внезапно переворачивается с ног на голову.

В этой статье мы подробно разберем, почему для групп справочника в СУБД хранится значение FALSE (или бинарный ноль 0x00), а для обычных элементов — значение TRUE (бинарная единица 0x01). Мы погрузимся в историю развития платформы, проанализируем низкоуровневые механизмы работы СУБД и выясним, какие подводные камни ожидают разработчика при интеграции и как их успешно обойти.

Физическое представление поля _Folder в различных СУБД

Для начала давайте проанализируем, как именно платформа «1С:Предприятие» создает и заполняет колонку _Folder на уровне базы данных. В зависимости от используемой СУБД, тип данных этого поля и его физическое представление будут различаться:

  1. Microsoft SQL Server: Вопреки ожиданиям, платформа 1С традиционно не использует стандартный тип bit для хранения булевых значений. Вместо этого все логические поля (включая _Folder) создаются с типом данных BINARY(1). При этом группа справочника записывается как шестнадцатеричный ноль (0x00), что логически эквивалентно Ложь (FALSE). Обычный элемент записывается как шестнадцатеричная единица (0x01), что соответствует Истина (TRUE).
  2. PostgreSQL: В этой СУБД платформа использует стандартный системный тип boolean. Однако логика сохраняется: группы записываются как значение false (в консоли запросов отображается как символ f), а конечные элементы — как true (символ t).
  3. IBM DB2: Здесь применяется тип данных CHAR(1) FOR BIT DATA, где поведение полностью аналогично MS SQL Server.

Давайте посмотрим на практический пример. Если мы выполним прямой запрос к таблице справочника Номенклатуры (например, _Reference131) в PostgreSQL, чтобы сгруппировать записи по полю _Folder, мы увидим следующую картину:


SELECT COUNT(_idrref) AS Количество, _folder 
FROM _Reference131 
GROUP BY _folder;

Результат выполнения этого запроса покажет нам неожиданную статистику:


 count | _folder 
-------+---------
   180 | f
  7780 | t

Из этого результата видно, что 180 папок (групп) имеют значение false, а 7780 элементов — значение true. То есть на уровне базы данных признак «Это Группа» работает ровно наоборот.

Исторические причины инверсии: наследие 1С:Предприятия 7.7

Для того чтобы понять, откуда возникло это архитектурное решение, нам необходимо перенестись более чем на 20 лет назад, во времена господства платформы «1С:Предприятие 7.7». В версии 7.7 для хранения данных использовались файлы DBF (в файловом варианте) либо СУБД MS SQL Server 6.5/7.0/2000 (в клиент-серверном варианте).

В иерархических справочниках версии 7.7 за разделение групп и элементов отвечало числовое поле ISFOLDER (тип данных numeric(1,0) или tinyint). Оно могло принимать строго определенные значения:

Возникает закономерный вопрос: почему именно 1 и 2, а не наоборот? Ответ кроется в особенностях построения индексов и сортировки данных. Для пользователей крайне важно, чтобы в любом иерархическом списке (например, в справочнике товаров или контрагентов) папки всегда отображались на самом верху списка, а элементы располагались под ними. СУБД выполняет сортировку по умолчанию по возрастанию (ASC). При сортировке по полю ISFOLDER значения распределяются следующим образом: сначала идут записи со значением 1 (группы), а затем — со значением 2 (элементы). Таким образом, группы автоматически оказываются вверху списка без необходимости применения ресурсоемких операций обратной сортировки (DESC).

В те времена файловая система DBF обладала серьезными ограничениями производительности. Построение индексов с обратной сортировкой (по убыванию) либо требовало создания дополнительных сложных индексных файлов, либо сильно замедляло выборку данных. Разработчики платформы пошли по пути максимальной оптимизации: они спроектировали структуру хранения так, чтобы стандартный индекс по возрастанию работал на пользу интерфейса.

Когда в начале 2000-х годов создавалась платформа «1С:Предприятие 8.0», разработчики решили модернизировать типы данных. Логично было заменить числовое поле ISFOLDER на полноценный булев тип. Однако базовые принципы индексации в СУБД не изменились. При индексировании булева поля значение FALSE (бинарный ноль 0x00) всегда располагается в индексе раньше, чем TRUE (бинарная единица 0x01).

Чтобы сохранить проверенную временем схему, при которой группы гарантированно находятся в начале индекса и выводятся наверх списка при стандартной сортировке по возрастанию, было принято решение:

Это позволило унаследовать высокую скорость работы динамических списков и отчетов без усложнения логики индексирования СУБД.

Как платформа транслирует запросы «на лету»

Разработчикам прикладных решений в 1С нет необходимости постоянно держать в голове эту инверсию при написании кода во встроенном языке. Объектная модель 1С полностью абстрагирует разработчика от особенностей физического хранения. Метод ЭтоГруппа() объекта справочника или обращение к свойству ЭтоГруппа в запросах 1С работают в привычной человеческой логике: если объект является группой, возвращается Истина, если элементом — Ложь.

Давайте проанализируем, как транслятор запросов 1С преобразует наши запросы во встроенном языке в физические SQL-запросы к СУБД. Предположим, мы пишем следующий запрос на встроенном языке 1С:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Номенклатура.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |	Номенклатура.ЭтоГруппа = ИСТИНА";
Результат = Запрос.Выполнить();

Платформа перехватывает этот запрос и перед отправкой в СУБД выполняет логическое отрицание (инверсию). На уровне MS SQL Server мы получим следующий транслированный запрос:


SELECT
    T1._IDRRef AS Ссылка
FROM
    _Reference131 T1
WHERE
    T1._Folder = 0x00

Если же мы ищем обычные элементы, записывая условие ГДЕ Номенклатура.ЭтоГруппа = ЛОЖЬ, платформа сформирует SQL-запрос с условием WHERE T1._Folder = 0x01.

Многих разработчиков волнует вопрос производительности: не замедляет ли СУБД постоянное инвертирование условий? Спешим успокоить: современные оптимизаторы запросов в MS SQL Server, PostgreSQL и других СУБД работают чрезвычайно эффективно. Операция сравнения с константой (будь то 0x00 или 0x01) выполняется мгновенно. С точки зрения плана выполнения запроса конструкции ЭтоГруппа = Ложь и НЕ ЭтоГруппа абсолютно равноценны и одинаково эффективно используют имеющиеся индексы по полю _Folder.

Подводные камни при прямой работе с SQL и интеграции

Проблемы и недопонимания начинаются тогда, когда мы пытаемся обойти платформу и работать с базой данных 1С напрямую с помощью SQL-скриптов, внешних систем интеграции (SSIS, ADO, ETL-процессы) или систем аналитики (PowerBI, ClickHouse). В этих сценариях необходимо учитывать особенности хранения поля _Folder.

Рассмотрим основные сложности и способы их решения:

1. Чтение данных через внешние компоненты (ADO / OLE DB):

При чтении данных напрямую из MS SQL Server через библиотеки доступа к данным (например, ADODB в скриптах или внешних приложениях) поле типа BINARY(1) часто считывается не как привычное булево значение (типа Boolean), а как массив байтов (в COM-объектах это возвращается как COMSafeArray или тип byte[] в .NET). Это приводит к ошибкам примитивного сравнения в коде стороннего приложения.

Чтобы избежать этой проблемы, мы рекомендуем выполнять явное приведение типов на стороне СУБД еще на этапе выборки данных. Для этого можно использовать конструкцию CAST или CONVERT:


SELECT 
    _IDRRef, 
    _Description, 
    CAST(~_Folder AS bit) AS IsGroup 
FROM 
    _Reference131;

Обратите внимание на побитовое отрицание ~ перед именем поля _Folder в MS SQL Server. Оно позволяет перевернуть значение обратно в человеческую логику перед преобразованием в тип bit.

2. Использование условных выражений CASE:

Если вам нужно получить привычные 1 (для групп) и 0 (для элементов) в классическом SQL-запросе, наиболее безопасным и переносимым решением будет использование конструкции CASE WHEN. Этот подход отлично работает во всех поддерживаемых СУБД:


SELECT 
    _IDRRef,
    _Description,
    CASE WHEN _Folder = 0x00 THEN 1 ELSE 0 END AS IsGroup
FROM 
    _Reference131;

Для PostgreSQL данный запрос будет выглядеть аналогично, но с использованием булевых литералов:


SELECT 
    _idrref,
    _description,
    CASE WHEN _folder = FALSE THEN 1 ELSE 0 END AS is_group
FROM 
    _Reference131;

3. Проблемы прямой записи в базу данных (Direct INSERT/UPDATE):

Важное предупреждение: Любая прямая запись в таблицы базы данных 1С в обход платформы категорически не рекомендуется, так как это нарушает ссылочную целостность данных и может привести к непредсказуемому поведению системы. Однако, если вы разрабатываете механизмы миграции или отладки на копиях баз, вы должны помнить о строгом соответствии типов.

Попытка записать обычное число 0 или 1, либо стандартный булев тип в колонку BINARY(1) в MS SQL Server вызовет ошибку несовместимости типов. СУБД требует передачи строго двоичного представления байта. Запись должна выполняться с явным указанием шестнадцатеричных констант:


-- Правильная запись новой группы (устанавливаем _Folder в 0x00)
INSERT INTO _Reference131 (_IDRRef, _Folder, _Description) 
VALUES (0x80e300155d01010111e38dfbc6f1d111, 0x00, 'Новая папка товаров');

-- Правильная запись нового элемента (устанавливаем _Folder в 0x01)
INSERT INTO _Reference131 (_IDRRef, _Folder, _Description) 
VALUES (0x80e300155d01010111e38dfbc6f1d222, 0x01, 'Новый товар');

Резюме

Подводя итог, мы можем с уверенностью сказать, что инвертированное поведение реквизита ЭтоГруппа (колонки _Folder) в базах данных 1С — это не ошибка разработчиков и не архитектурный просчет, а осознанное оптимизационное решение. Оно берет свое начало из эпохи производительности файловых баз данных 1С 7.7 и перенесено в современную платформу 1С 8 для сохранения высокой скорости сортировки и оптимальной работы индексов СУБД.

При разработке стандартных решений на встроенном языке 1С мы можем полностью доверять платформе — она берет всю работу по трансляции и инвертированию условий на себя. Если же перед вами стоит задача построения прямых SQL-запросов или интеграции баз данных на низком уровне, теперь вы вооружены знаниями о внутренней кухне СУБД 1С и сможете легко обойти любые архитектурные подводные камни.

← На главную