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

> يرث من MergeTree، لكنه يضيف منطقًا لاختزال الصفوف أثناء عملية الدمج.

# محرك الجدول CollapsingMergeTree

<div id="description">
  ## الوصف
</div>

يرث المحرك `CollapsingMergeTree` من [MergeTree](/docs/ar/reference/engines/table-engines/mergetree-family/mergetree)
ويضيف منطقًا لاختزال الصفوف أثناء عملية الدمج.
يقوم محرك الجدول `CollapsingMergeTree` بحذف (اختزال)
أزواج من الصفوف بشكل غير متزامن إذا كانت جميع الحقول في مفتاح الفرز (`ORDER BY`) متطابقة، باستثناء الحقل الخاص `Sign`،
الذي يمكن أن تكون قيمته `1` أو `-1`.
ويُحتفَظ بالصفوف التي لا يقابلها صف ذو قيمة `Sign` معاكسة.

لمزيد من التفاصيل، راجع قسم [Collapsing](#table_engine-collapsingmergetree-collapsing) في الوثيقة.

<Note>
  قد يقلّل هذا المحرك بشكل كبير من حجم التخزين،
  مما يزيد بالتبعية من كفاءة استعلامات `SELECT`.
</Note>

<div id="parameters">
  ## المعلمات
</div>

جميع معلمات محرك الجدول هذا، باستثناء المعلمة `Sign`،
لها المعنى نفسه كما في [`MergeTree`](/docs/ar/reference/engines/table-engines/mergetree-family/mergetree).

* `Sign` — الاسم المُعطى لعمود يحدد نوع الصف، بحيث تكون القيمة `1` صف «حالة»، وتكون القيمة `-1` صف «إلغاء». النوع: [Int8](/docs/ar/reference/data-types/int-uint).

<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 = CollapsingMergeTree(Sign)
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
```

<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 [=] CollapsingMergeTree(date-column [, sampling_expression], (primary, key), index_granularity, Sign)
  ```

  `Sign` — الاسم المُعطى لعمود من نوع الصف، حيث تمثل القيمة `1` صف "حالة" وتمثل القيمة `-1` صف "إلغاء". [Int8](/docs/ar/reference/data-types/int-uint).
</details>

* للحصول على وصف لمعلمات الاستعلام، راجع [وصف الاستعلام](/docs/ar/reference/statements/create/table).
* عند إنشاء جدول `CollapsingMergeTree`، تكون [بنود الاستعلام](/docs/ar/reference/engines/table-engines/mergetree-family/mergetree#table_engine-mergetree-creating-a-table) نفسها مطلوبة، كما هو الحال عند إنشاء جدول `MergeTree`.

<div id="table_engine-collapsingmergetree-collapsing">
  ## اختزال
</div>

<div id="data">
  ### البيانات
</div>

افترض حالة تحتاج فيها إلى حفظ بيانات تتغير باستمرار لكائن معيّن.
قد يبدو من المنطقي أن يكون هناك صف واحد لكل كائن وتحديثه كلما تغيّر شيء ما،
لكن عمليات التحديث مكلفة وبطيئة بالنسبة إلى نظام إدارة قواعد البيانات، لأنها تتطلب إعادة كتابة البيانات في التخزين.
إذا كنا بحاجة إلى كتابة البيانات بسرعة، فإن إجراء أعداد كبيرة من عمليات التحديث ليس نهجًا مقبولًا،
لكن يمكننا دائمًا كتابة تغييرات الكائن بشكل تسلسلي.
وللقيام بذلك، نستخدم العمود الخاص `Sign`.

* إذا كانت قيمة `Sign` = `1` فهذا يعني أن الصف هو صف "حالة": *صف يحتوي على حقول تمثل الحالة الحالية الصالحة*.
* إذا كانت قيمة `Sign` = `-1` فهذا يعني أن الصف هو صف "إلغاء": *صف يُستخدم لإلغاء حالة كائن له السمات نفسها*.

على سبيل المثال، نريد حساب عدد الصفحات التي تصفحها المستخدمون على موقع ويب ما، والمدة التي قضوها في زيارتها.
وفي لحظة زمنية معيّنة، نكتب الصف التالي الذي يمثل حالة نشاط المستخدم:

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

لاحقًا، نسجّل التغيّر في نشاط المستخدم وندوّنه في الصفّين التاليين:

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

يلغي الصف الأول الحالة السابقة للكائن (الذي يمثّل مستخدمًا في هذه الحالة).
يجب أن ينسخ جميع حقول مفتاح الفرز الخاصة بالصف "الملغى" باستثناء `Sign`.
يحتوي الصف الثاني أعلاه على الحالة الحالية.

وبما أننا نحتاج فقط إلى آخر حالة لنشاط المستخدم، يمكن حذف صف "الحالة" الأصلي وصف "الإلغاء"
الذي أدرجناه، كما هو موضح أدناه، مع اختزال الحالة غير الصالحة (القديمة) للكائن:

```text theme={null}
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         5 │      146 │    1 │ -- old "state" row can be deleted
│ 4324182021466249494 │         5 │      146 │   -1 │ -- "cancel" row can be deleted
│ 4324182021466249494 │         6 │      185 │    1 │ -- new "state" row remains
└─────────────────────┴───────────┴──────────┴──────┘
```

ينفّذ `CollapsingMergeTree` سلوك *الاختزال* هذا تحديدًا أثناء دمج أجزاء البيانات.

<Note>
  تُناقَش أسباب الحاجة إلى صفّين لكل تغيير
  بمزيد من التفصيل في فقرة [Algorithm](#table_engine-collapsingmergetree-collapsing-algorithm).
</Note>

**خصائص هذا النهج**

1. يجب أن يتذكّر البرنامج الذي يكتب البيانات حالة الكائن حتى يتمكّن من إلغائها. ويجب أن يحتوي صف "إلغاء" على نُسخ من حقول مفتاح الفرز الخاصة بـ "حالة"، مع قيمة `Sign` معاكسة. يزيد هذا من الحجم الأولي للتخزين، لكنه يتيح كتابة البيانات بسرعة.
2. تؤدي المصفوفات الطويلة والمتزايدة في الأعمدة إلى تقليل كفاءة المحرّك بسبب زيادة العبء عند الكتابة. وكلما كانت البيانات أبسط، ارتفعت الكفاءة.
3. تعتمد نتائج `SELECT` بدرجة كبيرة على اتساق سجل تغييرات الكائن. توخَّ الدقة عند إعداد البيانات لإدراجها. فقد تحصل على نتائج غير متوقعة إذا كانت البيانات غير متسقة. على سبيل المثال، قيم سالبة لمقاييس غير سالبة مثل عمق الجلسة.

<div id="table_engine-collapsingmergetree-collapsing-algorithm">
  ### الخوارزمية
</div>

عندما يدمج ClickHouse [أجزاء](/docs/ar/concepts/core-concepts/glossary#parts) البيانات،
تُختزل كل مجموعة من الصفوف المتتالية التي لها مفتاح الفرز نفسه (`ORDER BY`) إلى ما لا يزيد على صفّين:
صف "حالة" حيث `Sign` = `1`، وصف "إلغاء" حيث `Sign` = `-1`.
وبعبارة أخرى، في ClickHouse تُختزل الإدخالات.

لكل جزء بيانات ناتج، يحفظ ClickHouse ما يلي:

|    |                                                                                                      |
| -- | ---------------------------------------------------------------------------------------------------- |
| 1. | أول صف "إلغاء" وآخر صف "حالة"، إذا كان عدد صفوف "حالة" و"إلغاء" متساويًا وكان الصف الأخير صف "حالة". |
| 2. | آخر صف "حالة"، إذا كان عدد صفوف "حالة" أكبر من عدد صفوف "إلغاء".                                     |
| 3. | أول صف "إلغاء"، إذا كان عدد صفوف "إلغاء" أكبر من عدد صفوف "حالة".                                    |
| 4. | لا يحفظ أي صف، في جميع الحالات الأخرى.                                                               |

بالإضافة إلى ذلك، عندما يكون هناك صفّان إضافيان على الأقل من نوع "حالة" مقارنةً بصفوف "إلغاء"،
أو صفّان إضافيان على الأقل من نوع "إلغاء" مقارنةً بصفوف "حالة"، تستمر عملية الدمج.
ومع ذلك، يتعامل ClickHouse مع هذا الوضع على أنه خطأ منطقي ويسجّله في سجل الخادم.
قد يحدث هذا الخطأ إذا أُدرجت البيانات نفسها أكثر من مرة.
ولذلك، يجب ألا يغيّر الاختزال نتائج حساب الإحصاءات.
وتُختزل التغييرات تدريجيًا بحيث لا تبقى في النهاية إلا الحالة الأخيرة لكل كائن تقريبًا.

العمود `Sign` مطلوب لأن خوارزمية الدمج لا تضمن
أن تكون جميع الصفوف ذات مفتاح الفرز نفسه في جزء البيانات الناتج نفسه، أو حتى على الخادم الفعلي نفسه.
يعالج ClickHouse استعلامات `SELECT` باستخدام خيوط تنفيذ متعددة، ولا يمكنه التنبؤ بترتيب الصفوف في النتيجة.

يلزم التجميع إذا كانت هناك حاجة إلى الحصول على بيانات "مختزلة" بالكامل من جدول `CollapsingMergeTree`.
ولإتمام الاختزال، اكتب استعلامًا باستخدام عبارة `GROUP BY` ودوال التجميع التي تراعي الإشارة.
فعلى سبيل المثال، لحساب العدد، استخدم `sum(Sign)` بدلًا من `count()`.
ولحساب مجموع قيمة ما، استخدم `sum(Sign * x)` مع `HAVING sum(Sign) > 0` بدلًا من `sum(x)`
كما في [المثال](#example-of-use) أدناه.

يمكن حساب الدوال التجميعية `count` و`sum` و`avg` بهذه الطريقة.
ويمكن حساب الدالة التجميعية `uniq` إذا كان للكائن حالة واحدة غير مختزلة على الأقل.
أما الدالتان التجميعيتان `min` و`max` فلا يمكن حسابهما
لأن `CollapsingMergeTree` لا يحفظ سجل الحالات المختزلة.

<Note>
  إذا كنت بحاجة إلى استخراج البيانات من دون تجميع
  (على سبيل المثال، للتحقق مما إذا كانت الصفوف التي تتطابق أحدث قيمها مع شروط معيّنة موجودة)،
  فيمكنك استخدام المعدِّل [`FINAL`](/docs/ar/reference/statements/select/from#final-modifier) لعبارة `FROM`. وسيؤدي ذلك إلى دمج البيانات قبل إرجاع النتيجة.
  وبالنسبة إلى CollapsingMergeTree، لا يُعاد إلا أحدث صف حالة لكل مفتاح.
</Note>

<div id="examples">
  ## أمثلة
</div>

<div id="example-of-use">
  ### مثال للاستخدام
</div>

إذا كانت لدينا بيانات المثال التالية:

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

لنُنشئ جدول `UAct` باستخدام `CollapsingMergeTree`:

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

بعد ذلك، سنُدرِج بعض البيانات:

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

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

نستخدم استعلامَي `INSERT` لإنشاء جزأَي بيانات مختلفين.

<Note>
  إذا أدرجنا البيانات باستعلام واحد فقط، فسينشئ ClickHouse جزء بيانات واحدًا فقط ولن يُجري أي عملية دمج مطلقًا.
</Note>

يمكننا الاستعلام عن البيانات باستخدام:

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

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

لنلقِ نظرة على البيانات المُعادة أعلاه ولنرَ ما إذا كان الاختزال قد حدث...
باستخدام استعلامَي `INSERT`، أنشأنا جزأين من البيانات.
نُفِّذ استعلام `SELECT` على خيطَي تنفيذ، وحصلنا على ترتيب عشوائي للصفوف.
ومع ذلك، **لم يحدث الاختزال** لأنه لم يكن هناك أي دمج لأجزاء البيانات بعد،
كما أن ClickHouse يدمج أجزاء البيانات في الخلفية في وقت غير معلوم لا يمكننا التنبؤ به.

لذلك نحتاج إلى تجميع،
ونُجري ذلك باستخدام دالة التجميع [`sum`](/docs/ar/reference/functions/aggregate-functions/sum)
وعبارة [`HAVING`](/docs/ar/reference/statements/select/having):

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

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

إذا لم نكن بحاجة إلى التجميع وأردنا فرض عملية الاختزال، فيمكننا أيضًا استخدام المُعدِّل `FINAL` مع عبارة `FROM`.

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

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

<Note>
  هذا الأسلوب في تحديد البيانات أقل كفاءة، ولا يُنصح باستخدامه مع الكميات الكبيرة من البيانات التي يجري فحصها (ملايين الصفوف).
</Note>

<div id="example-of-another-approach">
  ### مثال على نهج آخر
</div>

تقوم فكرة هذا النهج على أن عمليات الدمج لا تأخذ في الحسبان سوى حقول المفتاح.
لذلك يمكننا، في الصف "إلغاء"، تحديد قيم سالبة
تلغي أثر الإصدار السابق من الصف عند الجمع من دون استخدام العمود `Sign`.

في هذا المثال، سنستخدم بيانات العينة الواردة أدناه:

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

في هذا النهج، يلزم تغيير أنواع البيانات للحقلين `PageViews` و`Duration` بحيث يمكن تخزين القيم السالبة.
لذلك نغيّر نوعَي هذين العمودين من `UInt8` إلى `Int16` عند إنشاء الجدول `UAct` باستخدام
`collapsingMergeTree`:

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

لنختبر هذا النهج عبر إدخال البيانات في جدولنا.

ومع ذلك، يُعد ذلك مقبولًا في الأمثلة أو مع الجداول الصغيرة:

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

SELECT * FROM UAct FINAL;
```

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

```sql theme={null}
SELECT
    UserID,
    sum(PageViews) AS PageViews,
    sum(Duration) AS Duration
FROM UAct
GROUP BY UserID
```

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

```sql theme={null}
SELECT COUNT() FROM UAct
```

```text theme={null}
┌─count()─┐
│       3 │
└─────────┘
```

```sql theme={null}
OPTIMIZE TABLE UAct FINAL;

SELECT * FROM UAct
```

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