# Как получить внешний ID клиента в аналитических экспортах

В аналитических экспортах все данные привязаны к внутреннему идентификатору клиента `mindboxId`. Чтобы использовать в аналитике на своей стороне внешний идентификатор (`externalId`), свяжите `mindboxId` с `externalId` и присоедините результат к нужным аналитическим таблицам.

## Перед началом

Убедитесь, что у вас есть:

* настроенная интеграция «Данные для аналитики» и доступ к аналитическим экспортам (delta-sharing);
* ключ шифрования — находится в разделе **Интеграции → Точка интеграции «Данные для аналитики» → Ключ шифрования**.

## Шаг 1. Выберите тип идентификатора

Внешние идентификаторы — это [дополнительные поля](https://help.mindbox.ru/docs/additional-data) с типом `TechnicalIdentity` или `SocialNetworkIdentity`. Список типов и их `internalId` доступны в таблице [справочника дополнительных полей](https://developers.mindbox.ru/docs/customfields-reference):

```sql
SELECT internalId, systemName, name, typeSystemName
FROM CustomFieldKinds
WHERE typeSystemName IN ('TechnicalIdentity', 'SocialNetworkIdentity');
```

Выберите нужный тип и запомните его `internalId` — это значение для фильтра `customfieldKindInternalId` в следующих шагах. Определить нужный тип можно по полям `systemName` или `name`, проверив их в интерфейсе Mindbox.

### Шаг 2. Получите актуальную связку mindboxId → externalId

Таблица [`exports.CDP.ExternalCustomerIdsHistory`](https://developers.mindbox.ru/docs/external-ids-change-history) хранит версии изменений внешних идентификаторов клиентов. Чтобы получить актуальное состояние, возьмите последнюю версию каждой записи и отбросьте удаленные:

```sql
SELECT *
FROM (
    SELECT *
    FROM ExternalCustomerIdsHistory
    ORDER BY _database_version DESC
    LIMIT 1 BY id, customfieldKindInternalId
)
WHERE _isDeleted = false
  AND customfieldKindInternalId = <нужный_internalId>;
```

В результате:

* `unmergedCustomerId` — `mindboxId` клиента;
* `value` — внешний идентификатор клиента (зашифрован, расшифровка — в шаге 4).

:::warning
`customfieldKindInternalId` — это **тип** идентификатора (ссылка на дополнительное поле), а не идентификатор клиента. Идентификатор клиента — `unmergedCustomerId`, внешний идентификатор — `value`.
:::

### Шаг 3. Учтите объединение клиентов

В Mindbox клиенты с одинаковыми контактными данными могут объединяться. После объединения в истории остаются записи по всем исходным клиентам (каждая со своим `value`), но с одним итоговым `unmergedCustomerId`. Из-за этого на один `mindboxId` может приходиться несколько значений `value`.

Для корректной связки:

* исходный `mindboxId` каждого клиента до объединения хранится в `historicalCustomerId` в таблице [`exports.CDP.ExternalCustomerIdsHistory`](https://developers.mindbox.ru/docs/external-ids-change-history);
* полную карту объединений `unmergedCustomerId → mergedCustomerId` можно получить с помощью [экспорта объединений клиентов](https://developers.mindbox.ru/docs/customer-merges).

Если нужен ровно один последний внешний ID на клиента, после среза по версии возьмите последнюю запись по дате создания исходного слепка:

```sql
SELECT *
FROM (
    SELECT *
    FROM ExternalCustomerIdsHistory
    ORDER BY _database_version DESC
    LIMIT 1 BY id, customfieldKindInternalId
)
WHERE _isDeleted = false
  AND customfieldKindInternalId = <нужный_internalId>
ORDER BY historicalCustomerCreationDateTimeUtc DESC
LIMIT 1 BY unmergedCustomerId;
```

:::info
Количество уникальных `unmergedCustomerId` и уникальных `value` может не совпадать — это нормальное поведение при объединении клиентов.
:::

### Шаг 4. Расшифруйте значение `value`

Значение внешнего идентификатора в поле `value` зашифровано. Для расшифровки используйте ключ шифрования из интерфейса Mindbox: **Интеграции → Точка интеграции «Данные для аналитики» → Ключ шифрования**.

Алгоритм и готовый пример на Python описаны в разделе [«Зашифрованные данные»](https://developers.mindbox.ru/docs/how-to-read-data#зашифрованные-данные).

## Пример: внешний ID рядом со статусом рассылки

Задача: к каждой записи статуса рассылки добавить внешний идентификатор клиента.

Логика запроса:

* Получите [статусы рассылок](https://developers.mindbox.ru/docs/mailing-statuses).
* Приведите `unmergedCustomerId` к актуальному `mergedCustomerId` через [экспорт объединений клиентов](https://developers.mindbox.ru/docs/customer-merges).
* Присоедините актуальный срез внешних идентификаторов по актуальному ID клиента.

```sql
SELECT
    sendings.messageId,
    sendings.messageStatusId,
    sendings.mailingStatusSystemName,
    sendings.dateTimeUTC,
    sendings.unmergedCustomerId,
    merged.mergedCustomerId,
    sendings.mailingInternalId,
    sendings.mailingVariantNum,
    sendings.mailingLink,
    sendings.mailingSourceEntityType,
    sendings.mailingSourceEntityId,
    sendings.notSentSystemName,
    sendings.notDeliveredReasonSystemName,
    ext_ids.customfieldKindInternalId,
    ext_ids.value AS externalCustomerId
FROM (
    -- Статусы рассылок (без удаленных)
    SELECT *
    FROM CustomerMessagesStatuses
    WHERE (empty(_isDeleted) OR _isDeleted = false)
) AS sendings
-- Объединения клиентов: приведение к актуальному mergedCustomerId
LEFT JOIN (
    SELECT *
    FROM (
        SELECT *
        FROM MergedCustomers
        ORDER BY _rowversion_ts DESC
        LIMIT 1 BY unmergedCustomerId, mergedCustomerId
    )
    WHERE (empty(_isDeleted) OR _isDeleted = false)
) AS merged
    ON sendings.unmergedCustomerId = merged.unmergedCustomerId
-- Внешние идентификаторы по актуальному ID клиента
LEFT JOIN (
    SELECT *
    FROM (
        SELECT *
        FROM ExternalCustomerIdsHistory
        ORDER BY _database_version DESC
        LIMIT 1 BY id, customfieldKindInternalId
    )
    WHERE _isDeleted = false
        AND customfieldKindInternalId = <нужный_internalId>
    ORDER BY historicalCustomerCreationDateTimeUtc DESC
    LIMIT 1 BY unmergedCustomerId, customfieldKindInternalId
) AS ext_ids
    ON ext_ids.unmergedCustomerId = coalesce(merged.mergedCustomerId, sendings.unmergedCustomerId);
```

:::warning
Пример построен на сырых данных аналитических экспортов (прямое чтение таблиц delta-sharing). Если на вашей стороне есть промежуточные преобразования перед записью в DWH, адаптируйте запрос под вашу модель данных. Описания полей и типов всех таблиц — в ["Описании схемы данных"](https://developers.mindbox.ru/docs/analytics-data-schema).
:::

## Связанные материалы

* [Описание схемы данных](https://developers.mindbox.ru/docs/analytics-data-schema) — единый справочник полей всех таблиц экспорта.
* [Как прочитать данные](https://developers.mindbox.ru/docs/how-to-read-data) — delta-sharing, шифрование, Python-коннектор.
* [Как периодически обновлять данные](https://developers.mindbox.ru/docs/periodic-data-updates) — витрины с актуальным состоянием.
* [Экспорт данных для аналитики](https://help.mindbox.ru/docs/как-настроить-экспорт-данных-для-аналитики) — настройка интеграции.
