تعلم ستة انضمامات SQL التي يحتاجها المحلل — INNER و LEFT و RIGHT و FULL OUTER و CROSS و SELF — مع أمثلة عملية وملاحظات عن الأخطاء الشائعة.

تسمح لك عملية JOIN في SQL بدمج صفوف من جدولين (أو أكثر) في نتيجة واحدة عبر مطابَقتها على عمود مرتبط — عادةً معرف (ID).
تُقسّم قواعد البيانات الواقعية عادةً إلى جداول منفصلة حتى لا تُكرر المعلومات. على سبيل المثال، اسم العميل موجود في جدول ، بينما مشترياته موجودة في جدول . الانضمامات هي الطريقة لإعادة ربط تلك القطع عندما تحتاج إجابات.
customersordersلهذا السبب تظهر الانضمامات في كل مكان في التقارير والتحليل:
بدون الانضمامات، ستضطر لتشغيل استعلامات منفصلة ودمج النتائج يدويًا — بطيء ومعرض للأخطاء وصعب التكرار.
إذا كنت تبني منتجات تعتمد على قاعدة بيانات علاقية (لوحات معلومات، لوحات إدارة، أدوات داخلية، بوابات العملاء)، فالانضمامات هي ما يحول "الجداول الخام" إلى عروض للمستخدمين. حتى أدوات توليد التطبيقات قد تعتمد على أساسيات الانضمام عندما تحتاج صفحات قوائم دقيقة أو واجهات مصالحة بيانات.
هذا الدليل يركّز على ستة انضمامات تغطي معظم أعمال SQL اليومية:
صياغة JOIN متشابهة عبر قواعد بيانات عديدة (PostgreSQL، MySQL، SQL Server، SQLite). هناك اختلافات قليلة — خاصة حول دعم FULL OUTER JOIN وبعض السلوكيات الحدّية — لكن المفاهيم والأنماط الأساسية قابلة للنقل.
لنجعل الأمثلة بسيطة، سنستخدم ثلاث جداول صغيرة تعكس إعدادًا عمليًا: العملاء يضعون طلبات، والطلبات قد (أو قد لا) تحتوي على مدفوعات.
ملاحظة صغيرة قبل البدء: الجداول النموذجية أدناه تظهر بعض الأعمدة فقط، لكن بعض الاستعلامات لاحقًا تشير إلى حقول إضافية (مثل order_date, created_at, status, أو paid_at) لإظهار أنماط شائعة. اعتبر تلك الأعمدة حقول "نمطية" تتواجد غالبًا في مخططات الإنتاج.
المفتاح الأساسي: customer_id
| customer_id | name |
|---|---|
| 1 | Ava |
| 2 | Ben |
| 3 | Chen |
| 4 | Dia |
المفتاح الأساسي: order_id
المفتاح الخارجي: customer_id → customers.customer_id
| order_id | customer_id | order_total |
|---|---|---|
| 101 | 1 | 50 |
| 102 | 1 | 120 |
| 103 | 2 | 35 |
| 104 | 5 | 70 |
لاحظ أن order_id = 104 يشير إلى customer_id = 5، وهو غير موجود في customers. هذه الحالة "الغير متطابقة" مفيدة لرؤية سلوك LEFT JOIN وRIGHT JOIN وFULL OUTER JOIN.
المفتاح الأساسي: payment_id
المفتاح الخارجي: order_id → orders.order_id
| payment_id | order_id | amount |
|---|---|---|
| 9001 | 101 | 50 |
| 9002 | 102 | 60 |
| 9003 | 102 | 60 |
| 9004 | 999 | 25 |
تفصيلان تعليميان مهمان هنا:
order_id = 102 له صفان للمدفوعات (دفعة مقسمة). عندما تنضم orders إلى payments سيظهر هذا الطلب مرتين — هنا حيث تفاجئ الناس بالتكرارات.payment_id = 9004 يشير إلى order_id = 999، الذي لا يوجد في orders. هذا ينشئ حالة "غير متطابقة" أخرى.orders إلى payments سيكرر الطلب 102 لأنه لديه مدفوعتان.يعيد INNER JOIN الصفوف فقط حيث توجد مطابقة في كلا الجدولين. إذا لم يكن لدى عميل طلبات، فلن يظهر في النتيجة. إذا أشار طلب إلى عميل غير موجود (بيانات خاطئة)، فلن يظهر ذلك الطلب أيضًا.
تختار جدولًا "أيسر" وتُنضم إلى جدول "أيمن" وتربطهما بشرط في جملة ON.
SELECT
c.customer_id,
c.name,
o.order_id,
o.order_date
FROM customers c
INNER JOIN orders o
ON o.customer_id = c.customer_id;
الفكرة الأساسية هي سطر ON o.customer_id = c.customer_id: يخبر SQL كيف ترتبط الصفوف.
إذا أردت قائمة بالعملاء الذين وضعوا فعليًا طلبًا واحدًا على الأقل (وتفاصيل الطلب)، فإن INNER JOIN هو الاختيار الطبيعي:
SELECT
c.name,
o.order_id,
o.total_amount
FROM customers c
INNER JOIN orders o
ON o.customer_id = c.customer_id
ORDER BY o.order_id;
هذا مفيد لحالات مثل "إرسال رسالة متابعة للطلب" أو "حساب الإيرادات لكل عميل" (حين تهتم فقط بالعملاء الذين لديهم مشتريات).
إذا كتبت انضمامًا ونسيت شرط ON (أو انضممت على أعمدة خاطئة)، يمكنك عن غير قصد إنشاء حاصل ديكارتي (كل عميل مع كل طلب) أو إنتاج تطابقات غير صحيحة.
سيئ (لا تفعل هذا):
SELECT c.name, o.order_id
FROM customers c
JOIN orders o;
تأكد دائمًا من وجود شرط انضمام واضح في ON (أو USING في الحالات المناسبة — سنغطي ذلك لاحقًا).
يعيد LEFT JOIN كل الصفوف من الجدول الأيسر، ويضيف بيانات مطابقة من الجدول الأيمن عند وجودها. إذا لم توجد مطابقة، تظهر أعمدة الجانب الأيمن كـ NULL.
استخدم LEFT JOIN عندما تريد قائمة كاملة من جدولك الأساسي، مع بيانات مرتبطة اختيارية.
مثال: "أرني كل العملاء، وأدرج طلباتهم إن وُجدت."
SELECT
c.customer_id,
c.name,
o.order_id,
o.order_date
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
ORDER BY c.customer_id;
o.order_id (وأعمدة orders الأخرى) ستكون NULL.استخدام شائع جدًا لـ LEFT JOIN هو العثور على عناصر ليس لها سجلات مرتبطة.
مثال: "أي العملاء لم يسبق لهم أن طلبوا؟"
SELECT
c.customer_id,
c.name
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL;
شرط WHERE ... IS NULL يبقي فقط صفوف جدول اليسار حيث لم يعثر الانضمام على تطابق.
يمكن أن "يضاعف" LEFT JOIN صفوف جدول اليسار عندما توجد مطابقات متعددة في الجدول الأيمن.
إذا كان لدى عميل طلبات متعددة، سيظهر العميل مرات متعددة — مرة لكل طلب. هذا متوقع، لكنه قد يفاجئك إذا كنت تحاول عدّ العملاء.
مثال، هذا يحسب الطلبات (ليس العملاء):
SELECT COUNT(*)
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id;
إذا كان هدفك عدّ العملاء فَغالبًا ما تحسب على المفتاح بدلًا من ذلك (مثل COUNT(DISTINCT c.customer_id)), حسب ما تقيس.
يحافظ RIGHT JOIN على كل الصفوف من الجدول الأيمن، ويُدرج فقط الصفوف المطابقة من الجدول الأيسر. إذا لم توجد مطابقة، تظهر أعمدة الجدول الأيسر كـ NULL. هو في الأساس صورة معكوسة للـ LEFT JOIN.
باستخدام جداولنا، تخيّل أنك تريد سرد كل المدفوعات حتى لو لم تربط بطلب (ربما تم حذف الطلب أو بيانات الدفع فوضوية).
SELECT
o.order_id,
o.customer_id,
p.payment_id,
p.amount,
p.paid_at
FROM orders o
RIGHT JOIN payments p
ON o.order_id = p.order_id;
ما ستحصل عليه:
payments على اليمين).o.order_id و o.customer_id قيمتها NULL.غالبًا يمكنك إعادة كتابة RIGHT JOIN كـ LEFT JOIN عبر تبديل ترتيب الجداول:
SELECT
o.order_id,
o.customer_id,
p.payment_id,
p.amount,
p.paid_at
FROM payments p
LEFT JOIN orders o
ON o.order_id = p.order_id;
يعطي نفس النتيجة، لكن كثيرًا ما يجد الناس أنه أسهل للقراءة: تبدأ بالجدول "الرئيسي" (payments) ثم تسحب البيانات الاختيارية.
تمنع إرشادات أسلوب SQL استخدام RIGHT JOIN لأن القارئ يُجبَر على قلب نمط التفكير الاعتيادي:
عندما تُكتب العلاقات الاختيارية دائمًا كـ LEFT JOIN تصبح الاستعلامات أسهل للمسح.
قد يكون مفيدًا عند تعديل استعلام موجود وتكتشف أن الجدول الذي يجب الحفاظ عليه موجود على اليمين. بدل إعادة كتابة الاستعلام الطويل، تغيير انضمام واحد إلى RIGHT JOIN قد يكون تعديلًا سريعًا وآمنًا.
يعيد FULL OUTER JOIN كل الصفوف من كلا الجدولين.
INNER JOIN).NULL لعمود اليمين.NULL لعمود اليسار.حالة عمل كلاسيكية هي مصالحة الطلبات مقابل المدفوعات:
مثال:
SELECT
o.order_id,
o.customer_id,
p.payment_id,
p.amount
FROM orders o
FULL OUTER JOIN payments p
ON p.order_id = o.order_id;
FULL OUTER JOIN مدعوم في PostgreSQL، SQL Server، وOracle.
هو غير متاح في MySQL وSQLite (ستحتاج إلى حلول بديلة).
إذا لم تدعم قاعدة بياناتك FULL OUTER JOIN، يمكنك محاكاتها بدمج:
orders (مع المدفوعات المطابقة إن وُجدت)، وpayments التي لم تطابق أي طلب.نمط شائع:
SELECT o.order_id, o.customer_id, p.payment_id, p.amount
FROM orders o
LEFT JOIN payments p
ON p.order_id = o.order_id
UNION
SELECT o.order_id, o.customer_id, p.payment_id, p.amount
FROM orders o
RIGHT JOIN payments p
ON p.order_id = o.order_id;
نصيحة: عندما ترى NULLs على أحد الجانبين، فهذه إشارة إلى أن الصف كان "مفقودًا" في الجدول الآخر — بالضبط ما تريده للمراجعات والتسويات.
يعيد CROSS JOIN كل الأزواج الممكنة من الصفوف بين جدولين. إذا كان لدى الجدول A ثلاث صفوف والجدول B أربعة صفوف، ستكون النتيجة 3 × 4 = 12 صفًا. يُسمَّى هذا أيضًا حاصل ديكارتي.
هذا قد يبدو مخيفًا — ويمكن أن يكون كذلك — لكنه مفيد عندما تريد فعلاً توليد التوافيق.
تخيل أن خيارات المنتج مخزنة في جداول منفصلة:
sizes: S, M, Lcolors: Red, Blueيستطيع CROSS JOIN توليد كل المتغيرات الممكنة (مفيد لإنشاء رموز المخزون، تحضير كتالوج، أو للاختبار):
SELECT
s.size,
c.color
FROM sizes AS s
CROSS JOIN colors AS c;
النتيجة (3 × 2 = 6 صفوف):
لأن أحجام الصفوف تُضرب، يمكن أن ينفجر حجم CROSS JOIN بسرعة:
قد يبطئ ذلك الاستعلامات ويستهلك الذاكرة وينتج مخرجات لا فائدة لها. إذا احتجت توليد التوافيق، اجعل الجداول المدخلة صغيرة واعتبر إضافة قيود أو حدود بشكل متحكم.
الـ SELF JOIN هو تمامًا ما يبدو عليه: تنضم إلى نفس الجدول. هذا مفيد عندما يرتبط صف بصف آخر داخل نفس الجدول — غالبًا في علاقات الوالد/الابن مثل الموظفون ومديروهم.
بما أنك تستخدم نفس الجدول مرتين، يجب أن تُعطي كل "نسخة" اسمًا مستعارًا مختلفًا. الأسماء المستعارة تجعل الاستعلام قابلًا للقراءة وتُحدد لأي جانب تشير.
نمط شائع:
e للموظفm للمديرتخيل جدول employees يحتوي على:
idnamemanager_id (يشير إلى id لموظف آخر)لإدراج كل موظف مع اسم مديره:
SELECT
e.id,
e.name AS employee_name,
m.name AS manager_name
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id;
لاحظ أن الاستعلام يستخدم LEFT JOIN، وليس INNER JOIN. هذا مهم لأن بعض الموظفين قد لا يكون لهم مدير (مثل المدير التنفيذي). في تلك الحالات، يكون manager_id غالبًا NULL، وانضمام LEFT يحافظ على صف الموظف مع إظهار manager_name كـ NULL.
لو استخدمت INNER JOIN لكان أولئك الموظفون ذوو NULL في manager_id قد اختفوا من النتيجة.
الانضمام لا "يعرف" تلقائيًا كيف ترتبط الجداول — عليك أن تخبره. علاقة الانضمام تُعرّف في شرط الانضمام، ويجب أن توضع بجانب جملة JOIN لأنها تشرح كيف تتطابق الجداول، ليس كيف تريد ترشيح النتيجة النهائية.
ON: الأكثر مرونة (والأشهر)استخدم ON عندما تريد تحكمًا كاملاً في منطق المطابقة — أسماء أعمدة مختلفة، شروط متعددة، أو قواعد إضافية.
SELECT
c.customer_id,
c.name,
o.order_id,
o.created_at
FROM customers AS c
INNER JOIN orders AS o
ON o.customer_id = c.customer_id;
ON هو المكان الذي يمكنك فيه تعريف مطابقات أكثر تعقيدًا (مثال: المطابقة على عمودين) دون أن تتحول الاستعلامات إلى لعبة تخمين.
USING: أقصر، لكنه لعمود باسم مشترك فقطبعض قواعد البيانات (مثل PostgreSQL و MySQL) تدعم USING. هي اختصار مريح عندما يحتوي كلا الجدولين على عمود بنفس الاسم وتريد الانضمام على ذلك العمود.
SELECT
customer_id,
name,
order_id
FROM customers
JOIN orders
USING (customer_id);
فائدة أنيقة: USING عادةً يعيد عمود customer_id واحدًا في الإخراج بدلاً من نسختين.
بعد الانضمام، تتداخل أسماء الأعمدة غالبًا (id, created_at, status). إذا كتبت SELECT id قد ترمي قاعدة البيانات خطأ "عمود غامض" — أو الأسوأ، قد تقرأ الـ id الخاطئ.
فضّل بادئات الجدول (أو الأسماء المستعارة) للوضوح:
SELECT c.customer_id, o.order_id
FROM customers AS c
JOIN orders AS o
ON o.customer_id = c.customer_id;
SELECT * في الاستعلامات المنضمةSELECT * يصبح فوضويًا بسرعة مع الانضمامات: تسحب أعمدة غير ضرورية، وتخاطر بأسماء مكررة، وتجعل من الصعب رؤية ماذا ينتج الاستعلام.
بدلًا من ذلك، اختر الأعمدة التي تحتاجها بالتحديد. نتيجتك أنظف، أسهل صيانة، وغالبًا أكثر كفاءة — خصوصًا عندما تكون الجداول عريضة.
عند الانضمام، كل من WHERE و ON يبدوان كأنهما "يرشيحان"، لكن توقيتهما مختلف.
ذلك الفرق في التوقيت هو سبب تحوّل LEFT JOIN عن غير قصد إلى INNER JOIN.
افترض أنك تريد كل العملاء حتى أولئك الذين ليس لديهم طلبات مدفوعة حديثة:
SELECT c.customer_id, c.name, o.order_id, o.status, o.order_date
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
WHERE o.status = 'PAID'
AND o.order_date >= DATE '2025-01-01';
المشكلة: للعملاء الذين ليس لديهم أي طلب مطابق، ستكون قيم o.status و o.order_date هي NULL. شرط WHERE يرفض تلك الصفوف، لذا يختفي العملاء غير المطابقين — فيصبح LEFT JOIN وكأنه INNER JOIN.
SELECT c.customer_id, c.name, o.order_id, o.status, o.order_date
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
AND o.status = 'PAID'
AND o.order_date >= DATE '2025-01-01';
الآن يظل العملاء الذين ليس لديهم طلبات مؤهلة ظاهرين (مع أعمدة طلب قيمتها NULL) — وهذا عادة ما يكون الهدف من LEFT JOIN.
WHERE o.order_id IS NOT NULL صراحةً).الانضمامات لا تضيف أعمدة فقط — يمكنها أيضًا تضاعف الصفوف. هذا سلوك صحيح غالبًا، لكنه يفاجئ عندما تتغير الإجماليات.
يعيد الانضمام صف إخراج لكل زوج من الصفوف المطابقة.
customers إلى orders، قد يظهر العميل عدة مرات — مرة لكل طلب.orders إلى payments وكل طلب قد له دفعات متعددة، فستحصل على عدة صفوف لكل طلب. وإذا انضممت أيضًا إلى جدول "عناصر الطلب" (order_items)، قد تنشأ ظاهرة الضرب (payments × items) لكل طلب.إذا هدفك "صف واحد لكل عميل" أو "صف واحد لكل طلب" فلخص الجانب "الكثير" أولًا، ثم انضم.
-- One row per order from payments
WITH payment_totals AS (
SELECT
order_id,
SUM(amount) AS total_paid,
COUNT(*) AS payment_count
FROM payments
GROUP BY order_id
)
SELECT
o.order_id,
o.customer_id,
COALESCE(pt.total_paid, 0) AS total_paid,
COALESCE(pt.payment_count, 0) AS payment_count
FROM orders o
LEFT JOIN payment_totals pt
ON pt.order_id = o.order_id;
هذا يحافظ على شكل الانضمام المتوقع: صف واحد لكل طلب يظل صفًا واحدًا.
SELECT DISTINCT قد يجعل التكرارات "تبدو" مصححة، لكنه قد يخفي المشكلة الحقيقية:
استخدمه فقط عندما تكون متأكدًا أن التكرارات عرضية وتعرف سببها.
قبل الوثوق في النتائج، قارن عدّات الصفوف:
غالبًا ما تُلام الانضمامات على "بطء الاستعلامات"، لكن السبب الحقيقي عادةً هو كمية البيانات التي تطلب من قاعدة البيانات دمجها، ومدى سهولة إيجاد الصفوف المطابقة.
فكر في الفهرس مثل فهرس كتاب. بدونه قد تضطر قاعدة البيانات لمسح الكثير من الصفوف للعثور على المطابقات. بوجود فهرس على مفتاح الانضمام (مثلاً customers.customer_id و orders.customer_id) يمكن لقارئ قاعدة البيانات القفز إلى الصفوف ذات الصلة بسرعة.
إذا كان عمود يُستخدم كثيرًا للمطابقة، فهو مرشح جيد للفهرسة.
عند الإمكان، انضم على معرفات فريدة ومستقرة:
customers.customer_id = orders.customer_idcustomers.email = orders.email أو customers.name = orders.nameالأسماء تتغير وقد تتكرر؛ الإيميلات قد تتغير أو تختلف بالصياغة. المعرفات مُصممة للمطابقة الثابتة وغالبًا ما تكون مفهرسة.
عادتاان بسيطتان تُسرّع الانضمامات بشكل ملحوظ:
SELECT * عند الانضمام.مثال: قيّد الطلبات أولًا ثم انضم:
SELECT c.customer_id, c.name, o.order_id, o.created_at
FROM customers c
JOIN (
SELECT order_id, customer_id, created_at
FROM orders
WHERE created_at >= DATE '2025-01-01'
) o
ON o.customer_id = c.customer_id;
إذا كنت تطوّر هذه الاستعلامات داخل تطبيق (مثلاً صفحة تقرير تعتمد على PostgreSQL)، يمكن لأدوات توليد الكود أن تسرع بناء البنية التحتية بينما تظل أنت متحكمًا في منطق الانضمام الذي يضمن الصحة.
NULL عند عدم وجودها)NULL عند عدم وجودها)NULLيُـجمع SQL JOIN صفوفًا من جدولين (أو أكثر) في نتيجة واحدة عن طريق مطابقة أعمدة مرتبطة — عادة مفتاح أساسي إلى مفتاح خارجي (مثال: customers.customer_id = orders.customer_id). هذه هي الطريقة لإعادة "ربط" الجداول المعيارية عندما تحتاج إلى تقارير أو تدقيقات أو تحليلات.
استخدم INNER JOIN عندما تريد فقط الصفوف التي توجد فيها العلاقة في كلتا الجدولين.
إنه مثالي للعلاقات المؤكدة، مثل عرض فقط العملاء الذين قاموا فعليًا بطلبات.
استخدم LEFT JOIN عندما تحتاج كل الصفوف من جدولك الرئيسي (اليسار) بالإضافة إلى البيانات المطابقة من اليمين إن وجدت.
للعثور على "الصفوف التي لا تتطابق": انضم ثم صفِّ الآن الجانب الأيمن ليكون NULL:
SELECT c.customer_id, c.name
FROM customers c
LEFT orders o o.customer_id c.customer_id
o.order_id ;
RIGHT JOIN يبقي كل الصفوف من الجدول الأيمن ويملأ أعمدة الجدول الأيسر بـ NULL عندما لا يوجد تطابق. يتجنبه كثير من الفرق لأنه يقرأ "بالعكس".
في معظم الحالات يمكنك إعادة كتابته كـ LEFT JOIN عبر تبديل ترتيب الجداول:
FROM payments p
LEFT JOIN orders o ON o.order_id = p.order_id
استخدم FULL OUTER JOIN للمطابقة والتسوية: تريد الصفوف المطابقة، وصفوف اليسار فقط، وصفوف اليمين فقط في نفس المخرَج.
مناسب تمامًا لمهمات التدقيق مثل "طلبات بدون مدفوعات" و"مدفوعات بدون طلبات" لأن الجانب غير المطابق يظهر كأعمدة NULL.
بعض قواعد البيانات (مثل MySQL و SQLite) لا تدعم FULL OUTER JOIN مباشرة. الحل الشائع هو دمج استعلامين:
orders LEFT JOIN paymentsعادةً ما يتم ذلك باستخدام UNION (أو UNION ALL مع فلترة مناسبة) للحصول على كل سجلات الجانب الأيسر واليميني.
يُرجع CROSS JOIN كل التوافيق الممكنة بين صفوف جدولين (حاصل ديكارتي). مفيد لتوليد سيناريوهات (مثل المقاسات × الألوان) أو شبكات تواريخ.
كن حذرًا: أعداد الصفوف تتضاعف بسرعة وقد تنفجر نتائج الاستعلام إذا كانت المدخلات كبيرة.
الـ SELF JOIN هو ربط الجدول بنفسه لربط صفوف داخل نفس الجدول (شائع في الهياكل الهرمية مثل الموظف → المدير).
يجب استخدام الأسماء المستعارة للتمييز بين النسختين، على سبيل المثال:
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id
ON يحدد كيف تتطابق الصفوف أثناء الانضمام؛ WHERE يصف أي الصفوف تُبقى بعد تكوين نتيجة الانضمام. مع LEFT JOIN، شرط في WHERE على جدول اليمين قد يُقصي صفوفًا بها NULL ويحوّل الانضمام عمليًا إلى INNER JOIN.
إذا أردت الاحتفاظ بكل صفوف اليسار وتقييد صفوف اليمين، ضع شرط تصفية جدول اليمين في بدلاً من .
تتضاعف الصفوف لأن الانضمام يُرجع كل زوج من الصفوف المتطابقة: في علاقة واحد-لكثير يظهر الكيان الواحد مرات بعدد المطابقات.
لتجنب العد المزدوج، لخص جانب "الكثير" أولًا (مثال: SUM أو COUNT مجمَّعًا حسب order_id) ثم انضم إلى النتيجة الملخّصة. استخدم DISTINCT فقط كحل أخير لأنه قد يخفي المشكلة ويُشوّه المجاميع.
ONWHERE