> ## 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.

> Позволяет быстро записывать состояния объектов, которые постоянно меняются, и удалять старые состояния объектов в фоновом режиме.

# Движок таблицы VersionedCollapsingMergeTree

Этот движок:

* Позволяет быстро записывать состояния объектов, которые постоянно меняются.
* Удаляет старые состояния объектов в фоновом режиме. Это значительно сокращает объем хранимых данных.

Подробности см. в разделе [схлопывание](#table_engines_versionedcollapsingmergetree).

Движок наследуется от [MergeTree](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree) и добавляет логику схлопывания строк в алгоритм слияния частей данных. `VersionedCollapsingMergeTree` служит той же цели, что и [CollapsingMergeTree](/docs/ru/reference/engines/table-engines/mergetree-family/collapsingmergetree), но использует другой алгоритм схлопывания, который позволяет вставлять данные в любом порядке с использованием нескольких потоков. В частности, столбец `Version` помогает корректно схлопывать строки, даже если они вставлены в неправильном порядке. В отличие от него, `CollapsingMergeTree` допускает только строго последовательную вставку.

<div id="creating-a-table">
  ## Создание таблицы
</div>

```sql theme={null}
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
) ENGINE = VersionedCollapsingMergeTree(sign, version)
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
```

Описание параметров запроса см. в разделе [описание запроса](/docs/ru/reference/statements/create/table).

<div id="engine-parameters">
  ### Параметры движка
</div>

```sql theme={null}
VersionedCollapsingMergeTree(sign, version)
```

| Параметр  | Описание                                                                          | Тип                                                                                                                                                                                                                                                                                      |
| --------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sign`    | Имя столбца с типом строки: `1` — это строка состояния, `-1` — это строка отмены. | [`Int8`](/docs/ru/reference/data-types/int-uint)                                                                                                                                                                                                                                              |
| `version` | Имя столбца с версией состояния объекта.                                          | [`Int*`](/docs/ru/reference/data-types/int-uint), [`UInt*`](/docs/ru/reference/data-types/int-uint), [`Date`](/docs/ru/reference/data-types/date), [`Date32`](/docs/ru/reference/data-types/date32), [`DateTime`](/docs/ru/reference/data-types/datetime) или [`DateTime64`](/docs/ru/reference/data-types/datetime64) |

<div id="query-clauses">
  ### Секции запроса
</div>

При создании таблицы `VersionedCollapsingMergeTree` требуются те же [секции](/docs/ru/reference/engines/table-engines/mergetree-family/mergetree), что и при создании таблицы `MergeTree`.

<details markdown="1">
  <summary>Устаревший метод создания таблицы</summary>

  <Note>
    Не используйте этот метод в новых проектах. По возможности переведите существующие проекты на метод, описанный выше.
  </Note>

  ```sql theme={null}
  CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
  (
      name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
      name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
      ...
  ) ENGINE [=] VersionedCollapsingMergeTree(date-column [, sampling_expression], (primary, key), index_granularity, sign, version)
  ```

  Все параметры, кроме `sign` и `version`, имеют то же значение, что и в `MergeTree`.

  * `sign` — имя столбца с типом строки: `1` — это строка состояния, `-1` — строка отмены.

    Тип данных столбца — `Int8`.

  * `version` — имя столбца с версией состояния объекта.

    Тип данных столбца должен быть `UInt*`.
</details>

<div id="table_engines_versionedcollapsingmergetree">
  ## Схлопывание
</div>

<div id="data">
  ### Данные
</div>

Рассмотрим ситуацию, когда вам нужно сохранять непрерывно изменяющиеся данные некоторого объекта. Логично хранить для объекта одну строку и обновлять её при каждом изменении. Однако операция обновления затратна и медленна для СУБД, поскольку требует перезаписи данных в хранилище. Обновление не подходит, если данные нужно записывать быстро, но вместо этого можно последовательно записывать изменения объекта следующим образом.

При записи строки используйте столбец `Sign`. Если `Sign = 1`, это означает, что строка представляет состояние объекта (назовём её строкой состояния). Если `Sign = -1`, это указывает на отмену состояния объекта с теми же атрибутами (назовём её строкой отмены). Также используйте столбец `Version`, который должен идентифицировать каждое состояние объекта отдельным числом.

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

```text theme={null}
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │         5 │      146 │    1 │       1 |
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
```

Чуть позже мы фиксируем изменение активности пользователя и записываем его следующими двумя строками.

```text theme={null}
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │         5 │      146 │   -1 │       1 |
│ 4324182021466249494 │         6 │      185 │    1 │       2 |
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
```

Первая строка отменяет предыдущее состояние объекта (пользователя). Она должна копировать все поля отменяемого состояния, кроме `Sign`.

Вторая строка содержит текущее состояние.

Поскольку нам нужно только последнее состояние активности пользователя, строки

```text theme={null}
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │         5 │      146 │    1 │       1 |
│ 4324182021466249494 │         5 │      146 │   -1 │       1 |
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
```

может быть удалено, в результате чего недействительное (старое) состояние объекта схлопывается. `VersionedCollapsingMergeTree` делает это при слиянии частей данных.

Чтобы узнать, почему для каждого изменения нужны две строки, см. [Алгоритм](#table_engines-versionedcollapsingmergetree-algorithm).

**Примечания по использованию**

1. Программа, которая записывает данные, должна помнить состояние объекта, чтобы можно было его отменить. Строка "Cancel" должна содержать копии полей первичного ключа, версию строки "state" и противоположный `Sign`. Это увеличивает начальный объем хранилища, но позволяет быстро записывать данные.
2. Длинные растущие массивы в столбцах снижают эффективность движка из-за нагрузки при записи. Чем проще данные, тем выше эффективность.
3. Результаты `SELECT` сильно зависят от согласованности истории изменений объекта. Будьте внимательны при подготовке данных для вставки. Несогласованные данные могут приводить к непредсказуемым результатам, например к отрицательным значениям для неотрицательных метрик, таких как глубина сеанса.

<div id="table_engines-versionedcollapsingmergetree-algorithm">
  ### Алгоритм
</div>

Когда ClickHouse выполняет слияние частей данных, он удаляет каждую пару строк с одинаковыми первичным ключом и версией, но с разным `Sign`. Порядок строк не имеет значения.

При вставке данных ClickHouse упорядочивает строки по первичному ключу. Если столбец `Version` не входит в первичный ключ, ClickHouse неявно добавляет его в конец первичного ключа и использует для упорядочивания.

<div id="selecting-data">
  ## Выборка данных
</div>

ClickHouse не гарантирует, что все строки с одинаковым первичным ключом окажутся в одной и той же результирующей части данных или даже на одном и том же физическом сервере. Это верно как для записи данных, так и для последующего слияния частей данных. Кроме того, ClickHouse обрабатывает `SELECT`-запросы в несколько потоков и не может предсказать порядок строк в результате. Это означает, что, если вам нужно получить полностью «схлопнутые» данные из таблицы `VersionedCollapsingMergeTree`, необходима агрегация.

Чтобы завершить схлопывание, напишите запрос с предложением `GROUP BY` и агрегатными функциями, учитывающими знак. Например, чтобы вычислить количество, используйте `sum(Sign)` вместо `count()`. Чтобы вычислить сумму чего-либо, используйте `sum(Sign * x)` вместо `sum(x)` и добавьте `HAVING sum(Sign) > 0`.

Агрегатные функции `count`, `sum` и `avg` можно вычислить таким способом. Агрегатную функцию `uniq` можно вычислить, если у объекта есть хотя бы одно несхлопнутое состояние. Агрегатные функции `min` и `max` вычислить нельзя, потому что `VersionedCollapsingMergeTree` не сохраняет историю значений схлопнутых состояний.

Если вам нужно получить данные со «схлопыванием», но без агрегации (например, чтобы проверить, есть ли строки, у которых последние значения соответствуют определённым условиям), можно использовать модификатор `FINAL` в предложении `FROM`. Этот подход неэффективен и не должен использоваться с большими таблицами.

<div id="example-of-use">
  ## Пример использования
</div>

Пример данных:

```text theme={null}
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │         5 │      146 │    1 │       1 |
│ 4324182021466249494 │         5 │      146 │   -1 │       1 |
│ 4324182021466249494 │         6 │      185 │    1 │       2 |
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
```

Создание таблицы:

```sql theme={null}
CREATE TABLE UAct
(
    UserID UInt64,
    PageViews UInt8,
    Duration UInt8,
    Sign Int8,
    Version UInt8
)
ENGINE = VersionedCollapsingMergeTree(Sign, Version)
ORDER BY UserID
```

Вставка данных:

```sql theme={null}
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1, 1)
```

```sql theme={null}
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1, 1),(4324182021466249494, 6, 185, 1, 2)
```

Мы выполняем два запроса `INSERT`, чтобы создать две разные части данных. Если вставить данные одним запросом, ClickHouse создаст одну часть данных и никогда не выполнит слияние.

Получение данных:

```sql theme={null}
SELECT * FROM UAct
```

```text theme={null}
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │         5 │      146 │    1 │       1 │
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │         5 │      146 │   -1 │       1 │
│ 4324182021466249494 │         6 │      185 │    1 │       2 │
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
```

Что мы здесь видим и где находятся схлопнутые части?
Мы создали две части данных с помощью двух запросов `INSERT`. Запрос `SELECT` выполнялся в двух потоках, поэтому в результате строки расположены в случайном порядке.
Схлопывания не произошло, потому что части данных ещё не были слиты. ClickHouse выполняет слияние частей данных в непредсказуемый момент времени, который мы не можем заранее определить.

Вот почему нам нужна агрегация:

```sql theme={null}
SELECT
    UserID,
    sum(PageViews * Sign) AS PageViews,
    sum(Duration * Sign) AS Duration,
    Version
FROM UAct
GROUP BY UserID, Version
HAVING sum(Sign) > 0
```

```text theme={null}
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Version─┐
│ 4324182021466249494 │         6 │      185 │       2 │
└─────────────────────┴───────────┴──────────┴─────────┘
```

Если агрегация не нужна и требуется принудительно выполнить схлопывание, можно использовать модификатор `FINAL` в предложении `FROM`.

```sql theme={null}
SELECT * FROM UAct FINAL
```

```text theme={null}
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
│ 4324182021466249494 │         6 │      185 │    1 │       2 │
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
```

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