> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Управление данными

> Управление данными для обсервабилити

export const Image = ({img, alt, size = "lg"}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} />
      </Frame>
    </div>;
};

Развертывания ClickHouse для обсервабилити неизбежно связаны с большими объёмами данных, которыми необходимо управлять. ClickHouse предлагает ряд возможностей для управления данными.

<Tip>
  **ClickStack поставляется с оптимизированной схемой по умолчанию**

  **ClickStack предоставляет готовые схемы для журналов, трассировки и метрик**, которые используют новейшие возможности ClickHouse (текстовые индексы для полнотекстового поиска и поиска по ключам в Map, материализованные столбцы и ALIAS-массивы для фильтрации при прямом чтении, поиск строк по номеру блока) и были протестированы бенчмарками, чтобы обеспечивать высокую производительность из коробки для нагрузок журналирования и трассировки. Используйте их как отправную точку для собственного проектирования.

  * Канонический DDL: [Таблицы и схемы, используемые ClickStack](/docs/ru/clickstack/ingesting-data/schemas).
  * Рецепты оптимизации: [Настройка производительности ClickStack](/docs/ru/clickstack/managing/performance-tuning). Многие рекомендации на этой странице (материализованные столбцы, индексы пропуска данных, выбор первичного ключа, проекции, materialized views) напрямую применимы и к конфигурации, которую вы собираете самостоятельно.
</Tip>

<div id="partitions">
  ## Партиции
</div>

Партиционирование в ClickHouse позволяет логически разделять данные на диске по столбцу или SQL-выражению. Благодаря такому логическому разделению с каждой партицией можно работать независимо, например удалять её. Это позволяет эффективно перемещать партиции, а значит и подмножества данных, между уровнями хранения по времени или [удалять устаревшие данные/эффективно удалять данные из кластера](/docs/ru/reference/statements/alter/partition).

Партиционирование задаётся для таблицы при её первоначальном определении с помощью условия `PARTITION BY`. Это условие может содержать SQL-выражение для любого столбца или набора столбцов, результат которого определяет, в какую партицию будет направлена строка.

<Image img="https://mintcdn.com/private-7c7dfe99/xE8TEsdF6028Tf3x/images/use-cases/observability/observability-14.webp?fit=max&auto=format&n=xE8TEsdF6028Tf3x&q=85&s=291ec5afa51f05bce8e8817d3999b9a5" alt="Партиции" size="md" width="1600" height="1077" data-path="images/use-cases/observability/observability-14.webp" />

Части данных логически связаны (через общий префикс имени папки) с каждой партицией на диске и могут запрашиваться изолированно. В примере ниже схема `otel_logs` по умолчанию использует партиционирование по дням с выражением `toDate(Timestamp)`. По мере вставки строк в ClickHouse это выражение вычисляется для каждой строки, и данные направляются в соответствующую партицию, если она уже существует (если строка для этого дня первая, партиция будет создана).

```sql theme={null}
CREATE TABLE default.otel_logs
(
...
)
ENGINE = MergeTree
PARTITION BY toDate(Timestamp)
ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId)
```

С партициями можно выполнять [ряд операций](/docs/ru/reference/statements/alter/partition), включая [резервные копии](/docs/ru/reference/statements/alter/partition#freeze-partition), [операции со столбцами](/docs/ru/reference/statements/alter/partition#clear-column-in-partition), мутации для [изменения](/docs/ru/reference/statements/alter/partition#update-in-partition)/[удаления](/docs/ru/reference/statements/alter/partition#delete-in-partition) данных по строкам) и [очистку индексов (например, вторичных индексов)](/docs/ru/reference/statements/alter/partition#clear-index-in-partition).

Например, предположим, что таблица `otel_logs` разбита на партиции по дням. Если она заполнена структурированным набором журнальных данных, она будет содержать данные за несколько дней:

```sql theme={null}
SELECT Timestamp::Date AS day,
         count() AS c
FROM otel_logs
GROUP BY day
ORDER BY c DESC
```

```response theme={null}
┌────────day─┬───────c─┐
│ 2019-01-22 │ 2333977 │
│ 2019-01-23 │ 2326694 │
│ 2019-01-26 │ 1986456 │
│ 2019-01-24 │ 1896255 │
│ 2019-01-25 │ 1821770 │
└────────────┴─────────┘

5 rows in set. Elapsed: 0.058 sec. Processed 10.37 million rows, 82.92 MB (177.96 million rows/s., 1.42 GB/s.)
Peak memory usage: 4.41 MiB.
```

Текущие партиции можно посмотреть с помощью простого запроса к системной таблице:

```sql theme={null}
SELECT DISTINCT partition
FROM system.parts
WHERE `table` = 'otel_logs'
```

```response theme={null}
┌─partition──┐
│ 2019-01-22 │
│ 2019-01-23 │
│ 2019-01-24 │
│ 2019-01-25 │
│ 2019-01-26 │
└────────────┘

5 rows in set. Elapsed: 0.005 sec.
```

У нас может быть и другая таблица — `otel_logs_archive`, которую мы используем для хранения более старых данных. Данные можно эффективно перемещать в эту таблицу по партициям (это лишь изменение метаданных).

```sql theme={null}
CREATE TABLE otel_logs_archive AS otel_logs
--move data to archive table
ALTER TABLE otel_logs
        (MOVE PARTITION tuple('2019-01-26') TO TABLE otel_logs_archive
--confirm data has been moved
SELECT
        Timestamp::Date AS day,
        count() AS c
FROM otel_logs
GROUP BY day
ORDER BY c DESC
```

```response theme={null}
┌────────day─┬───────c─┐
│ 2019-01-22 │ 2333977 │
│ 2019-01-23 │ 2326694 │
│ 2019-01-24 │ 1896255 │
│ 2019-01-25 │ 1821770 │
└────────────┴─────────┘

4 rows in set. Elapsed: 0.051 sec. Processed 8.38 million rows, 67.03 MB (163.52 million rows/s., 1.31 GB/s.)
потребление памяти: 4.40 MiB.
```

```sql theme={null}
SELECT Timestamp::Date AS day,
        count() AS c
FROM otel_logs_archive
GROUP BY day
ORDER BY c DESC
```

```response theme={null}
┌────────day─┬───────c─┐
│ 2019-01-26 │ 1986456 │
└────────────┴─────────┘

1 строка в наборе. Elapsed: 0.024 sec. Processed 1.99 million rows, 15.89 MB (83.86 million rows/s., 670.87 MB/s.)
потребление памяти: 4.99 MiB.
```

В отличие от других методов, для которых потребовались бы `INSERT INTO SELECT` и перезапись данных в новую целевую таблицу.

<Info>
  **Перемещение партиций**

  Для [перемещения партиций между таблицами](/docs/ru/reference/statements/alter/partition#move-partition-to-table) необходимо соблюдение нескольких условий; в частности, таблицы должны иметь одинаковые структуру, ключ партиционирования, первичный ключ и индексы/проекции. Подробные сведения о том, как указывать партиции в `ALTER` DDL, можно найти [здесь](/docs/ru/reference/statements/alter/partition#how-to-set-partition-expression).
</Info>

Кроме того, данные можно эффективно удалять на уровне партиций. Это значительно менее затратно по ресурсам, чем альтернативные методы (мутации или легковесные удаления), поэтому этому варианту следует отдавать предпочтение.

```sql theme={null}
ALTER TABLE otel_logs
        (DROP PARTITION tuple('2019-01-25'))

SELECT
        Timestamp::Date AS day,
        count() AS c
FROM otel_logs
GROUP BY day
ORDER BY c DESC
```

```response theme={null}
┌────────day─┬───────c─┐
│ 2019-01-22 │ 4667954 │
│ 2019-01-23 │ 4653388 │
│ 2019-01-24 │ 3792510 │
└────────────┴─────────┘
```

<Note>
  Эта возможность задействуется механизмом TTL при использовании настройки [`ttl_only_drop_parts=1`](/docs/ru/reference/settings/merge-tree-settings#ttl_only_drop_parts). Подробнее см. в разделе [Управление данными с помощью TTL](#data-management-with-ttl-time-to-live).
</Note>

<div id="applications">
  ### Применение
</div>

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

* **Многоуровневые архитектуры** - перемещение данных между уровнями хранения (см. [Уровни хранения](#storage-tiers)), что позволяет строить архитектуру «горячего» и «холодного» хранения.
* **Эффективное удаление** - когда данные достигают заданного TTL (см. [Управление данными с помощью TTL](#data-management-with-ttl-time-to-live))

Ниже мы подробно рассмотрим оба случая.

<div id="query-performance">
  ### Производительность запросов
</div>

Хотя партиции могут помочь повысить производительность запросов, это сильно зависит от характера доступа к данным. Если запросы затрагивают только несколько партиций (в идеале — одну), производительность может улучшиться. Обычно это имеет смысл только в том случае, если ключ партиционирования не входит в первичный ключ и фильтрация выполняется по нему. Однако запросы, которым нужно охватить много партиций, могут работать хуже, чем без партиционирования (поскольку частей может оказаться больше). Преимущество обращения к одной партиции будет еще менее заметным или вовсе исчезнет, если ключ партиционирования уже находится в начале первичного ключа. Партиционирование также можно использовать, чтобы [оптимизировать запросы GROUP BY](/docs/ru/reference/engines/table-engines/mergetree-family/custom-partitioning-key#group-by-optimisation-using-partition-key), если значения в каждой партиции уникальны. Однако в общем случае следует убедиться, что первичный ключ оптимизирован, и рассматривать партиционирование как метод оптимизации запросов только в исключительных случаях, когда характер доступа предполагает обращение к определенному предсказуемому подмножеству данных, например партиционирование по дням, когда большинство запросов выполняется по данным за последний день. [Здесь](https://medium.com/datadenys/using-partitions-in-clickhouse-3ea0decb89c4) приведен пример такого поведения.

<div id="data-management-with-ttl-time-to-live">
  ## Управление данными с помощью TTL (Time-to-live)
</div>

Time-to-Live (TTL) — крайне важная возможность в решениях для обсервабилити на базе ClickHouse, обеспечивающая эффективное хранение и управление данными, особенно с учетом того, что постоянно генерируются огромные объемы данных. Использование TTL в ClickHouse позволяет автоматически удалять устаревшие данные по истечении заданного срока, обеспечивая оптимальное использование хранилища и поддерживая производительность без ручного вмешательства. Эта возможность необходима для того, чтобы база данных оставалась компактной, затраты на хранение снижались, а запросы выполнялись быстро и эффективно за счет работы с наиболее актуальными и свежими данными. Кроме того, TTL помогает соблюдать политики хранения данных благодаря системному управлению их жизненным циклом, что повышает общую устойчивость и масштабируемость решения для обсервабилити.

TTL можно задавать в ClickHouse как на уровне таблицы, так и на уровне столбца.

<div id="table-level-ttl">
  ### TTL на уровне таблицы
</div>

Схема по умолчанию и для журналов, и для трассировок включает TTL, чтобы данные удалялись по истечении заданного срока. Это указывается в экспортере ClickHouse с помощью ключа `ttl`, например.

```yaml theme={null}
exporters:
 clickhouse:
   endpoint: tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1
   ttl: 72h
```

Этот синтаксис в настоящее время поддерживает [синтаксис Golang Duration](https://pkg.go.dev/time#ParseDuration). **Мы рекомендуем использовать `h` и следить за тем, чтобы значение соответствовало периоду партиционирования. Например, если таблица партиционируется по дням, убедитесь, что значение кратно количеству дней, например 24h, 48h, 72h.** Это автоматически обеспечит добавление в таблицу предложения `TTL`, например при `ttl: 96h`.

```sql theme={null}
PARTITION BY toDate(Timestamp)
ORDER BY (ServiceName, SpanName, toUnixTimestamp(Timestamp), TraceId)
TTL toDateTime(Timestamp) + toIntervalDay(4)
SETTINGS ttl_only_drop_parts = 1
```

По умолчанию данные с истекшим TTL удаляются, когда ClickHouse [объединяет части данных](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree#mergetree-data-storage). Когда ClickHouse обнаруживает, что срок TTL данных истек, он выполняет внеплановое слияние.

<Info>
  **TTL по расписанию**

  TTL применяется не сразу, а по расписанию, как указано выше. Настройка таблицы MergeTree `merge_with_ttl_timeout` задает минимальную задержку в секундах перед повторным выполнением слияния с delete TTL. Значение по умолчанию — 14400 секунд (4 часа). Но это лишь минимальная задержка: до запуска TTL-слияния может пройти больше времени. Если значение слишком мало, будет выполняться много внеплановых слияний, которые могут потреблять значительные ресурсы. Принудительно применить TTL можно командой `ALTER TABLE my_table MATERIALIZE TTL`.
</Info>

\*\*Важно: мы рекомендуем использовать настройку [`ttl_only_drop_parts=1`](/docs/ru/reference/settings/merge-tree-settings#ttl_only_drop_parts) \*\* (она применяется в схеме по умолчанию). Когда эта настройка включена, ClickHouse удаляет часть целиком, если в ней истекли все строки. Удаление частей целиком вместо частичной очистки строк с истекшим TTL (которая выполняется с помощью ресурсоемких мутаций при `ttl_only_drop_parts=0`) позволяет использовать меньшие значения `merge_with_ttl_timeout` и снижает влияние на производительность системы. Если данные партиционированы по той же единице, по которой выполняется истечение TTL, например по дням, части естественным образом будут содержать данные только из заданного интервала. Это гарантирует, что `ttl_only_drop_parts=1` можно будет применять эффективно.

<div id="column-level-ttl">
  ### TTL на уровне столбца
</div>

В приведённом выше примере срок хранения данных задаётся на уровне таблицы. Вы также можете настроить истечение срока хранения данных на уровне столбца. По мере старения данных это можно использовать для удаления столбцов, чья ценность при расследовании не оправдывает затраты ресурсов на их хранение. Например, мы рекомендуем сохранять столбец `Body` на случай, если будут добавлены новые динамические метаданные, которые не были извлечены во время вставки, например новая метка Kubernetes. Через некоторое время, например через 1 месяц, может стать очевидно, что эти дополнительные метаданные не приносят пользы, — и тогда хранение столбца `Body` теряет смысл.

Ниже показано, как удалить столбец `Body` через 30 дней.

```sql theme={null}
CREATE TABLE otel_logs_v2
(
        `Body` String TTL Timestamp + INTERVAL 30 DAY,
        `Timestamp` DateTime,
        ...
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)
```

<Note>
  Чтобы задать TTL на уровне столбца, пользователям нужно определить собственную схему. Это нельзя указать в OTel collector.
</Note>

<div id="recompressing-data">
  ## Повторное сжатие данных
</div>

Хотя для наборов данных обсервабилити мы обычно рекомендуем `ZSTD(1)`, вы можете поэкспериментировать с другими алгоритмами сжатия или более высокими уровнями сжатия, например `ZSTD(3)`. Помимо возможности указать это при создании схемы, сжатие можно настроить так, чтобы оно менялось по истечении определённого времени. Это может быть уместно, если кодек или алгоритм сжатия обеспечивает лучшее сжатие, но снижает производительность запросов. Такой компромисс может быть приемлем для старых данных, к которым обращаются реже, но не для свежих данных, которые чаще используются при расследованиях.

Пример показан ниже: вместо удаления данных через 4 дня мы начинаем сжимать их с помощью `ZSTD(3)`.

```sql theme={null}
CREATE TABLE default.otel_logs_v2
(
        `Body` String,
        `Timestamp` DateTime,
        `ServiceName` LowCardinality(String),
        `Status` UInt16,
        `RequestProtocol` LowCardinality(String),
        `RunTime` UInt32,
        `Size` UInt32,
        `UserAgent` String,
        `Referer` String,
        `RemoteUser` String,
        `RequestType` LowCardinality(String),
        `RequestPath` String,
        `RemoteAddress` IPv4,
        `RefererDomain` String,
        `RequestPage` String,
        `SeverityText` LowCardinality(String),
        `SeverityNumber` UInt8,
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)
TTL Timestamp + INTERVAL 4 DAY RECOMPRESS CODEC(ZSTD(3))
```

<Info>
  **Оцените производительность**

  Мы рекомендуем всегда оценивать, как разные уровни сжатия и алгоритмы влияют на производительность вставки и запросов. Например, дельта-кодеки могут быть полезны для сжатия временных меток. Однако если они входят в состав первичного ключа, производительность фильтрации может снизиться.
</Info>

Дополнительные сведения и примеры по настройке TTL можно найти [здесь](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-multiple-volumes). Примеры того, как TTL можно добавлять и изменять для таблиц и столбцов, можно найти [здесь](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-ttl). О том, как TTL позволяют создавать иерархии хранения, например архитектуры hot-warm, см. раздел [Уровни хранения](#storage-tiers).

<div id="storage-tiers">
  ## Уровни хранения
</div>

В ClickHouse можно создавать уровни хранения на разных дисках, например хранить горячие/недавние данные на SSD, а более старые — в S3. Такая архитектура позволяет использовать для старых данных более дешёвое хранилище, поскольку из-за их редкого использования при расследованиях к запросам к ним предъявляются менее строгие SLA.

<Info>
  **Не относится к ClickHouse Cloud**

  ClickHouse Cloud использует одну копию данных, размещённую в S3, с кэшами узлов на SSD. Поэтому уровни хранения в ClickHouse Cloud не требуются.
</Info>

Чтобы создать уровни хранения, сначала нужно создать диски, а затем на их основе определить политики хранения с томами, которые можно указать при создании таблицы. Данные могут автоматически перемещаться между дисками в зависимости от уровня заполнения, размера частей и приоритетов томов. Более подробную информацию можно найти [здесь](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-multiple-volumes).

Хотя данные можно вручную перемещать между дисками с помощью команды `ALTER TABLE MOVE PARTITION`, перемещением данных между томами также можно управлять с помощью TTL. Полный пример можно найти [здесь](/docs/ru/concepts/features/operations/delete/ttl#implementing-a-hotwarmcold-architecture).

<div id="managing-schema-changes">
  ## Управление изменениями схемы
</div>

Схемы Log и трассировок неизбежно будут меняться на протяжении жизненного цикла системы — например, когда пользователи начинают отслеживать новые системы с другими метаданными или метками подов. Если формировать данные в соответствии со схемой OTel и сохранять исходные данные событий в структурированном формате, схемы ClickHouse будут устойчивы к таким изменениям. Однако по мере появления новых метаданных и изменения шаблонов доступа к данным в запросах вам потребуется обновлять схемы, чтобы отразить эти изменения.

Чтобы избежать простоя при изменении схемы, у пользователей есть несколько вариантов, которые мы рассмотрим ниже.

<div id="use-default-values">
  ### Использование значений по умолчанию
</div>

Столбцы можно добавлять в схему с помощью [значений `DEFAULT`](/docs/ru/reference/statements/create/table#default). Указанное значение по умолчанию будет использоваться, если оно не задано при INSERT.

Изменения в схему можно внести до изменения логики преобразования в materialized view или конфигурации OTel collector, из-за которых начнётся отправка новых столбцов.

После изменения схемы можно перенастроить OTel collectors. Если пользователи применяют рекомендуемый процесс, описанный в ["Извлечение структуры с помощью SQL"](/docs/ru/guides/use-cases/observability/build-your-own/schema-design#extracting-structure-with-sql), при котором OTel collectors отправляют данные в движок таблицы Null, а materialized view отвечает за извлечение целевой схемы и отправку результатов в целевую таблицу для хранения, представление можно изменить с помощью [синтаксиса `ALTER TABLE ... MODIFY QUERY`](/docs/ru/reference/statements/alter/view). Предположим, у нас есть приведённая ниже целевая таблица и соответствующее ей materialized view (аналогичное тому, что используется в "Извлечение структуры с помощью SQL") для извлечения целевой схемы из структурированных журналов OTel:

```sql theme={null}
CREATE TABLE default.otel_logs_v2
(
        `Body` String,
        `Timestamp` DateTime,
        `ServiceName` LowCardinality(String),
        `Status` UInt16,
        `RequestProtocol` LowCardinality(String),
        `RunTime` UInt32,
        `UserAgent` String,
        `Referer` String,
        `RemoteUser` String,
        `RequestType` LowCardinality(String),
        `RequestPath` String,
        `RemoteAddress` IPv4,
        `RefererDomain` String,
        `RequestPage` String,
        `SeverityText` LowCardinality(String),
        `SeverityNumber` UInt8
)
ENGINE = MergeTree
ORDER BY (ServiceName, Timestamp)

CREATE MATERIALIZED VIEW otel_logs_mv TO otel_logs_v2 AS
SELECT
        Body,
        Timestamp::DateTime AS Timestamp,
        ServiceName,
        LogAttributes['status']::UInt16 AS Status,
        LogAttributes['request_protocol'] AS RequestProtocol,
        LogAttributes['run_time'] AS RunTime,
        LogAttributes['user_agent'] AS UserAgent,
        LogAttributes['referer'] AS Referer,
        LogAttributes['remote_user'] AS RemoteUser,
        LogAttributes['request_type'] AS RequestType,
        LogAttributes['request_path'] AS RequestPath,
        LogAttributes['remote_addr'] AS RemoteAddress,
        domain(LogAttributes['referer']) AS RefererDomain,
        path(LogAttributes['request_path']) AS RequestPage,
        multiIf(Status::UInt64 > 500, 'CRITICAL', Status::UInt64 > 400, 'ERROR', Status::UInt64 > 300, 'WARNING', 'INFO') AS SeverityText,
        multiIf(Status::UInt64 > 500, 20, Status::UInt64 > 400, 17, Status::UInt64 > 300, 13, 9) AS SeverityNumber
FROM otel_logs
```

Предположим, мы хотим извлечь новый столбец `Size` из `LogAttributes`. Мы можем добавить его в схему с помощью `ALTER TABLE`, указав значение по умолчанию:

```sql theme={null}
ALTER TABLE otel_logs_v2
        (ADD COLUMN `Size` UInt64 DEFAULT JSONExtractUInt(Body, 'size'))
```

В приведённом выше примере мы указываем значение по умолчанию как ключ `size` в `LogAttributes` (если его нет, будет 0). Это означает, что запросы, обращающиеся к этому столбцу для строк, в которые это значение не было вставлено, должны обращаться к Map и, следовательно, будут выполняться медленнее. Мы также могли бы легко указать здесь константу, например 0, снизив стоимость последующих запросов к строкам, в которых это значение отсутствует. Запрос к этой таблице показывает, что значение заполняется из Map, как и ожидалось:

```sql theme={null}
SELECT Size
FROM otel_logs_v2
LIMIT 5
```

```response theme={null}
┌──Size─┐
│ 30577 │
│  5667 │
│  5379 │
│  1696 │
│ 41483 │
└───────┘

5 строк в наборе. Затрачено: 0.012 сек.
```

Чтобы это значение добавлялось ко всем будущим данным, мы можем изменить нашу materialized view с помощью синтаксиса `ALTER TABLE`, как показано ниже:

```sql theme={null}
ALTER TABLE otel_logs_mv
        MODIFY QUERY
SELECT
        Body,
        Timestamp::DateTime AS Timestamp,
        ServiceName,
        LogAttributes['status']::UInt16 AS Status,
        LogAttributes['request_protocol'] AS RequestProtocol,
        LogAttributes['run_time'] AS RunTime,
        LogAttributes['size'] AS Size,
        LogAttributes['user_agent'] AS UserAgent,
        LogAttributes['referer'] AS Referer,
        LogAttributes['remote_user'] AS RemoteUser,
        LogAttributes['request_type'] AS RequestType,
        LogAttributes['request_path'] AS RequestPath,
        LogAttributes['remote_addr'] AS RemoteAddress,
        domain(LogAttributes['referer']) AS RefererDomain,
        path(LogAttributes['request_path']) AS RequestPage,
        multiIf(Status::UInt64 > 500, 'CRITICAL', Status::UInt64 > 400, 'ERROR', Status::UInt64 > 300,                 'WARNING', 'INFO') AS SeverityText,
        multiIf(Status::UInt64 > 500, 20, Status::UInt64 > 400, 17, Status::UInt64 > 300, 13, 9) AS SeverityNumber
FROM otel_logs
```

В последующих строках столбец `Size` будет заполняться при вставке.

<div id="create-new-tables">
  ### Создание новых таблиц
</div>

В качестве альтернативы описанному выше процессу можно просто создать новую целевую таблицу с новой схемой. Затем любые materialized view можно настроить на использование новой таблицы с помощью приведённой выше команды `ALTER TABLE MODIFY QUERY.` При таком подходе можно версионировать таблицы, например `otel_logs_v3`.

При таком подходе пользователям придётся выполнять запросы к нескольким таблицам. Чтобы выполнять запросы сразу к нескольким таблицам, можно использовать [функцию `merge`](/docs/ru/reference/functions/table-functions/merge), которая поддерживает шаблоны с подстановочными знаками в имени таблицы. Ниже это показано на примере запроса к версиям v2 и v3 таблицы `otel_logs`:

```sql theme={null}
SELECT Status, count() AS c
FROM merge('otel_logs_v[2|3]')
GROUP BY Status
ORDER BY c DESC
LIMIT 5
```

```response theme={null}
┌─Status─┬────────c─┐
│   200  │ 38319300 │
│   304  │  1360912 │
│   302  │   799340 │
│   404  │   420044 │
│   301  │   270212 │
└────────┴──────────┘

5 rows in set. Elapsed: 0.137 sec. Processed 41.46 million rows, 82.92 MB (302.43 million rows/s., 604.85 MB/s.)
```

Если вы хотите избежать использования функции `merge` и предоставить конечным пользователям таблицу, объединяющую несколько таблиц, можно использовать [движок таблицы Merge](/docs/ru/reference/engines/table-engines/special/merge). Ниже мы покажем, как это сделать:

```sql theme={null}
CREATE TABLE otel_logs_merged
ENGINE = Merge('default', 'otel_logs_v[2|3]')

SELECT Status, count() AS c
FROM otel_logs_merged
GROUP BY Status
ORDER BY c DESC
LIMIT 5
```

```response theme={null}
┌─Status─┬────────c─┐
│   200  │ 38319300 │
│   304  │  1360912 │
│   302  │   799340 │
│   404  │   420044 │
│   301  │   270212 │
└────────┴──────────┘

5 rows in set. Elapsed: 0.073 sec. Processed 41.46 million rows, 82.92 MB (565.43 million rows/s., 1.13 GB/s.)
```

Это можно обновлять каждый раз при добавлении новой таблицы с помощью синтаксиса `EXCHANGE`. Например, чтобы добавить таблицу v4, можно создать новую таблицу и атомарно поменять её местами с предыдущей версией.

```sql theme={null}
CREATE TABLE otel_logs_merged_temp
ENGINE = Merge('default', 'otel_logs_v[2|3|4]')

EXCHANGE TABLE otel_logs_merged_temp AND otel_logs_merged

SELECT Status, count() AS c
FROM otel_logs_merged
GROUP BY Status
ORDER BY c DESC
LIMIT 5
```

```response theme={null}
┌─Status─┬────────c─┐
│   200  │ 39259996 │
│   304  │  1378564 │
│   302  │   820118 │
│   404  │   429220 │
│   301  │   276960 │
└────────┴──────────┘

5 rows in set. Elapsed: 0.068 sec. Processed 42.46 million rows, 84.92 MB (620.45 million rows/s., 1.24 GB/s.)
```
