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

# دالة الجدول Merge

> الاستعلام عن عدة جداول في الوقت نفسه.

تتيح لنا [دالة الجدول Merge](/ar/reference/functions/table-functions/merge) إجراء استعلام على عدة جداول بالتوازي.
وتفعل ذلك بإنشاء جدول [Merge](/ar/reference/engines/table-engines/special/merge) مؤقت، ثم تستمد بنية هذا الجدول من اتحاد أعمدة تلك الجداول واستنتاج الأنواع المشتركة.

<Frame>
  <iframe src="https://www.youtube.com/embed/b4YfRhD9SSI?si=MuoDwDWeikAV5ttk" title="مشغّل فيديو YouTube" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen />
</Frame>

<h2 id="setup-tables">
  إعداد الجداول
</h2>

سنتعلّم كيفية استخدام هذه الدالة بالاستعانة بـ [قاعدة بيانات مباريات ATP الخاصة بـ TennisMyLife](https://github.com/Tennismylife/TML-Database).
سنعالج ملفات CSV التي تتضمن مباريات تعود إلى ستينيات القرن الماضي، لكننا سننشئ مخططًا مختلفًا قليلًا لكل عقد.
وسنضيف أيضًا عمودين إضافيين لعقد التسعينيات.

تعليمات الاستيراد موضحة أدناه:

```sql theme={null}
CREATE OR REPLACE TABLE atp_matches_1960s ORDER BY tourney_id AS
SELECT tourney_id, surface, winner_name, loser_name, winner_seed, loser_seed, score
FROM url('https://raw.githubusercontent.com/Tennismylife/TML-Database/refs/heads/master/{1968..1969}.csv')
SETTINGS schema_inference_make_columns_nullable=0, 
         schema_inference_hints='winner_seed Nullable(String), loser_seed Nullable(UInt8)';

CREATE OR REPLACE TABLE atp_matches_1970s ORDER BY tourney_id AS 
SELECT tourney_id, surface, winner_name, loser_name, winner_seed, loser_seed, splitByWhitespace(score) AS score
FROM url('https://raw.githubusercontent.com/Tennismylife/TML-Database/refs/heads/master/{1970..1979}.csv')
SETTINGS schema_inference_make_columns_nullable=0, 
         schema_inference_hints='winner_seed Nullable(UInt8), loser_seed Nullable(UInt8)';

CREATE OR REPLACE TABLE atp_matches_1980s ORDER BY tourney_id AS
SELECT tourney_id, surface, winner_name, loser_name, winner_seed, loser_seed, splitByWhitespace(score) AS score
FROM url('https://raw.githubusercontent.com/Tennismylife/TML-Database/refs/heads/master/{1980..1989}.csv')
SETTINGS schema_inference_make_columns_nullable=0,
         schema_inference_hints='winner_seed Nullable(UInt16), loser_seed Nullable(UInt16)';

CREATE OR REPLACE TABLE atp_matches_1990s ORDER BY tourney_id AS
SELECT tourney_id, surface, winner_name, loser_name, winner_seed, loser_seed, splitByWhitespace(score) AS score,
       toBool(arrayExists(x -> position(x, 'W/O') > 0, score))::Nullable(bool) AS walkover,
       toBool(arrayExists(x -> position(x, 'RET') > 0, score))::Nullable(bool) AS retirement
FROM url('https://raw.githubusercontent.com/Tennismylife/TML-Database/refs/heads/master/{1990..1999}.csv')
SETTINGS schema_inference_make_columns_nullable=0,
         schema_inference_hints='winner_seed Nullable(UInt16), loser_seed Nullable(UInt16), surface Enum(\'Hard\', \'Grass\', \'Clay\', \'Carpet\')';
```

<h2 id="schema-multiple-tables">
  مخطط عدة جداول
</h2>

يمكننا تشغيل الاستعلام التالي لعرض أعمدة كل جدول وأنواعها جنبًا إلى جنب، مما يسهّل ملاحظة الاختلافات.

```sql theme={null}
SELECT * EXCEPT(position) FROM (
    SELECT position, name,
       any(if(table = 'atp_matches_1960s', type, null)) AS 1960s,
       any(if(table = 'atp_matches_1970s', type, null)) AS 1970s,
       any(if(table = 'atp_matches_1980s', type, null)) AS 1980s,
       any(if(table = 'atp_matches_1990s', type, null)) AS 1990s
    FROM system.columns
    WHERE database = currentDatabase() AND table LIKE 'atp_matches%'
    GROUP BY ALL
    ORDER BY position ASC
)
SETTINGS output_format_pretty_max_value_width=25;
```

```text theme={null}
┌─name────────┬─1960s────────────┬─1970s───────────┬─1980s────────────┬─1990s─────────────────────┐
│ tourney_id  │ String           │ String          │ String           │ String                    │
│ surface     │ String           │ String          │ String           │ Enum8('Hard' = 1, 'Grass'⋯│
│ winner_name │ String           │ String          │ String           │ String                    │
│ loser_name  │ String           │ String          │ String           │ String                    │
│ winner_seed │ Nullable(String) │ Nullable(UInt8) │ Nullable(UInt16) │ Nullable(UInt16)          │
│ loser_seed  │ Nullable(UInt8)  │ Nullable(UInt8) │ Nullable(UInt16) │ Nullable(UInt16)          │
│ score       │ String           │ Array(String)   │ Array(String)    │ Array(String)             │
│ walkover    │ ᴺᵁᴸᴸ             │ ᴺᵁᴸᴸ            │ ᴺᵁᴸᴸ             │ Nullable(Bool)            │
│ retirement  │ ᴺᵁᴸᴸ             │ ᴺᵁᴸᴸ            │ ᴺᵁᴸᴸ             │ Nullable(Bool)            │
└─────────────┴──────────────────┴─────────────────┴──────────────────┴───────────────────────────┘
```

لنستعرض الاختلافات:

* يغيّر جدول 1970s نوع `winner_seed` من `Nullable(String)` إلى `Nullable(UInt8)`، ونوع `score` من `String` إلى `Array(String)`.
* يغيّر جدول 1980s نوعَي `winner_seed` و`loser_seed` من `Nullable(UInt8)` إلى `Nullable(UInt16)`.
* يغيّر جدول 1990s `surface` من `String` إلى `Enum('Hard', 'Grass', 'Clay', 'Carpet')`، ويضيف العمودين `walkover` و`retirement`.

<h2 id="querying-multiple-tables">
  الاستعلام عن عدة جداول باستخدام merge
</h2>

لنكتب استعلامًا للعثور على المباريات التي فاز بها جون ماكنرو ضد لاعب كان المصنف الأول:

```sql theme={null}
SELECT loser_name, score
FROM merge('atp_matches*')
WHERE winner_name = 'John McEnroe'
AND loser_seed = 1;
```

```text theme={null}
┌─loser_name────┬─score───────────────────────────┐
│ Bjorn Borg    │ ['6-3','6-4']                   │
│ Bjorn Borg    │ ['7-6','6-1','6-7','5-7','6-4'] │
│ Bjorn Borg    │ ['7-6','6-4']                   │
│ Bjorn Borg    │ ['4-6','7-6','7-6','6-4']       │
│ Jimmy Connors │ ['6-1','6-3']                   │
│ Mats Wilander │ ['6-2','6-4']                   │
│ Ivan Lendl    │ ['6-2','4-6','6-3','6-7','7-6'] │
│ Ivan Lendl    │ ['6-3','3-6','6-3','7-6']       │
│ Ivan Lendl    │ ['6-1','6-3']                   │
│ Stefan Edberg │ ['6-2','6-3']                   │
│ Stefan Edberg │ ['7-6','6-2']                   │
│ Ivan Lendl    │ ['7-6','1-4','WEA']             │
│ Stefan Edberg │ ['6-2','6-2']                   │
│ Jakob Hlasek  │ ['6-3','7-6']                   │
└───────────────┴─────────────────────────────────┘
```

بعد ذلك، لنفترض أننا نريد تصفية تلك المباريات للعثور على المباريات التي كان فيها McEnroe مُصنَّفًا رقم 3 أو أقل.
وهذا أكثر تعقيدًا قليلًا لأن `winner_seed` يستخدم أنواعًا مختلفة عبر الجداول المختلفة:

```sql theme={null}
SELECT loser_name, score, winner_seed
FROM merge('atp_matches*')
WHERE winner_name = 'John McEnroe'
AND loser_seed = 1
AND multiIf(
  variantType(winner_seed) = 'UInt8', variantElement(winner_seed, 'UInt8') >= 3,
  variantType(winner_seed) = 'UInt16', variantElement(winner_seed, 'UInt16') >= 3,
  variantElement(winner_seed, 'String')::Nullable(UInt16) >= 3
);
```

نستخدم الدالة [`variantType`](/ar/reference/functions/regular-functions/other-functions#variantType) للتحقق من نوع `winner_seed` في كل صف، ثم نستخدم [`variantElement`](/ar/reference/functions/regular-functions/other-functions#variantElement) لاستخراج القيمة الفعلية.
وعندما يكون النوع `String`، نحوّله إلى رقم ثم نجري المقارنة.
تظهر نتيجة تنفيذ الاستعلام أدناه:

```text theme={null}
┌─loser_name────┬─score───────────────┬─winner_seed─┐
│ Bjorn Borg    │ ['6-3','6-4']       │ 3           │
│ Mats Wilander │ ['6-2','6-4']       │ 3           │
│ Stefan Edberg │ ['6-2','6-3']       │ 6           │
│ Stefan Edberg │ ['7-6','6-2']       │ 4           │
│ Ivan Lendl    │ ['7-6','1-4','WEA'] │ 4           │
│ Stefan Edberg │ ['6-2','6-2']       │ 7           │
└───────────────┴─────────────────────┴─────────────┘
```

<h2 id="which-table-merge">
  من أي جدول تأتي الصفوف عند استخدام merge؟
</h2>

ماذا لو أردنا معرفة الجدول الذي تنتمي إليه الصفوف؟
يمكننا استخدام العمود الافتراضي `_table` للقيام بذلك، كما هو موضح في الاستعلام التالي:

```sql theme={null}
SELECT _table, loser_name, score, winner_seed
FROM merge('atp_matches*')
WHERE winner_name = 'John McEnroe'
AND loser_seed = 1
AND multiIf(
  variantType(winner_seed) = 'UInt8', variantElement(winner_seed, 'UInt8') >= 3,
  variantType(winner_seed) = 'UInt16', variantElement(winner_seed, 'UInt16') >= 3,
  variantElement(winner_seed, 'String')::Nullable(UInt16) >= 3
);
```

```text theme={null}
┌─_table────────────┬─loser_name────┬─score───────────────┬─winner_seed─┐
│ atp_matches_1970s │ Bjorn Borg    │ ['6-3','6-4']       │ 3           │
│ atp_matches_1980s │ Mats Wilander │ ['6-2','6-4']       │ 3           │
│ atp_matches_1980s │ Stefan Edberg │ ['6-2','6-3']       │ 6           │
│ atp_matches_1980s │ Stefan Edberg │ ['7-6','6-2']       │ 4           │
│ atp_matches_1980s │ Ivan Lendl    │ ['7-6','1-4','WEA'] │ 4           │
│ atp_matches_1980s │ Stefan Edberg │ ['6-2','6-2']       │ 7           │
└───────────────────┴───────────────┴─────────────────────┴─────────────┘
```

يمكننا أيضًا استخدام هذا العمود الافتراضي ضمن استعلام لعدّ القيم في العمود `walkover`:

```sql theme={null}
SELECT _table, walkover, count()
FROM merge('atp_matches*')
GROUP BY ALL
ORDER BY _table;
```

```text theme={null}
┌─_table────────────┬─walkover─┬─count()─┐
│ atp_matches_1960s │ ᴺᵁᴸᴸ     │    7319 │
│ atp_matches_1970s │ ᴺᵁᴸᴸ     │   39404 │
│ atp_matches_1980s │ ᴺᵁᴸᴸ     │   36097 │
│ atp_matches_1990s │ true     │     129 │
│ atp_matches_1990s │ false    │   37023 │
└───────────────────┴──────────┴─────────┘
```

يمكننا أن نرى أن العمود `walkover` تكون قيمته `NULL` في جميع الحالات باستثناء `atp_matches_1990s`.
سنحتاج إلى تحديث الاستعلام للتحقق مما إذا كان العمود `score` يحتوي على السلسلة `W/O` إذا كانت قيمة العمود `walkover` هي `NULL`:

```sql theme={null}
SELECT _table,
   multiIf(
     walkover IS NOT NULL,
     walkover,
     variantType(score) = 'Array(String)',
     toBool(arrayExists(
        x -> position(x, 'W/O') > 0,
        variantElement(score, 'Array(String)')
     )),
     variantElement(score, 'String') LIKE '%W/O%'
   ),
   count()
FROM merge('atp_matches*')
GROUP BY ALL
ORDER BY _table;
```

إذا كان النوع الأساسي لـ `score` هو `Array(String)`، فعلينا استعراض عناصر المصفوفة والبحث عن `W/O`، أما إذا كان نوعه `String`، فيمكننا ببساطة البحث عن `W/O` في السلسلة النصية.

```text theme={null}
┌─_table────────────┬─multiIf(isNo⋯, '%W/O%'))─┬─count()─┐
│ atp_matches_1960s │ true                     │     296 │
│ atp_matches_1960s │ false                    │    7023 │
│ atp_matches_1970s │ true                     │     583 │
│ atp_matches_1970s │ false                    │   38821 │
│ atp_matches_1980s │ true                     │     117 │
│ atp_matches_1980s │ false                    │   35980 │
│ atp_matches_1990s │ true                     │     129 │
│ atp_matches_1990s │ false                    │   37023 │
└───────────────────┴──────────────────────────┴─────────┘
```
