> For the complete documentation index, see [llms.txt](https://navixy.com/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://navixy.com/docs/analytics/ru/iot-query/schema-overview/bronze-layer/bdr.md).

# Схема BDR

BDR организует устройства и бизнес-сущности в активы с Настраиваемыми полями, связями и журналами аудита. Просмотрите определение схемы и описания основных таблиц

{% hint style="warning" %}
**Эта схема в настоящее время находится в разработке.** BDR (Business Data Repository) организует данные вокруг активов: единой комбинации устройства и бизнес-сущности, например транспортного средства и его водителя или устройства и его датчиков. IoT Query читает данные из BDR, чтобы предоставлять их для аналитики. Если вы заинтересованы в раннем доступе или у вас есть вопросы об этой функциональности, пожалуйста, свяжитесь с <iotquery@navixy.com>.
{% endhint %}

BDR представляет собой комплексную платформу для управления организационными структурами, участниками, устройствами, активами и их связями в мультиарендных средах. Построенная на PostgreSQL 18 с помощью `ltree` и `pg_trgm` расширений, схема поддерживает иерархические организации, определения настраиваемых полей для любого типа сущности, управление доступом на основе ролей участников с ограничениями на уровне объекта, оптимистическую блокировку для одновременного редактирования и полные журналы аудита с отслеживанием изменений на уровне полей. Все сущности можно расширять без изменений схемы, локализовать для международных внедрений и связывать с помощью гибких полиморфных связей.

Схема решает сложные задачи управления данными, включая иерархии активов автопарка на разных организационных уровнях, мультиарендные SaaS-платформы, требующие изоляции данных, операции, ориентированные на соблюдение требований, с подробными требованиями к аудиту, и системы, которым нужны динамические модели данных, адаптируемые с помощью настраиваемых полей вместо миграций базы данных.

{% hint style="info" %}
Интерактивная диаграмма схемы данных BDR доступна на **dbdiagram.io**: <https://dbdiagram.io/d/Navixy-Repo-data-schema-68ad788c1e7a611967a0930e>
{% endhint %}

Подробности схемы BDR приведены ниже.

{% code title="Схема данных BDR" expandable="true" %}

```sql
// ============================================
// Схема BDR (Business Data Repository) - путь клиента
// PostgreSQL 18 с расширениями ltree, pg_trgm
// Версия: 2.2
// ============================================

// ============================================
// БАЗОВЫЕ СПРАВОЧНЫЕ ТАБЛИЦЫ (иерархия ci_base - наследование одной таблицы)
// ============================================

Table ci_base {
  id uuid [primary key, note: '4-символьный код типа сущности, встроенный в байты 5-8 UUID, извлекается через uuid_type_code()']
  code text [not null, note: 'Машиночитаемый код, должен начинаться с буквы']
  title_en text [not null, note: 'Регистронезависимая сортировка (ICU)']
  description_en text
  order int [not null, default: 0]
  is_system boolean [not null, default: false]
  discriminator text [not null, note: 'Автоматически заполняется из id через trg_ci_set_discriminator']
  catalog_id uuid [note: 'Родительский каталог для элементов пользовательского каталога. NULL для системных каталогов и самих определений каталогов']
  organization_id uuid
  parent_id uuid
  path ltree [note: 'Автоматически поддерживается триггером, когда is_hierarchical = true']
  is_hierarchical boolean [not null, default: false]
  extra jsonb [not null, default: `{}`]
  version int [not null, default: 1, note: 'Оптимистичная блокировка']
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz

  indexes {
    (id) [name: 'idx_ci_active', note: 'partial: WHERE deleted_at IS NULL']
    (parent_id) [name: 'idx_ci_parent']
    (path) [type: gist, name: 'idx_ci_path_gist']
    (catalog_id) [name: 'idx_ci_catalog']
    (organization_id) [name: 'idx_ci_org']
    (discriminator) [name: 'idx_ci_discriminator']
    (title_en) [name: 'idx_ci_title_en']
    (discriminator, code) [unique, name: 'uq_ci_code_discriminator']
    (catalog_id, code) [unique, name: 'uq_ci_code_catalog']
  }
}

Table ci_module {
  id uuid [primary key]
}

Table ci_catalog_category {
  id uuid [primary key]
}

Таблица ci_country {
  id uuid [primary key]
}

Таблица ci_role {
  id uuid [primary key]
}

Table ci_entity_type {
  id uuid [primary key]
  uuid_discriminator char(4) [not null, note: '4-символьный код, встроенный в UUID сущностей']
  is_customizable boolean [not null, default: true, note: 'Поддерживают ли сущности этого типа Настраиваемые поля']
}

Таблица ci_device_status {
  id uuid [primary key]
}

Таблица ci_permission_scope {
  id uuid [primary key]
  module_id uuid [not null]
  entity_type_id uuid [not null]
  category text [not null]
}

Таблица ci_device_vendor {
  id uuid [primary key, note: 'Производители/поставщики устройств']
}

Таблица ci_device_model {
  id uuid [primary key]
  vendor_id uuid [not null]
}

Table ci_device_type {
  id uuid [primary key]
}

Таблица ci_asset_type {
  id uuid [primary key, note: 'Иерархический через ci_base parent_id/path']
}

Таблица ci_geo_object_type {
  id uuid [primary key]
}

Таблица ci_schedule_type {
  id uuid [primary key]
}

Таблица ci_asset_group_type {
  id uuid [primary key]
}

Таблица asset_group_type_to_asset_type_relation {
  id uuid [primary key]
  group_type_id uuid [not null]
  id типа Актив uuid [не равно null]
  max_items int [note: 'Макс. количество активов этого типа в Группа. NULL = без ограничений']

  indexes {
    (group_type_id) [name: 'idx_agt_at_rel_group']
    (asset_type_id) [имя: 'idx_agt_at_rel_asset']
    (group_type_id, asset_type_id) [уникальный]
  }
}

Таблица ci_device_relation_type {
  id uuid [primary key]
}

Таблица ci_Тег {
  id uuid [primary key]
  entity_type_id uuid [note: 'Применимый тип сущности. NULL = универсальный тег']
}

Table ci_user_catalog_item {
  id uuid [primary key, note: 'Элементы пользовательских каталогов']
}

Table ci_catalog {
  id uuid [primary key, note: 'Определения каталога. Каталоги сами являются элементами каталога']
  organization_id uuid [not null]
  module_id uuid [not null]
  category_id uuid
  fields_schema jsonb [note: 'JSON Schema для проверки item.extra и интерфейса']

  indexes {
    (organization_id) [name: 'idx_ci_catalog_org']
    (module_id) [name: 'idx_ci_catalog_module']
  }
}

Table ci_custom_field_definition {
  id uuid [primary key]
  owner_catalog_item_id uuid [not null, примечание: 'Владелец: entity_type (system) или конкретный тип, например asset_type']
  target_entity_type_id uuid [not null]
  field_type text [not null, note: 'STRING, TEXT, NUMBER, BOOLEAN, DATE, DATETIME, GEOJSON, SCHEDULE, OPTIONS, DEVICE, REFERENCE, CATALOG, Тег']
  is_required boolean [not null, default: false]
  params jsonb [note: 'Параметры, специфичные для типа (min/max, options, ref, etc.)']

  indexes {
    (owner_catalog_item_id) [name: 'idx_ci_cfd_owner']
    (target_entity_type_id) [name: 'idx_ci_cfd_target']
  }
}

// ============================================
// ОСНОВНЫЕ СУЩНОСТИ
// ============================================

Организация таблицы {
  id uuid [primary key]
  parent_id uuid
  путь ltree
  текст кода [not null]
  title_en text [not null]
  external_id text [примечание: 'Идентификатор внешней системы для интеграций']
  is_active boolean [not null, default: true]
  is_dealer boolean [not null, default: false, note: 'Может ли организация создавать дочерние организации']
  version int [not null, default: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (parent_id) [name: 'idx_org_parent']
    (path) [type: gist, name: 'idx_org_path_gist']
    (code) [unique, name: 'uq_org_code']
    (external_id) [name: 'idx_org_external_id']
    (title_en) [name: 'idx_org_title', note: 'Исходный DDL указывает на несуществующий столбец title, исправлено здесь на title_en']
  }
}

Таблица actor {
  id uuid [primary key, note: 'Абстрактная сущность для пользователей и интеграций. Основа для ACL']
  actor_type text [not null, note: 'USER, INTEGRATION, or SYSTEM']
  version int [not null, default: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (actor_type) [name: 'idx_actor_type']
  }
}

Table user {
  id uuid [primary key]
  identity_provider text [not null, default: 'keycloak']
  identity_provider_id uuid [not null]
  full_name text [not null]
  external_id text
  is_active boolean [not null, default: true]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (identity_provider, identity_provider_id) [unique, name: 'uq_user_idp']
    (external_id) [name: 'idx_user_external_id']
  }
}

Table member {
  id uuid [primary key, note: 'Членство пользователя в организации']
  user_id uuid [not null]
  organization_id uuid [not null]
  is_active boolean [not null, default: true]
  custom_fields_data jsonb [not null, default: `{}`, note: 'Настраиваемые поля участника (должность, отдел и т. д.)']
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  assigned_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (user_id) [name: 'idx_member_user']
    (organization_id) [name: 'idx_member_org']
    (user_id, organization_id) [unique, name: 'uq_member_user_org']
  }
}

Таблица integration {
  id uuid [primary key, note: 'Актор интеграции внешней системы']
  name text [not null]
  credential_ref text [примечание: 'Ссылка на учетные данные в защищенном хранилище']
  is_active boolean [not null, default: true]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid
}

// ============================================
// КОНТРОЛЬ ДОСТУПА (ACL)
// ============================================

Table actor_role {
  id uuid [primary key]
  actor_id uuid [not null]
  role_id uuid [not null]
  assigned_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  assigned_by uuid
  expire_date timestamptz [note: 'NULL = бессрочно']

  indexes {
    (actor_id) [name: 'idx_actor_role_actor']
    (role_id) [name: 'idx_actor_role_role']
    (actor_id, role_id) [уникальный]
  }
}

Table acl_role_permission {
  id uuid [primary key]
  role_id uuid [not null]
  permission_scope_id uuid [not null]
  target_entity_id uuid [примечание: 'Конкретная сущность. NULL = все сущности типа. Полиморфная, без FK']
  actions int [not null, note: 'Битовая маска: READ=1, CREATE=2, UPDATE=4, DELETE=8']
  granted_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  granted_by uuid

  indexes {
    (role_id) [name: 'idx_acl_role_perm_role']
    (permission_scope_id) [name: 'idx_acl_role_perm_scope']
    (role_id, permission_scope_id, target_entity_id) [уникальный]
  }
}

Table acl_user_scope {
  id uuid [primary key, note: 'Фильтр белого списка. Пусто = полный доступ по ролям. Заполнено = пересечение с ролями']
  actor_id uuid [not null]
  permission_scope_id uuid [not null]
  target_entity_id uuid [not null]
  actions int [not null]

  indexes {
    (actor_id, permission_scope_id) [name: 'idx_acl_user_scope_actor']
    (actor_id, permission_scope_id, target_entity_id) [unique]
  }
}

// ============================================
// БИЗНЕС-СУЩНОСТИ
// ============================================

Table device {
  id uuid [primary key]
  organization_id uuid [not null]
  device_type_id uuid [not null]
  model_id uuid [note: 'Необязательно']
  status_id uuid [not null]
  title text [not null]
  custom_fields_data jsonb [not null, default: `{}`]
  version int [not null, default: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [name: 'idx_device_org']
    (device_type_id) [name: 'idx_device_type']
    (model_id) [name: 'idx_device_model']
    (status_id) [name: 'idx_device_status']
    (title) [type: gin, name: 'idx_device_title_trgm', note: 'gin_trgm_ops для нечеткого поиска']
  }
}

Table device_identifier {
  id uuid [primary key]
  device_id uuid [not null]
  type text [not null, note: 'UUID, IMEI, MEID_HEX, MEID_DEC, MAC_ADDRESS, SERIAL_NUMBER, CUSTOM']
  текст значения [not null]
  namespace text [note: 'Область уникальности. NULL = global']
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]

  indexes {
    (device_id) [name: 'idx_device_identifier_device']
    (value) [name: 'idx_device_identifier_value']
    (type, value) [unique, name: 'uq_device_identifier_global', note: 'partial: WHERE namespace IS NULL']
    (type, value, namespace) [unique, name: 'uq_device_identifier_namespaced', note: 'partial: WHERE namespace IS NOT NULL']
  }
}

Таблица device_relation {
  id uuid [primary key]
  first_id uuid [not null]
  second_id uuid [not null]
  relation_type_id uuid [not null, note: 'Проверочное ограничение: first_id <> second_id']

  indexes {
    (first_id) [name: 'idx_device_relation_first']
    (second_id) [name: 'idx_device_relation_second']
    (first_id, second_id, relation_type_id) [unique]
  }
}

Таблица inventory {
  id uuid [primary key]
  organization_id uuid [not null]
  title text [not null]
  текст кода [not null]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [name: 'idx_inventory_org']
    (organization_id, code) [unique, name: 'uq_inventory_org_code']
  }
}

Таблица device_inventory_relation {
  id uuid [primary key]
  device_id uuid [not null]
  inventory_id uuid [not null]
  assigned_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  assigned_by uuid
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (device_id) [unique, name: 'uq_device_inventory_active', note: 'частичный: WHERE deleted_at IS NULL - одна активная инвентаризация на устройство']
    (inventory_id) [name: 'idx_device_inventory_rel_inventory']
  }
}

Таблица Актив {
  id uuid [primary key]
  organization_id uuid [not null]
  id типа Актив uuid [не равно null]
  title text [not null]
  custom_fields_data jsonb [not null, default: `{}`]
  version int [not null, default: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [name: 'idx_asset_org']
    (asset_type_id) [name: 'idx_asset_type']
    (заголовок) [type: gin, name: 'idx_asset_title_trgm']
  }
}

Table актив_группа {
  id uuid [primary key]
  organization_id uuid [not null]
  group_type_id uuid [not null]
  title_en text [not null]
  цвет текста
  version int [not null, default: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [имя: 'idx_asset_group_org']
    (group_type_id) [имя: 'idx_asset_group_type']
  }
}

Таблица asset_group_item {
  id uuid [primary key]
  group_id uuid [not null]
  asset_id uuid [not null]
  attached_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  detached_at timestamptz [note: 'NULL = в данный момент прикреплено']

  indexes {
    (group_id) [name: 'idx_asset_group_item_group']
    (asset_id) [name: 'idx_asset_group_item_asset']
    (Группа_id, Актив_id, detached_at) [уникальное]
  }
}

Таблица geo_object {
  id uuid [первичный ключ, note: 'геозоны, ориентиры, маршруты']
  organization_id uuid [not null]
  geo_object_type_id uuid [not null]
  title text [not null]
  custom_fields_data jsonb [not null, default: `{}`, note: 'Включает геометрию geojson']
  version int [not null, default: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [name: 'idx_geo_object_org']
    (geo_object_type_id) [name: 'idx_geo_object_type']
    (заголовок) [type: gin, name: 'idx_geo_object_title_trgm']
  }
}

Расписание таблицы {
  id uuid [primary key]
  organization_id uuid [not null]
  schedule_type_id uuid [not null]
  title text [not null]
  custom_fields_data jsonb [not null, default: `{}`, note: 'Сами данные расписания хранятся здесь']
  version int [not null, default: 1]
  created_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  updated_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]
  deleted_at timestamptz
  deleted_by uuid

  indexes {
    (organization_id) [name: 'idx_schedule_org']
    (schedule_type_id) [name: 'idx_schedule_type']
    (title) [type: gin, name: 'idx_schedule_title_trgm']
  }
}

// ============================================
// ЛОКАЛИЗАЦИЯ
// ============================================

Таблица i18n_text {
  entity_id uuid [pk]
  field_code text [pk]
  locale text [pk]
  текст значения [not null]

  indexes {
    (entity_id) [name: 'idx_i18n_entity']
    (locale) [name: 'idx_i18n_locale']
    (value) [type: gin, name: 'idx_i18n_text_trgm']
  }
}

// ============================================
// НАСТРАИВАЕМЫЕ ПОЛЯ - ЗНАЧЕНИЯ (таблицы кэша, по одной на field_type)
// ============================================

Таблица custom_field_value_text {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  текст значения [not null]

  indexes {
    (field_definition_id, value) [name: 'idx_cfv_text_value']
  }
}

Table custom_field_value_number {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value numeric [not null]
}

Table custom_field_value_boolean {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value boolean [not null]
}

Table custom_field_value_date {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value date [not null]
}

Table custom_field_value_datetime {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value timestamptz [not null]
}

Таблица custom_field_value_geojson {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value jsonb [not null]
}

Таблица custom_field_value_schedule {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  value jsonb [not null]
}

Таблица custom_field_value_option {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_item_id uuid [not null, note: 'Ссылается на ci_user_catalog_item']
}

Таблица custom_field_value_device {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_device_id uuid [not null]
}

Таблица custom_field_value_entity {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_entity_id uuid [not null, note: 'Полиморфная ссылка, без FK']
}

Таблица custom_field_value_catalog {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_item_id uuid [not null, note: 'Ссылается на ci_base']
}

Таблица custom_field_value_tag {
  entity_id uuid [pk]
  field_definition_id uuid [pk]
  value_index smallint [pk, default: 0]
  ref_tag_id uuid [not null]
}

// ============================================
// АУДИТ
// ============================================

Enum source_type {
  ВЕБ
  МОБИЛЬНЫЙ
  API
  ВНУТРЕННИЙ
  ИНТЕГРАЦИЯ
}

Table audit_event {
  id uuid [not null]
  organization_id uuid
  event_category text [not null, note: 'auth or domain']
  actor_id uuid
  ip_address inet
  текст user_agent
  source_type source_type [not null, default: 'API']
  source_name text [примечание: 'Название исходного приложения']
  trace_id uuid [note: 'Идентификатор распределенной трассировки для корреляции журналов']
  aggregate_type text [note: 'Тип сущности: устройство, Актив, пользователь и т. д.']
  aggregate_id uuid [note: 'Полиморфный, без внешнего ключа']
  event_type text [not null, примечание: 'CREATED, UPDATED, DELETED, RESTORED, ROLE_ASSIGNED, и т.д.']
  event_data jsonb [примечание: 'Полезная нагрузка с дельтой changed_fields']
  occurred_at timestamptz [not null, default: `CURRENT_TIMESTAMP`]

  Примечание: 'Partitioned BY RANGE (occurred_at), ежемесячные разделы, автоматически создаются через create_audit_partition_if_needed()'

  indexes {
    (id, occurred_at) [pk]
    (actor_id, occurred_at) [name: 'idx_audit_event_actor']
    (aggregate_type, aggregate_id, occurred_at) [name: 'idx_audit_event_aggregate']
    (event_category, occurred_at) [name: 'idx_audit_event_category']
    (event_type, occurred_at) [name: 'idx_audit_event_type']
    (organization_id, occurred_at) [name: 'idx_audit_event_org']
    (trace_id) [name: 'idx_audit_event_trace']
  }
}

// ============================================
// СВЯЗИ
// ============================================

Ссылка: ci_base.catalog_id > ci_catalog.id
См.: ci_base.organization_id > organization.id
См.: ci_base.parent_id > ci_base.id

См.: ci_module.id - ci_base.id
Ссылка: ci_catalog_category.id - ci_base.id
Ссылка: ci_country.id - ci_base.id
Ссылка: ci_role.id - ci_base.id
Ссылка: ci_entity_type.id - ci_base.id
Ссылка: ci_device_status.id - ci_base.id
См.: ci_permission_scope.id - ci_base.id
См.: ci_device_vendor.id - ci_base.id
Ref: ci_device_model.id - ci_base.id
См.: ci_device_type.id - ci_base.id
Ссылка: ci_asset_type.id - ci_base.id
Ссылка: ci_geo_object_type.id - ci_base.id
Ссылка: ci_schedule_type.id - ci_base.id
Ссылка: ci_asset_group_type.id - ci_base.id
См.: ci_device_relation_type.id - ci_base.id
Ссылка: ci_tag.id - ci_base.id
Ссылка: ci_user_catalog_item.id - ci_base.id
Ссылка: ci_catalog.id - ci_base.id
Ссылка: ci_custom_field_definition.id - ci_base.id

Ref: ci_device_model.vendor_id > ci_device_vendor.id
См.: ci_permission_scope.module_id > ci_module.id
См.: ci_permission_scope.entity_type_id > ci_entity_type.id
Ссылка: asset_group_type_to_asset_type_relation.group_type_id > ci_asset_group_type.id
Ref: asset_group_type_to_asset_type_relation.asset_type_id > ci_asset_type.id
Ссылка: ci_tag.entity_type_id > ci_entity_type.id
См.: ci_catalog.organization_id > organization.id
Ссылка: ci_catalog.module_id > ci_module.id
См.: ci_catalog.category_id > ci_catalog_category.id
Ссылка: ci_custom_field_definition.owner_catalog_item_id > ci_base.id
См.: ci_custom_field_definition.target_entity_type_id > ci_entity_type.id

Ссылка: organization.parent_id > organization.id
Ссылка: organization.deleted_by > actor.id

Ссылка: actor.deleted_by > actor.id

Ссылка: user.id - actor.id
См.: user.deleted_by > actor.id

Ссылка: member.user_id > user.id
Ссылка: member.organization_id > organization.id
См.: member.deleted_by > actor.id

См.: integration.id - actor.id
См.: integration.deleted_by > actor.id

См.: actor_role.actor_id > actor.id
См.: actor_role.role_id > ci_role.id
Ссылка: actor_role.assigned_by > actor.id

Ссылка: acl_role_permission.role_id > ci_role.id
Ссылка: acl_role_permission.permission_scope_id > ci_permission_scope.id
Ссылка: acl_role_permission.granted_by > actor.id

См.: acl_user_scope.actor_id > actor.id
См.: acl_user_scope.permission_scope_id > ci_permission_scope.id

Ссылка: device.organization_id > organization.id
Ссылка: device.device_type_id > ci_device_type.id
См.: device.model_id > ci_device_model.id
См.: device.status_id > ci_device_status.id
См.: device.deleted_by > actor.id

См.: device_identifier.device_id > device.id

Ссылка: device_relation.first_id > device.id
Ссылка: device_relation.second_id > device.id
См.: device_relation.relation_type_id > ci_device_relation_type.id

Ссылка: inventory.organization_id > organization.id
Ссылка: inventory.deleted_by > actor.id

См.: device_inventory_relation.device_id > device.id
См.: device_inventory_relation.inventory_id > inventory.id
См.: device_inventory_relation.assigned_by > actor.id
Ссылка: device_inventory_relation.deleted_by > actor.id

Ссылка: Актив.organization_id > organization.id
См.: Актив.asset_type_id > ci_asset_type.id
Ссылка: asset.deleted_by > actor.id

Ссылка: asset_group.organization_id > organization.id
Ссылка: asset_group.group_type_id > ci_asset_group_type.id
Ссылка: asset_group.deleted_by > actor.id

Ссылка: asset_group_item.group_id > asset_group.id
Ссылка: asset_group_item.asset_id > asset.id

Ссылка: geo_object.organization_id > organization.id
Ссылка: geo_object.geo_object_type_id > ci_geo_object_type.id
Ссылка: geo_object.deleted_by > actor.id

Ссылка: schedule.organization_id > organization.id
Ссылка: schedule.schedule_type_id > ci_schedule_type.id
Ссылка: schedule.deleted_by > actor.id

Ссылка: custom_field_value_text.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_number.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_boolean.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_date.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_datetime.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_geojson.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_schedule.field_definition_id > ci_custom_field_definition.id
См.: custom_field_value_option.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_option.ref_item_id > ci_user_catalog_item.id
Ссылка: custom_field_value_device.field_definition_id > ci_custom_field_definition.id
См.: custom_field_value_device.ref_device_id > device.id
См.: custom_field_value_entity.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_catalog.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_catalog.ref_item_id > ci_base.id
Ссылка: custom_field_value_tag.field_definition_id > ci_custom_field_definition.id
Ссылка: custom_field_value_tag.ref_tag_id > ci_tag.id

Ссылка: audit_event.organization_id > organization.id
См.: audit_event.actor_id > actor.id
```

{% endcode %}

## Частота обновления

Данные в BDR синхронизируются в реальном времени с исходными системами. Обновления происходят немедленно по мере изменений, а журналы аудита фиксируют все изменения для соблюдения требований и исторического анализа.

## `ci_base`

BDR использует **Наследование одной таблицы** паттерн для всех справочных данных через `ci_base` таблицу. Такое решение объединяет системные словари, классификации и пользовательские справочные элементы в одну единую структуру, обеспечивая согласованность и гибкость во всей схеме.

**Архитектура:**

Этот `ci_base` таблица служит основой для всех справочных данных, используя `дискриминатор` поле для определения конкретного типа ссылки. У каждого типа ссылки есть соответствующая таблица (например `ci_device_type`, `ci_asset_type`) который имеет то же `id` как `ci_base`, создавая типобезопасную иерархию наследования. Эта `дискриминатор` не задается вручную. Триггер автоматически выводит его из 4-символьного кода типа, встроенного в UUID сущности, и прерывает вставку, если код типа UUID не распознан.

Иерархии (например, вложенные типы Активов или Теги) обрабатываются непосредственно на `ci_base` через собственный `parent_id`/`path` столбцы, а не через отдельные таблицы категорий. Один `is_hierarchical` флаг включает это для каждого элемента каталога.

**Как бизнес-сущности связаны с ci\_base:**

Все бизнес-сущности в BDR ссылаются на `ci_base` подтипы, чтобы определить их классификацию и поведение:

* `organization` и `user`/`участник` являются *не* указываются через `ci_base`, поскольку организации и пользователи являются самостоятельными основными сущностями и не классифицируются элементом каталога
* `устройство` → ссылается на `ci_device_type`, при необходимости `ci_device_model` (который сам ссылается на `ci_device_vendor`), и `ci_device_status`
* `Актив` → ссылается на `ci_asset_type`
* `Актив_Группа` → ссылается на `ci_asset_group_type`, с ограничением допустимых типов элементов через `asset_group_type_to_asset_type_relation`
* `гео_объект` → ссылается на `ci_geo_object_type`
* `расписание` → ссылается на `ci_schedule_type`
* `инвентарь` не имеет собственного типа catalog-item

**Категории типов ссылок:**

| Категория                        | Таблицы                                                                                                              | Назначение                                                                                               |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| **Системная конфигурация**       | `ci_module`, `ci_country`, `ci_role`                                                                                 | Определите системные модули, географические ориентиры и роли пользователей                               |
| **Определения типов сущностей**  | `ci_entity_type`, `ci_device_type`, `ci_asset_type`, `ci_geo_object_type`, `ci_schedule_type`, `ci_asset_group_type` | Классифицируйте все бизнес-сущности по типу                                                              |
| **Каталог устройств**            | `ci_device_vendor`, `ci_device_model`, `ci_device_status`                                                            | Описывайте устройства по производителю, модели и текущему состоянию                                      |
| **Контроль доступа**             | `ci_permission_scope`                                                                                                | Определите, какие разрешения можно выдавать (связано с `ci_module` и `ci_entity_type`)                   |
| **Связи**                        | `ci_device_relation_type`                                                                                            | Определите типы связей между устройствами (ведущий-ведомый, резервный и т. д.)                           |
| **Категоризация**                | `ci_tag`, `ci_catalog_category`, `ci_user_catalog_item`                                                              | Включите гибкое тегирование и каталоги, определяемые пользователем                                       |
| **Каталог / Настраиваемые поля** | `ci_catalog`, `ci_custom_field_definition`                                                                           | Каталог каталогов и метаданные пользовательских полей, и то и другое смоделировано как элементы каталога |

<details>

<summary><strong>Example query patterns</strong></summary>

```sql
-- Get all device types for an organization (system + custom)
SELECT cb.id, cb.code, cb.title_en, cb.is_system
FROM bdr.ci_base cb
JOIN bdr.ci_device_type dt ON dt.id = cb.id
WHERE cb.discriminator = 'device_type'
  AND (cb.is_system = true OR cb.organization_id = $org_id)
  AND cb.deleted_at IS NULL;

-- Get device models with their vendor
SELECT
  cb.code as model_code,
  cb.title_en as model_name,
  vendor_cb.title_en as vendor_name
FROM bdr.ci_base cb
JOIN bdr.ci_device_model dm ON dm.id = cb.id
JOIN bdr.ci_device_vendor v ON v.id = dm.vendor_id
JOIN bdr.ci_base vendor_cb ON vendor_cb.id = v.id
WHERE cb.discriminator = 'device_model'
  AND cb.deleted_at IS NULL;

-- Получить иерархическую структуру тегов
SELECT cb.id, cb.code, cb.title_en, cb.path, cb.parent_id
FROM bdr.ci_base cb
ОБЪЕДИНИТЬ bdr.ci_tag t ON t.id = cb.id
ГДЕ cb.discriminator = 'tag'
  AND cb.deleted_at IS NULL
ORDER BY cb.path;
```

</details>

## Основные таблицы по категориям

Таблицы в BDR организованы по функциональным категориям. Приведенные ниже описания кратко суммируют наиболее важные таблицы по их бизнес-назначению.

<details>

<summary><code>organization</code></summary>

**Назначение:** Иерархическое управление организацией

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code>, <code>parent_id</code>, <code>path</code>, <code>code</code>, <code>title_en</code>, <code>is_active</code>, <code>is_dealer</code>, <code>version</code>, <code>deleted_at</code></td></tr><tr><td><strong>Индексация</strong></td><td>индекс GiST по <code>path</code> для иерархических запросов, уникальный индекс по <code>code</code>, индекс по <code>parent_id</code></td></tr><tr><td><strong>Особые примечания</strong></td><td>Использует ltree для многоуровневых иерархий. <code>is_dealer</code> предоставляет право создавать дочерние организации. <code>version</code> поддерживает оптимистическую блокировку</td></tr></tbody></table>

</details>

<details>

<summary><code>актор</code></summary>

**Назначение:** Абстракция, общая для каждого принципала ACL: пользователей, интеграций и будущих системных акторов

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code>, <code>actor_type</code> (<code>ПОЛЬЗОВАТЕЛЬ</code>, <code>ИНТЕГРАЦИЯ</code>, <code>СИСТЕМА</code>), <code>version</code></td></tr><tr><td><strong>Особые примечания</strong></td><td>Все таблицы ACL (<code>роль_актера</code>, <code>acl_Роль_permission</code>, <code>acl_user_scope</code>) и каждый <code>deleted_by</code> столбец выключения зажигания <code>actor_id</code>, не <code>user_id</code>, так что интеграции могут также иметь роли и удалять записи</td></tr></tbody></table>

</details>

<details>

<summary><code>user</code> / <code>участник</code></summary>

**Назначение:** Учетные записи пользователей и их членства в организациях

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>user</code>: <code>id</code> (совместно с <code>актор</code>), <code>identity_provider</code>, <code>identity_provider_id</code>, <code>full_name</code>. <code>участник</code>: <code>user_id</code>, <code>organization_id</code>, <code>custom_fields_data</code></td></tr><tr><td><strong>Индексация</strong></td><td>Уникальный индекс по (<code>identity_provider</code>, <code>identity_provider_id</code>). Уникальный индекс по (<code>user_id</code>, <code>organization_id</code>) в <code>участник</code></td></tr><tr><td><strong>Особые примечания</strong></td><td>Членство пользователя в организации — это отдельная строка в <code>участник</code>, поэтому один пользователь может принадлежать нескольким организациям. Пользователи проходят аутентификацию через внешний поставщик удостоверений (по умолчанию Keycloak)</td></tr></tbody></table>

</details>

<details>

<summary><code>интеграция</code></summary>

**Назначение:** Внешние системы, взаимодействующие с платформой (например, через Управление API ключами)

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code> (совместно с <code>актор</code>), <code>имя</code>, <code>credential_ref</code>, <code>is_active</code></td></tr><tr><td><strong>Особые примечания</strong></td><td><code>credential_ref</code> указывает на защищенное внешнее хранилище, поэтому учетные данные не хранятся в этой таблице</td></tr></tbody></table>

</details>

<details>

<summary><code>устройство</code> / <code>device_identifier</code></summary>

**Назначение:** Физические устройства мониторинга и их аппаратные идентификаторы

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>устройство</code>: <code>id</code>, <code>organization_id</code>, <code>device_type_id</code>, <code>model_id</code>, <code>status_id</code>, <code>title</code>, <code>custom_fields_data</code>, <code>version</code>. <code>device_identifier</code>: <code>device_id</code>, <code>тип</code>, <code>value</code>, <code>namespace</code></td></tr><tr><td><strong>Индексация</strong></td><td>GIN-триграммный индекс по <code>title</code> для нечеткого поиска. Уникальный индекс по (<code>тип</code>, <code>value</code>) для глобальных идентификаторов или (<code>тип</code>, <code>value</code>, <code>namespace</code>) при использовании пространства имен</td></tr><tr><td><strong>Особые примечания</strong></td><td>Устройство может содержать несколько идентификаторов (IMEI, MAC, серийный номер и т. д.) вместо одного столбца идентификатора оборудования. Настраиваемые поля находятся непосредственно в <code>custom_fields_data</code> в строке устройства</td></tr></tbody></table>

</details>

<details>

<summary><code>Актив</code></summary>

**Назначение:** Физические или виртуальные активы

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code>, <code>organization_id</code>, <code>asset_type_id</code>, <code>title</code>, <code>custom_fields_data</code>, <code>version</code></td></tr><tr><td><strong>Индексация</strong></td><td>Индексы по <code>organization_id</code> и <code>asset_type_id</code>. триграммный индекс GIN по <code>title</code></td></tr><tr><td><strong>Особые примечания</strong></td><td>Настраиваемые поля хранятся непосредственно в строке, а не через отдельную таблицу настраиваемой сущности</td></tr></tbody></table>

</details>

<details>

<summary><code>инвентарь</code> / <code>device_inventory_relation</code></summary>

**Назначение:** Инвентарные и складские записи, а также назначение устройства к инвентарю

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>инвентарь</code>: <code>id</code>, <code>organization_id</code>, <code>title</code>, <code>code</code>. <code>device_inventory_relation</code>: <code>device_id</code>, <code>inventory_id</code>, <code>assigned_at</code>, <code>deleted_at</code></td></tr><tr><td><strong>Индексация</strong></td><td>Уникальный индекс по (<code>organization_id</code>, <code>code</code>). Частичный уникальный индекс гарантирует, что у устройства может быть не более одного активного назначения инвентаря</td></tr><tr><td><strong>Особые примечания</strong></td><td>Назначение поддерживает мягкое удаление, поэтому история инвентаря устройств сохраняется</td></tr></tbody></table>

</details>

<details>

<summary><code>гео_объект</code></summary>

**Назначение:** Геозоны, точки интереса и маршруты

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code>, <code>organization_id</code>, <code>geo_object_type_id</code>, <code>title</code>, <code>custom_fields_data</code></td></tr><tr><td><strong>Особые примечания</strong></td><td>Геометрия (ранее отдельный <code>geojson</code> столбец) теперь хранится как пользовательское поле внутри <code>custom_fields_data</code></td></tr></tbody></table>

</details>

<details>

<summary><code>расписание</code></summary>

**Назначение:** Повторно используемые расписания, на которые ссылаются другие сущности

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code>, <code>organization_id</code>, <code>schedule_type_id</code>, <code>title</code>, <code>custom_fields_data</code></td></tr><tr><td><strong>Особые примечания</strong></td><td>Сами данные расписания (правила повторения, временные окна) хранятся в <code>custom_fields_data</code></td></tr></tbody></table>

</details>

<details>

<summary><code>Актив_Группа</code></summary>

**Назначение:** Группировка активов с историческим мониторингом

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code>, <code>organization_id</code>, <code>group_type_id</code>, <code>title_en</code>, <code>цвет</code></td></tr><tr><td><strong>Связи</strong></td><td><code>FROM bdr.asset_group AS ag JOIN bdr.asset_group_item AS agi ON agi.group_id = ag.id JOIN bdr.asset AS a ON a.id = agi.asset_id WHERE agi.detached_at IS NULL</code></td></tr><tr><td><strong>Особые примечания</strong></td><td>Членство на основе времени через <code>Актив_Группа_элемент</code>, запрашивайте текущих участников с помощью <code>WHERE detached_at IS NULL</code>. Разрешенные типы участников для каждого типа Группа ограничиваются через <code>asset_group_type_to_asset_type_relation</code></td></tr></tbody></table>

</details>

<details>

<summary><code>ci_custom_field_definition</code></summary>

**Назначение:** Определения пользовательских полей и метаданные, представленные как элемент каталога

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code> (совместно с <code>ci_base</code>), <code>owner_catalog_item_id</code>, <code>target_entity_type_id</code>, <code>field_type</code>, <code>is_required</code>, <code>params</code></td></tr><tr><td><strong>Содержимое</strong></td><td>12 типов полей: <code>STRING</code>, <code>TEXT</code>, <code>NUMBER</code>, <code>BOOLEAN</code>, <code>DATE</code>, <code>DATETIME</code>, <code>GeoJSON</code>, <code>Расписание</code>, <code>Опции</code>, <code>Устройство</code>, <code>Справочник</code>, <code>Каталог</code>, <code>Тег</code></td></tr><tr><td><strong>Особые примечания</strong></td><td>Значения хранятся как источник истины (<code>custom_fields_data</code> JSONB в сущности-владельце) и, в зависимости от типа, в отдельной <code>custom_field_value_*</code> таблица кеша для фильтрации/сортировки. <code>Опции</code> является оберткой для <code>Каталог</code>, и каждый <code>Опции</code> поле получает свой собственный скрытый каталог пользователей под капотом</td></tr></tbody></table>

</details>

<details>

<summary><code>acl_Роль_permission</code></summary>

**Назначение:** Управление разрешениями на основе Роль

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code>, <code>идентификатор роли</code>, <code>permission_scope_id</code>, <code>target_entity_id</code>, <code>действия</code></td></tr><tr><td><strong>Содержимое</strong></td><td>Битовая маска действий (READ=1, CREATE=2, UPDATE=4, DELETE=8), права, специфичные для цели, или для всего типа сущности</td></tr><tr><td><strong>Связи</strong></td><td><code>FROM bdr.actor_role AS ar JOIN bdr.acl_role_permission AS rp ON rp.role_id = ar.role_id WHERE ar.actor_id = $actor_id</code></td></tr><tr><td><strong>Особые примечания</strong></td><td>Эффективные разрешения = разрешения Роль ∩ <code>acl_user_scope</code>. актера <code>acl_user_scope</code> является фильтром белого списка: пустое значение означает неограниченный доступ к Роли, заполненное означает, что доступ ограничен перечисленными объектами</td></tr></tbody></table>

</details>

<details>

<summary><code>audit_event</code></summary>

**Назначение:** Единый журнал аудита для всех изменений системы

<table><thead><tr><th width="139">Атрибут</th><th>Подробности</th></tr></thead><tbody><tr><td><strong>Ключевые поля</strong></td><td><code>id</code>, <code>organization_id</code>, <code>event_category</code>, <code>actor_id</code>, <code>source_type</code>, <code>trace_id</code>, <code>aggregate_type</code>, <code>aggregate_id</code>, <code>event_type</code>, <code>event_data</code>, <code>occurred_at</code></td></tr><tr><td><strong>Индексация</strong></td><td>Индексы по (<code>actor_id</code>, <code>occurred_at</code>), (<code>aggregate_type</code>, <code>aggregate_id</code>, <code>occurred_at</code>), (<code>event_category</code>, <code>occurred_at</code>), (<code>organization_id</code>, <code>occurred_at</code>)</td></tr><tr><td><strong>Особые примечания</strong></td><td>Физически разделена по <code>occurred_at</code> (ежемесячные диапазонные разделы, создаются автоматически). <code>event_category</code> является <code>auth</code> (LOGIN, LOGOUT, FAILED_LOGIN, PASSWORD_RESET, SESSION_EXPIRED) или <code>domain</code> (СОЗДАНО, ОБНОВЛЕНО, УДАЛЕНО, ВОССТАНОВЛЕНО, НАЗНАЧЕНА РОЛЬ, ОТОЗВАНА РОЛЬ, ПРЕДОСТАВЛЕНО РАЗРЕШЕНИЕ, ОТОЗВАНО РАЗРЕШЕНИЕ, СВЯЗАНО, РАЗЪЕДИНЕНО, ПРИКРЕПЛЕНО, ОТКРЕПЛЕНО). <code>source_type</code> и <code>trace_id</code> поддерживают распределенную трассировку, а таблица хранит дельты изменений на уровне полей в <code>event_data</code> JSONB</td></tr></tbody></table>

</details>

## Связи данных

BDR реализует сложные шаблоны связей для гибкого моделирования данных:

**Иерархические структуры**

* Организации используют пути ltree для эффективных запросов по дереву
* Справочные элементы (`ci_base`) поддерживают необязательные иерархии через свои собственные `parent_id`/`path`, ограничено `is_hierarchical`
* Автоматическое Техобслуживание пути через триггеры базы данных

**Схемы наследования**

* Наследование ID: `ci_base` → таблицы справочного типа (`ci_device_type`, `ci_asset_type`, и т. д.), и `актор` → `user` и `интеграция`
* Настраиваемые поля привязываются к каждой сущности через `custom_fields_data` столбец JSONB непосредственно в каждой бизнес-таблице (`устройство`, `Актив`, `гео_объект`, `расписание`), а не через общую базовую таблицу сущности
* Определение типа через `дискриминатор` поле в `ci_base`, автоматически получаемый из UUID сущности

**Оптимистичная блокировка**

Бизнес-сущности и `ci_base` строки содержат `version` целое число. Обновления используют `SET version = version + 1 WHERE id = $id AND version = $expected_version`. Ноль затронутых строк указывает на конфликт версий, позволяя клиентам обнаруживать и разрешать одновременные правки без блокировок на уровне базы данных. `audit_event` и другие таблицы с режимом только добавления или массовых операций не используют этот шаблон.

**Полиморфные связи**

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

* `acl_role_permission.target_entity_id` и `acl_user_scope.target_entity_id` → любая бизнес-сущность
* `audit_event.aggregate_id` → любая бизнес-сущность, в паре с `aggregate_type`
* `custom_field_value_entity.ref_entity_id` → любая бизнес-сущность

Эти связи проверяются на уровне приложения.

## Дополнительная информация

### Проверка данных

BDR обеспечивает целостность данных с помощью нескольких механизмов:

**Ограничения базы данных**

* Ограничения UNIQUE с поддержкой мягкого удаления (частичные индексы WHERE `deleted_at` IS NULL)
* Ограничения CHECK (например, `device_relation` обеспечивает `first_id` ≠ `second_id`, и `ci_base.code` должно начинаться с буквы)
* ограничения NOT NULL для обязательных полей
* значения DEFAULT для временных меток, логических значений и столбцов JSONB

**Проверка на уровне приложения**

* Проверка типа сущности для полиморфных ссылок
* Проверка каталога для ссылок на пользовательские поля
* Проверка типа пользовательского поля
* Проверка версии при обновлении с оптимистической блокировкой

### Оптимизация запросов

Таблицы организованы с использованием определенных стратегий индексации:

**Стандартные индексы:**

* Все внешние ключи имеют отдельные индексы
* Временные индексы по `created_at`, `updated_at`, `deleted_at`
* Составные индексы для часто соединяемых столбцов

**Специализированные индексы:**

* GiST-индексы по путям ltree для иерархических запросов
* Частичные уникальные индексы, поддерживающие soft delete
* GIN триграмм (`pg_trgm`) индексы для нечеткого поиска по `title`/`title_en` и переведенный текст
* Индексы значений пользовательских полей для фильтрации и сортировки
* Индексы событий аудита по времени + сущности для эффективного поиска

**Соображения по производительности:**

* Рекомендуется пул соединений (PgBouncer)
* Регулярное обслуживание VACUUM для больших таблиц
* `audit_event` физически разделена по месяцам, поэтому старые партиции можно отсоединять или архивировать независимо
* Материализованные представления для сложных расчетов контроля доступа


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://navixy.com/docs/analytics/ru/iot-query/schema-overview/bronze-layer/bdr.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
