أداء EXISTS مقابل JOIN
اختر النمط الأسرع
أداء EXISTS مقابل JOIN درس مجاني في SQL Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في SQL Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة SQL Academy 4 دروس في المجموع.
لماذا يهم الأداء هنا
عندما تحتاجون إلى التحقق من وجود صفوف ذات صلة في جدول آخر، يوفر لكم SQL عدة أدوات: EXISTS وIN وJOIN. تنتج كل أداة نتائج صحيحة، لكنها قد تختلف كثيرًا في الأداء اعتمادًا على حجم البيانات والفهارس ومحرك قاعدة البيانات.
ستتعلمون في هذا الدرس كيفية عمل كل أسلوب داخليًا، ومتى تختارون كل واحد منها.
الجداول النموذجية
سنستخدم جدولين طوال هذا الدرس: customers وorders. قد يكون للعميل صفر من الطلبات أو العديد منها. وهذه علاقة تقليدية من نوع واحد إلى متعدد، ومناسبة تمامًا لاختبار أنماط EXISTS مقارنةً بـ JOIN.
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id),
total NUMERIC(10,2)
);
INSERT INTO customers (name) VALUES
('Alice'), ('Bob'), ('Carol'), ('Dave');
INSERT INTO orders (customer_id, total) VALUES
(1, 120.00), (1, 85.50), (3, 200.00);أسلوب JOIN
من الأنماط الشائعة استخدام INNER JOIN للعثور على العملاء الذين لديهم طلب واحد على الأقل. ينجح هذا الأسلوب، لكن لاحظوا المشكلة: إذا كان لدى العميل خمسة طلبات، فسيظهر خمس مرات في مجموعة النتائج قبل أن يزيل DISTINCT التكرارات.
يمثل هذا التكرار عملًا إضافيًا يجب أن تنفذه قاعدة البيانات — إذ تنشئ نتيجة JOIN الكاملة ثم تزيل التكرارات.
SELECT DISTINCT c.id, c.name
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;أسلوب EXISTS
يجيب EXISTS عن سؤال بنعم أو لا: هل يوجد صف مطابق واحد على الأقل؟ فبمجرد أن يعثر المحرك على أول تطابق، يتوقف عن الفحص — ويُسمى ذلك التقييم قصير الدائرة.
لا ينتج أي تكرار، ولا حاجة إلى DISTINCT، لأن EXISTS لا يُعيد الصفوف الداخلية فعليًا.
SELECT c.id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);التقييم قصير الدائرة هو الأساس
يعني التقييم قصير الدائرة أن الاستعلام الفرعي يتوقف بمجرد العثور على صف واحد مستوفٍ للشروط. وسواء كان لدى العميل طلب واحد أو 10,000 طلب، فإن EXISTS يقرأ حتى أول تطابق فقط.
يجب على JOIN قراءة جميع الصفوف المطابقة لإنشاء مجموعة النتائج، حتى عندما تهتمون بالوجود فقط. ومع الجداول العريضة التي تحتوي على العديد من الصفوف الفرعية لكل صف أب، يتضاعف أثر هذا الفرق بسرعة.
-- EXISTS stops after finding row #1
SELECT c.name
FROM customers c
WHERE EXISTS (
SELECT 1 -- 'SELECT 1' is conventional; the value does not matter
FROM orders o
WHERE o.customer_id = c.id
);
-- JOIN scans ALL matching order rows
SELECT DISTINCT c.name
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;مقارنة NOT EXISTS بـ LEFT JOIN ... IS NULL
للتحقق العكسي — أي العثور على العملاء الذين ليس لديهم أي طلبات — يمكنكم استخدام NOT EXISTS أو نمط LEFT JOIN ... WHERE IS NULL. كلاهما شائع، لكن NOT EXISTS عادةً أوضح، وغالبًا ما يفضله المُحسِّن.
-- NOT EXISTS
SELECT c.id, c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
-- LEFT JOIN ... IS NULL (equivalent result)
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;دور الفهارس
يستفيد كل من EXISTS وJOIN بدرجة كبيرة من وجود فهرس على عمود المفتاح الأجنبي. ومن دون فهرس على orders.customer_id، يؤدي كل صف خارجي إلى فحص كامل لجدول orders.
غالبًا ما تكون إضافة هذا الفهرس أكبر تحسين منفرد للأداء — وأهم تأثيرًا من الاختيار بين EXISTS وJOIN.
-- Create an index on the foreign key
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
-- Now both patterns use an index lookup instead of a full scan
EXPLAIN
SELECT c.name
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);قراءة مخرجات EXPLAIN
استخدموا EXPLAIN (أو EXPLAIN ANALYZE لتشغيل الاستعلام أيضًا) لمعرفة كيفية تنفيذ قاعدة البيانات للاستعلام. ابحثوا عن المؤشرات التالية:
- Index Scan — جيد؛ فهذا يعني استخدام الفهرس.
- Seq Scan على جدول كبير — قد يكون مؤشرًا مقلقًا؛ وربما يساعد وجود فهرس.
- Hash Join / Nested Loop — خوارزمية JOIN التي جرى اختيارها؛ ويتوافق Nested Loop جيدًا مع عمليات فحص الفهارس.
EXPLAIN ANALYZE
SELECT c.name
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
HAVING COUNT(o.id) > 0;متى يكون JOIN أفضل
يتفوق EXISTS في عمليات التحقق من الوجود فقط. لكن إذا كنتم تحتاجون أيضًا إلى بيانات من الجدول ذي الصلة — مثل إجمالي الطلب أو تاريخ الطلب — فيجب عليكم استخدام JOIN. فلا توجد طريقة لإعادة أعمدة من داخل استعلام EXISTS الفرعي.
اختاروا الأداة التي تناسب السؤال: EXISTS للإجابة عن «هل يوجد؟»، وJOIN للإجابة عن «أعطني بيانات من كلا الجدولين».
-- Need order data? JOIN is the only option.
SELECT c.name, o.total, o.id AS order_id
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id
ORDER BY c.name;IN مقابل EXISTS مع المجموعات الكبيرة
يقيّم IN (subquery) الاستعلام الفرعي بالكامل أولًا، ثم ينشئ قائمة قيم في الذاكرة، وبعد ذلك يتحقق من كل صف خارجي مقابل تلك القائمة. ومع وجود ملايين الصفوف، قد تستنفد هذه القائمة الذاكرة.
يُقيّم EXISTS صفًا تلو الآخر ويتوقف عند العثور على أول تطابق، ولذلك لا ينشئ أبدًا مجموعة النتائج الداخلية كاملة في الذاكرة. وعند إجراء عمليات تحقق مترابطة على مجموعات كبيرة، يكون EXISTS أسرع من IN في معظم الحالات.
-- IN builds the full list first
SELECT name
FROM customers
WHERE id IN (
SELECT customer_id FROM orders
);
-- EXISTS evaluates per-row and short-circuits
SELECT name
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);ورقة مرجعية لاتخاذ القرار
إليك مرجعًا سريعًا لاختيار النمط المناسب:
- EXISTS — عندما تحتاج فقط إلى معرفة ما إذا كان هناك تطابق؛ أو عند التعامل مع جداول فرعية كبيرة؛ استخدم NOT EXISTS للربط المضاد.
- JOIN — عندما تحتاج إلى أعمدة من الجدول المرتبط، أو إلى إجراء تجميعات عبر كلا الجدولين.
- IN — لقوائم القيم القصيرة والثابتة مثل
WHERE status IN ('active', 'pending')؛ وتجنّبه مع الاستعلامات الفرعية الكبيرة. - Always index عمود المفتاح الخارجي — فهذا أهم من اختيار الصياغة.
تحقق سريع
أي عبارة تشرح على أفضل نحو سبب إمكانية كون EXISTS أسرع من INNER JOIN + DISTINCT عند التحقق من وجود صفوف مرتبطة؟
مراجعة الدرس
في هذا الدرس تعلمتم كيفية الاختيار بين EXISTS وJOIN لتحسين أداء SQL:
- يتوقف EXISTS عند أول تطابق — إذ يتوقف عن الفحص بمجرد العثور على أول تطابق، متجنبًا التكرارات من دون الحاجة إلى DISTINCT.
- يعيد JOIN جميع الصفوف المتطابقة — استخدموه عندما تحتاجون إلى بيانات من الجدول المرتبط، ولكن أضيفوا DISTINCT أو GROUP BY إذا كنتم تهتمون فقط بصف الجدول الأساسي.
- يُعد NOT EXISTS نمطًا واضحًا للربط المضاد؛ أما LEFT JOIN ... IS NULL فهو مكافئ له لكنه أكثر إسهابًا.
- تجنبوا IN مع الاستعلامات الفرعية الكبيرة — إذ ينشئ نتيجة الاستعلام الداخلي كاملة في الذاكرة، بينما يكون EXISTS أكثر كفاءة في استخدام الذاكرة.
- فهرسوا مفاتيحكم الخارجية — فهذه الخطوة وحدها تحقق غالبًا أكبر مكسب في الأداء، بصرف النظر عن الصياغة التي تختارونها.
- استخدموا EXPLAIN / EXPLAIN ANALYZE للتحقق من خطة التنفيذ والتأكد من استخدام الفهارس.
الأسئلة الشائعة
هل درس «أداء EXISTS مقابل JOIN» مجاني؟
نعم — نص درس «أداء EXISTS مقابل JOIN» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة SQL Academy، انتقل إلى CoddyKit PRO. تتضمن دورة SQL Academy 4 دروس في المجموع.
ماذا ستتعلم في «أداء EXISTS مقابل JOIN»؟
اختر النمط الأسرع تتمرن على SQL Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ SQL Academy؟
لا تُشترط خبرة سابقة. SQL Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «أداء EXISTS مقابل JOIN»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس SQL Academy هذا؟
نعم. كل درس في SQL Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- الاستعلامات الفرعية المترابطة
- EXISTS وNOT EXISTS
- IN مقابل ANY مقابل ALL
- أداء EXISTS مقابل JOIN