> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-mintlify-fbfa8bee.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> استخدم إزالة التكرار عندما تحتاج إلى تنفيذ عمليات upsert وعمليات التحديث والحذف بشكل متكرر.

# استراتيجيات إزالة التكرار

export const Image = ({img, alt, size}) => {
  return <Frame>
      <img src={img} alt={alt} />
    </Frame>;
};

**إزالة التكرار** هي عملية ***حذف الصفوف المكررة من مجموعة بيانات***. في قاعدة بيانات OLTP، يُنجَز ذلك بسهولة لأن لكل صف مفتاحًا أساسيًا فريدًا، لكن على حساب إبطاء عمليات الإدراج. إذ يجب أولًا البحث عن كل صف مُدرَج، وإذا وُجد، فيجب استبداله.

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

* في أي لحظة، قد يظل جدولك يحتوي على صفوف مكررة (صفوف لها مفتاح الفرز نفسه)
* يحدث الحذف الفعلي للصفوف المكررة أثناء دمج الأجزاء
* يجب أن تراعي استعلاماتك احتمال وجود صفوف مكررة

<div class="transparent-table">
  |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |                                                                                                                                                                                                                                                               |
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | <Image img="https://mintcdn.com/private-7c7dfe99-mintlify-fbfa8bee/3U97coUNTxZWvrPx/images/guides/developer/de_duplication.png?fit=max&auto=format&n=3U97coUNTxZWvrPx&q=85&s=1b38355b77061d9e34680021f9babc50" alt="شعار إزالة التكرار" size="sm" width="1567" height="802" data-path="images/guides/developer/de_duplication.png" /> | يوفّر ClickHouse تدريبًا مجانيًا حول إزالة التكرار والعديد من الموضوعات الأخرى. وتُعد [Deleting and Updating Data training module](https://learn.clickhouse.com/visitor_catalog_class/show/1328954/?utm_source=clickhouse\&utm_medium=docs) نقطة انطلاق جيدة. |
</div>

<div id="options-for-deduplication">
  ## خيارات إزالة التكرار
</div>

تُنفَّذ إزالة التكرار في ClickHouse باستخدام محركات الجداول التالية:

1. محرك الجدول `ReplacingMergeTree`: مع محرك الجدول هذا، تُزال الصفوف المكررة ذات مفتاح الترتيب نفسه أثناء عمليات الدمج. ويُعد `ReplacingMergeTree` خيارًا جيدًا لمحاكاة سلوك upsert (عندما تريد أن تُرجع الاستعلامات آخر صف تم إدراجه).

2. طيّ الصفوف: يستخدم محركا الجدول `CollapsingMergeTree` و`VersionedCollapsingMergeTree` منطقًا يُلغى فيه صف موجود ويُدرج صف جديد. وهما أكثر تعقيدًا في التنفيذ من `ReplacingMergeTree`، لكن يمكن أن تكون الاستعلامات وعمليات التجميع أبسط في الكتابة دون الحاجة إلى القلق بشأن ما إذا كانت البيانات قد دُمجت بالفعل أم لا. ويكون محركا الجدول هذان مفيدين عندما تحتاج إلى تحديث البيانات بشكل متكرر.

نستعرض كلتا هاتين الطريقتين أدناه. ولمزيد من التفاصيل، اطّلع على [Deleting and Updating Data training module](https://learn.clickhouse.com/visitor_catalog_class/show/1328954/?utm_source=clickhouse\&utm_medium=docs) المجانية والمتاحة عند الطلب.

<div id="using-replacingmergetree-for-upserts">
  ## استخدام ReplacingMergeTree لعمليات upsert
</div>

لنلقِ نظرة على مثال بسيط لجدول يحتوي على تعليقات Hacker News مع عمود views يمثّل عدد مرات مشاهدة التعليق. لنفترض أننا نُدرج صفًا جديدًا عند نشر مقال، ثم نُجري upsert بإضافة صف جديد مرة واحدة يوميًا يتضمن إجمالي عدد المشاهدات إذا زادت القيمة:

```sql theme={null}
CREATE TABLE hackernews_rmt (
    id UInt32,
    author String,
    comment String,
    views UInt64
)
ENGINE = ReplacingMergeTree
PRIMARY KEY (author, id)
```

لنُدرِج صفَّين:

```sql theme={null}
INSERT INTO hackernews_rmt VALUES
   (1, 'ricardo', 'This is post #1', 0),
   (2, 'ch_fan', 'This is post #2', 0)
```

لتحديث العمود `views`، أدرِج صفًا جديدًا بالمفتاح الأساسي نفسه (لاحظ القيم الجديدة للعمود `views`):

```sql theme={null}
INSERT INTO hackernews_rmt VALUES
   (1, 'ricardo', 'This is post #1', 100),
   (2, 'ch_fan', 'This is post #2', 200)
```

يحتوي الجدول الآن على 4 صفوف:

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

```response theme={null}
┌─id─┬─author──┬─comment─────────┬─views─┐
│  2 │ ch_fan  │ This is post #2 │     0 │
│  1 │ ricardo │ This is post #1 │     0 │
└────┴─────────┴─────────────────┴───────┘
┌─id─┬─author──┬─comment─────────┬─views─┐
│  2 │ ch_fan  │ This is post #2 │   200 │
│  1 │ ricardo │ This is post #1 │   100 │
└────┴─────────┴─────────────────┴───────┘
```

تُظهر المربعات المنفصلة أعلاه في المخرجات الجزأين الفعليين في الخلفية — لم تُدمج هذه البيانات بعد، لذا لم تُزل الصفوف المكررة بعد. لنستخدم الكلمة المفتاحية `FINAL` في استعلام `SELECT`، ما يؤدي إلى دمج منطقي لنتيجة الاستعلام:

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

```response theme={null}
┌─id─┬─author──┬─comment─────────┬─views─┐
│  2 │ ch_fan  │ This is post #2 │   200 │
│  1 │ ricardo │ This is post #1 │   100 │
└────┴─────────┴─────────────────┴───────┘
```

تتضمن النتيجة صفَّين فقط، والصف الأخير المُدرَج هو الذي يُعاد.

<Note>
  يكون استخدام `FINAL` مناسبًا إذا كانت كمية البيانات صغيرة. أما إذا كنت تتعامل مع كمية كبيرة من البيانات،
  فربما لا يكون استخدام `FINAL` الخيار الأفضل. لنناقش خيارًا أفضل
  للعثور على أحدث قيمة في عمود.
</Note>

<div id="avoiding-final">
  ### تجنّب FINAL
</div>

لنُحدِّث العمود `views` مرة أخرى لكلٍّ من الصفّين الفريدين:

```sql theme={null}
INSERT INTO hackernews_rmt VALUES
   (1, 'ricardo', 'This is post #1', 150),
   (2, 'ch_fan', 'This is post #2', 250)
```

يحتوي الجدول الآن على 6 صفوف، لأن عملية دمج فعلية لم تتم بعد (وما حدث فقط هو الدمج وقت الاستعلام عند استخدام `FINAL`).

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

```response theme={null}
┌─id─┬─author──┬─comment─────────┬─views─┐
│  2 │ ch_fan  │ This is post #2 │   200 │
│  1 │ ricardo │ This is post #1 │   100 │
└────┴─────────┴─────────────────┴───────┘
┌─id─┬─author──┬─comment─────────┬─views─┐
│  2 │ ch_fan  │ This is post #2 │     0 │
│  1 │ ricardo │ This is post #1 │     0 │
└────┴─────────┴─────────────────┴───────┘
┌─id─┬─author──┬─comment─────────┬─views─┐
│  2 │ ch_fan  │ This is post #2 │   250 │
│  1 │ ricardo │ This is post #1 │   150 │
└────┴─────────┴─────────────────┴───────┘
```

بدلاً من استخدام `FINAL`، لنستخدم بعض منطق الأعمال — نحن نعلم أن العمود `views` يزداد دائماً، لذا يمكننا اختيار الصف ذي القيمة الأكبر باستخدام الدالة `max` بعد التجميع حسب الأعمدة المطلوبة:

```sql theme={null}
SELECT
    id,
    author,
    comment,
    max(views)
FROM hackernews_rmt
GROUP BY (id, author, comment)
```

```response theme={null}
┌─id─┬─author──┬─comment─────────┬─max(views)─┐
│  2 │ ch_fan  │ This is post #2 │        250 │
│  1 │ ricardo │ This is post #1 │        150 │
└────┴─────────┴─────────────────┴────────────┘
```

قد يكون التجميع كما هو موضح في الاستعلام أعلاه أكثر كفاءة بالفعل (من حيث أداء الاستعلام) من استخدام الكلمة المفتاحية `FINAL`.

تتوسع [Deleting and Updating Data training module](https://learn.clickhouse.com/visitor_catalog_class/show/1328954/?utm_source=clickhouse\&utm_medium=docs) في شرح هذا المثال، بما في ذلك كيفية استخدام عمود `version` مع `ReplacingMergeTree`.

<div id="using-collapsingmergetree-for-updating-columns-frequently">
  ## استخدام CollapsingMergeTree لتحديث الأعمدة بشكل متكرر
</div>

يتضمن تحديث عمود حذف صف موجود واستبداله بقيم جديدة. كما رأيت بالفعل، فإن هذا النوع من التعديل في ClickHouse يحدث *في النهاية* أثناء عمليات الدمج. وإذا كان لديك عدد كبير من الصفوف التي تحتاج إلى تحديث، فقد يكون من الأكثر كفاءة فعليًا تجنب `ALTER TABLE..UPDATE` والاكتفاء بإدراج البيانات الجديدة إلى جانب البيانات الحالية. يمكننا إضافة عمود يوضح ما إذا كانت البيانات قديمة أم جديدة... وفي الواقع، يوجد بالفعل محرك جدول يطبق هذا السلوك بكفاءة كبيرة، لا سيما أنه يحذف البيانات القديمة تلقائيًا نيابةً عنك. لنرَ كيف يعمل ذلك.

لنفترض أننا نتتبع عدد مشاهدات تعليق على Hacker News باستخدام نظام خارجي، وكل بضع ساعات ندفع البيانات إلى ClickHouse. نريد حذف الصفوف القديمة وأن تمثل الصفوف الجديدة الحالة الجديدة لكل تعليق على Hacker News. يمكننا استخدام `CollapsingMergeTree` لتنفيذ هذا السلوك.

لنعرّف جدولًا لتخزين عدد المشاهدات:

```sql theme={null}
CREATE TABLE hackernews_views (
    id UInt32,
    author String,
    views UInt64,
    sign Int8
)
ENGINE = CollapsingMergeTree(sign)
PRIMARY KEY (id, author)
```

لاحظ أن الجدول `hackernews_views` يحتوي على عمود من النوع `Int8` باسم sign، ويُشار إليه باسم عمود **sign**. اسم عمود الإشارة اختياري، لكن نوع البيانات `Int8` مطلوب، ولاحظ أنه تم تمرير اسم العمود إلى مُنشئ جدول `CollapsingMergeTree`.

ما عمود الإشارة في جدول `CollapsingMergeTree`؟ إنه يمثّل *حالة* الصف، ولا يمكن أن تكون قيمة عمود الإشارة إلا 1 أو -1. وإليك آلية عمله:

* إذا كان لصفَّين المفتاح الأساسي نفسه (أو ترتيب الفرز إذا كان مختلفًا عن المفتاح الأساسي)، لكن كانت قيم عمود الإشارة فيهما مختلفة، فإن آخر صف أُدرج بقيمة +1 يصبح صف الحالة، وتُلغي الصفوف الأخرى بعضها بعضًا
* تُحذف الصفوف التي يُلغي بعضها بعضًا أثناء عمليات الدمج
* تُحتفَظ بالصفوف التي لا تملك زوجًا مطابقًا

لنُضِف صفًا إلى الجدول `hackernews_views`. وبما أنه الصف الوحيد لهذا المفتاح الأساسي، فسنضبط حالته على 1:

```sql theme={null}
INSERT INTO hackernews_views VALUES
   (123, 'ricardo', 0, 1)
```

لنفترض الآن أننا نريد تغيير عمود المشاهدات. تُدرِج صفَّين: أحدهما يُلغي الصفَّ الحالي، والآخر يحتوي على الحالة الجديدة للصف:

```sql theme={null}
INSERT INTO hackernews_views VALUES
   (123, 'ricardo', 0, -1),
   (123, 'ricardo', 150, 1)
```

يحتوي الجدول الآن على 3 صفوف ذات المفتاح الأساسي `(123, 'ricardo')`:

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

```response theme={null}
┌──id─┬─author──┬─views─┬─sign─┐
│ 123 │ ricardo │     0 │   -1 │
│ 123 │ ricardo │   150 │    1 │
└─────┴─────────┴───────┴──────┘
┌──id─┬─author──┬─views─┬─sign─┐
│ 123 │ ricardo │     0 │    1 │
└─────┴─────────┴───────┴──────┘
```

لاحظ أن استخدام `FINAL` يُرجع صف الحالة الحالي:

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

```response theme={null}
┌──id─┬─author──┬─views─┬─sign─┐
│ 123 │ ricardo │   150 │    1 │
└─────┴─────────┴───────┴──────┘
```

لكن بالطبع، لا يُنصح باستخدام `FINAL` مع الجداول الكبيرة.

<Note>
  القيمة المُمرَّرة للعمود `views` في مثالنا ليست مطلوبة فعليًا، ولا يلزم أن تتطابق مع القيمة الحالية لـ `views` في الصف القديم. بل يمكنك في الواقع إلغاء صف باستخدام المفتاح الأساسي فقط والقيمة ‎-1:

  ```sql theme={null}
  INSERT INTO hackernews_views(id, author, sign) VALUES
     (123, 'ricardo', -1)
  ```
</Note>

<div id="real-time-updates-from-multiple-threads">
  ## تحديثات آنية من خيوط تنفيذ متعددة
</div>

مع جدول `CollapsingMergeTree`، تُلغي الصفوف بعضها بعضًا باستخدام عمود الإشارة، وتُحدَّد حالة الصف بناءً على آخر صف تم إدراجه. لكن قد يصبح هذا إشكاليًا إذا كنت تُدرِج الصفوف من خيوط تنفيذ مختلفة، إذ قد تُدرَج الصفوف بترتيب غير متسق. لذلك فإن الاعتماد على الصف "الأخير" لا ينجح في هذه الحالة.

وهنا تبرز فائدة `VersionedCollapsingMergeTree` — فهو يطوي الصفوف مثل `CollapsingMergeTree` تمامًا، لكنه بدلًا من الاحتفاظ بآخر صف تم إدراجه، يحتفظ بالصف ذي أعلى قيمة في عمود الإصدار الذي تحدده.

لنلقِ نظرة على مثال. لنفترض أننا نريد تتبّع عدد مشاهدات تعليقات Hacker News لدينا، وأن البيانات تتحدّث باستمرار. نريد أن تستخدم التقارير أحدث القيم من دون فرض عمليات الدمج أو انتظار اكتمالها. نبدأ بجدول مشابه لـ `CollapsedMergeTree`، لكننا نضيف عمودًا لتخزين إصدار حالة الصف:

```sql theme={null}
CREATE TABLE hackernews_views_vcmt (
    id UInt32,
    author String,
    views UInt64,
    sign Int8,
    version UInt32
)
ENGINE = VersionedCollapsingMergeTree(sign, version)
PRIMARY KEY (id, author)
```

لاحظ أن الجدول يستخدم `VersionsedCollapsingMergeTree` كمحرّك، ويحدّد **عمود الإشارة** و**عمود الإصدار**. إليك كيفية عمل الجدول:

* يحذف كل زوج من الصفوف لهما المفتاح الأساسي نفسه والإصدار نفسه، مع اختلاف الإشارة
* لا يهم الترتيب الذي أُدرجت به الصفوف
* لاحظ أنه إذا لم يكن عمود الإصدار جزءًا من المفتاح الأساسي، فإن ClickHouse يضيفه ضمنيًا إلى المفتاح الأساسي باعتباره الحقل الأخير

تستخدم النوع نفسه من هذا المنطق عند كتابة الاستعلامات: اجمع حسب المفتاح الأساسي، واستخدم منطقًا مناسبًا لتجنّب الصفوف التي أُلغيت ولكن لم تُحذف بعد. لنُضف بعض الصفوف إلى جدول `hackernews_views_vcmt`:

```sql theme={null}
INSERT INTO hackernews_views_vcmt VALUES
   (1, 'ricardo', 0, 1, 1),
   (2, 'ch_fan', 0, 1, 1),
   (3, 'kenny', 0, 1, 1)
```

نحدّث الآن صفّين ونحذف أحدهما. لإلغاء صف، تأكد من تضمين رقم الإصدار السابق (لأنه جزء من المفتاح الأساسي):

```sql theme={null}
INSERT INTO hackernews_views_vcmt VALUES
   (1, 'ricardo', 0, -1, 1),
   (1, 'ricardo', 50, 1, 2),
   (2, 'ch_fan', 0, -1, 1),
   (3, 'kenny', 0, -1, 1),
   (3, 'kenny', 1000, 1, 2)
```

سنُنفّذ الاستعلام نفسه كما في السابق، والذي يضيف القيم ويطرحها بذكاء استنادًا إلى عمود الإشارة:

```sql theme={null}
SELECT
    id,
    author,
    sum(views * sign)
FROM hackernews_views_vcmt
GROUP BY (id, author)
HAVING sum(sign) > 0
ORDER BY id ASC
```

النتيجة صفّان:

```response theme={null}
┌─id─┬─author──┬─sum(multiply(views, sign))─┐
│  1 │ ricardo │                         50 │
│  3 │ kenny   │                       1000 │
└────┴─────────┴────────────────────────────┘
```

لنفرض تنفيذ دمج للجدول:

```sql theme={null}
OPTIMIZE TABLE hackernews_views_vcmt
```

يجب أن تحتوي النتيجة على صفّين فقط:

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

```response theme={null}
┌─id─┬─author──┬─views─┬─sign─┬─version─┐
│  1 │ ricardo │    50 │    1 │       2 │
│  3 │ kenny   │  1000 │    1 │       2 │
└────┴─────────┴───────┴──────┴─────────┘
```

يُعد جدول `VersionedCollapsingMergeTree` مفيدًا جدًا عندما تريد تطبيق إزالة التكرار أثناء إدراج الصفوف من عدة عملاء و/أو خيوط تنفيذ.

<div id="why-arent-my-rows-being-deduplicated">
  ## لماذا لا تُزال الصفوف المكررة من صفوفي؟
</div>

أحد الأسباب التي قد تؤدي إلى عدم إزالة التكرار من الصفوف المُدرجة هو استخدام دالة أو تعبير غير متكرر النتائج في عبارة `INSERT`. على سبيل المثال، إذا كنت تُدرج صفوفًا مع العمود `createdAt DateTime64(3) DEFAULT now()`، فستكون صفوفك فريدة حتمًا لأن كل صف سيحصل على قيمة افتراضية فريدة للعمود `createdAt`. ولن يتمكن محرك الجدول MergeTree / ReplicatedMergeTree من إزالة التكرار من الصفوف، لأن كل صف مُدرج سينتج عنه `checksum` فريد.

في هذه الحالة، يمكنك تحديد `insert_deduplication_token` خاص بك لكل دفعة من الصفوف لضمان ألا تؤدي عمليات الإدراج المتعددة للدفعة نفسها إلى إعادة إدراج الصفوف نفسها. يُرجى الاطلاع على [الوثائق الخاصة بـ `insert_deduplication_token`](/ar/reference/settings/session-settings#insert_deduplication_token) لمزيد من التفاصيل حول كيفية استخدام هذا الإعداد.
