Каждый разработчик, который хоть раз сталкивался с необходимостью написания прямых запросов к базе данных «1С:Предприятие» или занимался интеграцией 1С со сторонними системами на уровне СУБД, обращал внимание на странную особенность. Логика хранения большинства булевых реквизитов кажется абсолютно прозрачной: признак проведения документа Проведен (поле _Posted) принимает значение TRUE (или 0x01), если документ проведен, а признак пометки на удаление ПометкаУдаления (поле _Marked) равен TRUE, если объект помечен на удаление. Однако, когда дело доходит до иерархических справочников и реквизита ЭтоГруппа (поле _Folder), логика внезапно переворачивается с ног на голову.
В этой статье мы подробно разберем, почему для групп справочника в СУБД хранится значение FALSE (или бинарный ноль 0x00), а для обычных элементов — значение TRUE (бинарная единица 0x01). Мы погрузимся в историю развития платформы, проанализируем низкоуровневые механизмы работы СУБД и выясним, какие подводные камни ожидают разработчика при интеграции и как их успешно обойти.
Для начала давайте проанализируем, как именно платформа «1С:Предприятие» создает и заполняет колонку _Folder на уровне базы данных. В зависимости от используемой СУБД, тип данных этого поля и его физическое представление будут различаться:
bit для хранения булевых значений. Вместо этого все логические поля (включая _Folder) создаются с типом данных BINARY(1). При этом группа справочника записывается как шестнадцатеричный ноль (0x00), что логически эквивалентно Ложь (FALSE). Обычный элемент записывается как шестнадцатеричная единица (0x01), что соответствует Истина (TRUE).boolean. Однако логика сохраняется: группы записываются как значение false (в консоли запросов отображается как символ f), а конечные элементы — как true (символ t).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. То есть на уровне базы данных признак «Это Группа» работает ровно наоборот.
Для того чтобы понять, откуда возникло это архитектурное решение, нам необходимо перенестись более чем на 20 лет назад, во времена господства платформы «1С:Предприятие 7.7». В версии 7.7 для хранения данных использовались файлы DBF (в файловом варианте) либо СУБД MS SQL Server 6.5/7.0/2000 (в клиент-серверном варианте).
В иерархических справочниках версии 7.7 за разделение групп и элементов отвечало числовое поле ISFOLDER (тип данных numeric(1,0) или tinyint). Оно могло принимать строго определенные значения:
1 — для групп (папок) справочника;2 — для элементов справочника.Возникает закономерный вопрос: почему именно 1 и 2, а не наоборот? Ответ кроется в особенностях построения индексов и сортировки данных. Для пользователей крайне важно, чтобы в любом иерархическом списке (например, в справочнике товаров или контрагентов) папки всегда отображались на самом верху списка, а элементы располагались под ними. СУБД выполняет сортировку по умолчанию по возрастанию (ASC). При сортировке по полю ISFOLDER значения распределяются следующим образом: сначала идут записи со значением 1 (группы), а затем — со значением 2 (элементы). Таким образом, группы автоматически оказываются вверху списка без необходимости применения ресурсоемких операций обратной сортировки (DESC).
В те времена файловая система DBF обладала серьезными ограничениями производительности. Построение индексов с обратной сортировкой (по убыванию) либо требовало создания дополнительных сложных индексных файлов, либо сильно замедляло выборку данных. Разработчики платформы пошли по пути максимальной оптимизации: они спроектировали структуру хранения так, чтобы стандартный индекс по возрастанию работал на пользу интерфейса.
Когда в начале 2000-х годов создавалась платформа «1С:Предприятие 8.0», разработчики решили модернизировать типы данных. Логично было заменить числовое поле ISFOLDER на полноценный булев тип. Однако базовые принципы индексации в СУБД не изменились. При индексировании булева поля значение FALSE (бинарный ноль 0x00) всегда располагается в индексе раньше, чем TRUE (бинарная единица 0x01).
Чтобы сохранить проверенную временем схему, при которой группы гарантированно находятся в начале индекса и выводятся наверх списка при стандартной сортировке по возрастанию, было принято решение:
FALSE (так как оно меньше и идет первым);TRUE.Это позволило унаследовать высокую скорость работы динамических списков и отчетов без усложнения логики индексирования СУБД.
Разработчикам прикладных решений в 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.
Проблемы и недопонимания начинаются тогда, когда мы пытаемся обойти платформу и работать с базой данных 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С и сможете легко обойти любые архитектурные подводные камни.