PostgreSQL: استعلامات الربط متعددة الجداول في PostgreSQL

آخر تحديث: 2026-08-26

1. ما ستتعلمه


2. القصة

Charlie هو مهندس خلفية في منصة SaaS. يطلب منه مدير المنتج إنشاء تقرير تفاصيل مشتريات المستخدم الذي يحتاج إلى دمج 4 جداول:

قد يكون لدى المستخدم طلبات متعددة؛ كل طلب له عناصر متعددة؛ كل عنصر يعين لمنتج واحد. يجب على Charlie اختيار نوع JOIN الصحيح لضمان عدم إسقاط المستخدمين بدون طلبات وعدم إنشاء جداء ديكارتي غير متوقع.


3. المفهوم

(1) نظرة عامة على أنواع JOIN

نوع JOIN المعنى أي جانب محتفظ به السيناريو النموذجي
INNER JOIN الاحتفاظ فقط بالصفوف المتطابقة لا جانب يتطلب تطابقًا للإدراج
LEFT JOIN الاحتفاظ بكل الجدول الأيسر الأيسر جدول رئيسي + صفوف مرتبطة اختيارية
RIGHT JOIN الاحتفاظ بكل الجدول الأيمن الأيمن نادر الاستخدام
FULL JOIN الاحتفاظ بكلا الجانبين كلاهما إيجاد الاختلافات / المطابقة
CROSS JOIN جداء ديكارتي غير مشروط التباديل / التوليفات
LATERAL JOIN استعلام فرعي يشير للجدول الأيسر Top-N لكل صف
100%
flowchart LR
    subgraph Inner
        A1((A)) --- B1((B))
    end
    subgraph Left
        A2((A)) --- B2((B))
        A3((A)) -.-> B3((∅))
    end
    subgraph Full
        A4((A)) --- B4((B))
        A5((A)) -.-> B6((∅))
        B5((∅)) -.-> A6((A))
    end

    style A1 fill:#c8e6c9
    style B1 fill:#c8e6c9
    style A2 fill:#c8e6c9
    style A3 fill:#c8e6c9
    style B2 fill:#c8e6c9
    style A4 fill:#c8e6c9
    style A5 fill:#c8e6c9
    style B4 fill:#c8e6c9

(2) INNER JOIN

يعيد فقط الصفوف التي تتطابق في كلا الجدولين.

(1) ▶ مثال

SQL
SELECT
  u.user_id,
  u.name,
  o.order_id,
  o.amount
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id;
TEXT 📖 للعرض فقط
 user_id | name  | order_id | amount
---------+-------+----------+--------
       1 | Alice |      101 |  15000
       1 | Alice |      102 |   8000
       2 | Bob   |      201 |  25000

المستخدمون الذين لم يقدموا طلبًا أبدًا لا يظهرون.

(3) LEFT JOIN

يحتفظ بجميع صفوف الجدول الأيسر؛ يملأ NULL على اليمين عندما لا يوجد تط��بق.

(2) ▶ مثال

SQL
SELECT
  u.user_id,
  u.name,
  o.order_id,
  o.amount
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id;
TEXT 📖 للعرض فقط
 user_id | name    | order_id | amount
---------+---------+----------+--------
       1 | Alice   |      101 |  15000
       1 | Alice   |      102 |   8000
       2 | Bob     |      201 |  25000
       3 | Charlie |          |

Charlie ليس لديه طلبات، لذا order_id و amount هما NULL.

(3) ▶ مثال

SQL
SELECT u.user_id, u.name
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
WHERE o.order_id IS NULL;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(4) RIGHT JOIN و FULL JOIN

(4) ▶ مثال

SQL
SELECT
  u.user_id,
  u.name,
  o.order_id,
  o.user_id AS order_user_id
FROM users u
FULL JOIN orders o ON u.user_id = o.user_id
ORDER BY u.user_id NULLS LAST, o.order_id NULLS LAST;
TEXT 📖 للعرض فقط
 user_id | name    | order_id | order_user_id
---------+---------+----------+---------------
       1 | Alice   |      101 |              1
       2 | Bob     |      201 |              2
       3 | Charlie |          |
         |         |      999 |             99

الطلب بـ user_id=99 ليس له مستخدم مطابق، والمستخدم بـ user_id=3 ليس له طلب — كلا الاختلافين مرئيان.

(5) ▶ مثال

SQL
SELECT o.order_id, o.user_id, u.name
FROM users u
RIGHT JOIN orders o ON u.user_id = o.user_id
WHERE u.user_id IS NULL;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
نوع JOIN السلوك مقارنة الاستخدام
LEFT JOIN الاحتفاظ بكل الجدول الأيسر استعلام رئيسي + مرتبط؛ إيجاد فجوات الجانب الأيسر
RIGHT JOIN الاحتفاظ بكل الجدول الأيمن يمكن إعادة كتابته كـ LEFT JOIN بتبديل الجوانب
FULL JOIN الاحتفاظ بكلا الجانبين المطابقة، إيجاد الاختلافات

(5) CROSS JOIN و NATURAL JOIN

(6) ▶ مثال

SQL
SELECT
  d.department_name,
  p.project_name
FROM departments d
CROSS JOIN projects p;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

3 أقسام × 5 مشاريع = 15 صفًا.

(7) ▶ مثال

SQL
SELECT * FROM users
NATURAL JOIN orders;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

NATURAL JOIN يربط تلقائيًا على الأعمدة التي لها نفس الاسم في كلا الجدولين. خطير: إذا شارك الجدولان عدة أعمدة بنفس الاسم (مثل created_at)، ينتج بصمت شروط AND إضافية. فضّل ON أو USING الصريحين.

الصيغة المزايا العيوب
NATURAL JOIN موجز تغيير اسم عمود قد يغير منطق الربط
USING(col) موجز وصريح فقط للربط المتساوي على أعمدة بنفس الاسم
ON a.col = b.col تحكم كامل مطول

(6) صيغة USING

(8) ▶ مثال

SQL
SELECT
  u.name,
  o.order_id,
  o.amount
FROM users u
JOIN orders o USING (user_id);

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

USING يدمج الأعمدة بنفس الاسم في عمود واحد؛ SELECT * لا يخرج user_id مرتين.

(7) الربط الذاتي

جدول مربوط مع نفسه، مفيد للهرميات أو مقارنات نفس الجدول.

(9) ▶ مثال

SQL
SELECT
  e.name AS employee,
  m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id;
TEXT 📖 للعرض فقط
 employee | manager
----------+---------
 Alice    | David
 Bob      | Alice
 Charlie  | Alice
 David    |

(10) ▶ مثال

SQL
SELECT
  a.product_name AS product_a,
  b.product_name AS product_b,
  a.price - b.price AS price_diff
FROM products a
JOIN products b ON a.category_id = b.category_id
  AND a.product_id < b.product_id
  AND ABS(a.price - b.price) < 100;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

4. النقاط الرئيسية

(1) الربط متعدد الجداول (3+ جداول)

متطلب Charlie: ربط users → orders → order_items → products.

(11) ▶ مثال

SQL
SELECT
  u.name            AS user_name,
  o.order_id,
  o.created_at      AS order_date,
  p.product_name,
  oi.quantity,
  oi.unit_price,
  oi.quantity * oi.unit_price AS line_total
FROM users u
JOIN orders o           ON u.user_id = o.user_id
JOIN order_items oi     ON o.order_id = oi.order_id
JOIN products p         ON oi.product_id = p.product_id
ORDER BY u.name, o.order_id, oi.order_item_id;
TEXT 📖 للعرض فقط
 user_name | order_id | order_date           | product_name | quantity | unit_price | line_total
-----------+----------+----------------------+--------------+----------+------------+------------
 Alice     |      101 | 2025-03-15 10:30:00  | Widget Pro   |        2 |      15000 |      30000
 Alice     |      101 | 2025-03-15 10:30:00  | Gadget Mini  |        5 |       3000 |      15000
 Alice     |      102 | 2025-04-02 14:20:00  | Widget Pro   |        1 |      15000 |      15000
 Bob       |      201 | 2025-05-10 09:00:00  | Server Rack  |        1 |      80000 |      80000

(2) LATERAL JOIN (ميزة PostgreSQL)

LATERAL يسمح لاستعلام فرعي بالإشارة إلى أعمدة من الجدول الأيسر — عمليًا، يعمل الاستعلام الفرعي مرة واحدة لكل صف من الجدول الأيسر.

(12) ▶ مثال

SQL
SELECT
  u.name,
  recent.order_id,
  recent.amount,
  recent.created_at
FROM users u
LEFT JOIN LATERAL (
  SELECT o.order_id, o.amount, o.created_at
  FROM orders o
  WHERE o.user_id = u.user_id
  ORDER BY o.created_at DESC
  LIMIT 3
) recent ON true
ORDER BY u.name, recent.created_at DESC;

Output:

TEXT 📖 للعرض فقط
CREATE TABLE
النهج يمكنه الإشارة للجدول الأيسر دعم Top-N الأداء
استعلام فرعي عادي لا يحتاج ROW_NUMBER نافذة مسح واحد
LATERAL نعم LIMIT مباشر استعلام فرعي يعمل لكل صف
دالة نافذة ROW_NUMBER + مرشح مسح واحد

(13) ▶ مثال

SQL
SELECT
  p.product_name,
  r.review_text,
  r.created_at
FROM products p
LEFT JOIN LATERAL (
  SELECT review_text, created_at
  FROM reviews r
  WHERE r.product_id = p.product_id
  ORDER BY created_at DESC
  LIMIT 1
) r ON true;

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

(3) أساسيات أداء JOIN

(14) ▶ مثال

SQL
EXPLAIN
SELECT u.name, o.order_id
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE o.amount > 50000;
TEXT 📖 للعرض فقط
 Hash Join
   Hash Cond: (o.user_id = u.user_id)
   ->  Seq Scan on orders
         Filter: (amount > 50000)
   ->  Hash
         ->  Seq Scan on users
استراتيجية JOIN متى تستخدم الخصائص
Nested Loop جدول صغير يقود جدول كبير جيد للشروط المتساوية المفهرسة
Hash Join ربط متساوٍ، بدون فهرس يبني جدول تجزئة؛ مفضل للأحجام الكبيرة
Merge Join بيانات مرتبة بالفعل يحتاج كلا الجانبين مرتبين

(15) ▶ مثال

SQL
EXPLAIN ANALYZE
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.email = o.customer_email;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

إذا لم يكن عمود email مفهرسًا، قد يتدهور إلى Nested Loop مسح جدول كامل.

(16) ▶ مثال

SQL
CREATE INDEX idx_orders_user_id ON orders(user_id);

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

5. تطبيق عملي

(17) ▶ مثال

SQL
SELECT
  u.user_id,
  u.name,
  COUNT(o.order_id) AS order_count
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
GROUP BY u.user_id, u.name
ORDER BY order_count DESC;

Output:

TEXT 📖 للعرض فقط
 count 
-------
     5
(1 row)

(18) ▶ مثال

SQL
SELECT
  a.user_id  AS system_a_id,
  a.email    AS system_a_email,
  b.user_id  AS system_b_id,
  b.email    AS system_b_email
FROM system_a_users a
FULL JOIN system_b_users b ON a.email = b.email
ORDER BY a.user_id NULLS LAST, b.user_id NULLS LAST;

Output:

TEXT 📖 للعرض فقط
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(19) ▶ مثال

SQL
SELECT DISTINCT
  a.name AS user_a,
  b.name AS user_b
FROM orders oa
JOIN users a ON oa.user_id = a.user_id
JOIN orders ob ON DATE(oa.created_at) = DATE(ob.created_at)
JOIN users b ON ob.user_id = b.user_id
WHERE a.user_id < b.user_id;

Output:

TEXT 📖 للعرض فقط
CREATE TABLE

6. مثال شامل

تقرير تفاصيل مشتريات المستخدم لـ Charlie — ربط 4 جداول + LATERAL للطلبات الحديثة:

SQL
SELECT
  u.name                  AS user_name,
  u.email,
  coalesce(order_summary.total_orders, 0)   AS total_orders,
  coalesce(order_summary.total_spent, 0)    AS total_spent,
  recent.order_id          AS latest_order_id,
  recent.created_at        AS latest_order_date,
  recent.amount            AS latest_amount
FROM users u
LEFT JOIN LATERAL (
  SELECT
    COUNT(*)    AS total_orders,
    SUM(amount) AS total_spent
  FROM orders o
  WHERE o.user_id = u.user_id
) order_summary ON true
LEFT JOIN LATERAL (
  SELECT order_id, created_at, amount
  FROM orders o
  WHERE o.user_id = u.user_id
  ORDER BY created_at DESC
  LIMIT 1
) recent ON true
ORDER BY total_spent DESC NULLS LAST;
TEXT 📖 للعرض فقط
 user_name | email              | total_orders | total_spent | latest_order_id | latest_order_date     | latest_amount
-----------+--------------------+--------------+-------------+-----------------+-----------------------+--------------
 Bob       | bob@example.com    |            5 |      320000 |             205 | 2025-11-20 16:00:00   |        85000
 Alice     | alice@example.com  |            3 |      180000 |             102 | 2025-04-02 14:20:00   |        15000
 Charlie   | charlie@example.com|            0 |           0 |                 |                       |

7. شجرة قرار اختيار JOIN

100%
flowchart TD
    A[تحتاج ربط جداول متعددة؟] -->|لا| Z[لا حاجة لـ JOIN]
    A -->|نعم| B{تحتاج الاحتفاظ<br/>بصفوف غير متطابقة؟}
    B -->|لا| C[INNER JOIN]
    B -->|نعم، احتفظ بالأيسر| D[LEFT JOIN]
    B -->|نعم، احتفظ بالأيمن| E[RIGHT JOIN]
    B -->|نعم، احتفظ بكليهما| F[FULL JOIN]
    C --> G{تحتاج Top-N<br/>أو إشارة لأعمدة يسرى؟}
    G -->|نعم| H[LATERAL JOIN]
    G -->|لا| I[INNER JOIN عادي]
    D --> G

    style H fill:#c8e6c9
    style F fill:#fff9c4

❓ أسئلة شائعة

س هل تصفية عمود الجدول الأيمن بـ WHERE بعد LEFT JOIN يحوله إلى INNER JOIN؟
ج نعم. WHERE o.col = 'x' يصفّي صفوف NULL في الجدول الأيمن، وهو ما يعادل INNER JOIN. انقل الشرط إلى جملة ON بدلاً من ذلك.
س هل يؤثر ترتيب JOINs متعددة الجداول على النتيجة؟
ج ترتيب INNER JOINs لا يؤثر على النتيجة (متكافئة منطقيًا)، لكن ترتيب LEFT JOINs يؤثر — الجانب الأيسر هو الجانب المحتفظ به ولا يمكن تبديله بحرية.
س ما الفرق بين USING و ON؟
ج USING(col) يتطلب أعمدة بنفس الاسم وربطًا متساويًا، ويدمج ذلك العمود في عمود إخراج واحد؛ ON أكثر مرونة، يدعم أسماء أعمدة مختلفة وشروطًا معقدة.
س كيف يختلف LATERAL عن الاستعلام الفرعي؟
ج الاستعلام الفرعي العادي لا يمكنه الإشارة إلى أعمدة من الجدول الأيسر في نفس مستوى FROM؛ LATERAL يمكنه — وهو عمليًا استعلام فرعي يعمل مرة واحدة لكل صف من الجدول الأيسر.
س ما فائدة CROSS JOIN فعليًا؟
ج توليد التباديل (مثل تاريخ × بُعد)، توليد التسلسلات، مصفوفات التقارير، وهكذا. لاحظ أن عدد صفوف النتيجة هو جداء حجمي الجدولين.
س لماذا لا ينصح بـ NATURAL JOIN؟
ج إنه يربط ضمنيًا على جميع الأعمدة بنفس الاسم؛ إضافة عمود بنفس الاسم يغير منطق الربط ويسبب أخطاء يصعب تتبعها. اكتب ON أو USING صريحًا للأمان.

📖 ملخص


📝 تمارين

  1. ⭐ اكتب استعلام INNER JOIN يربط orders و order_items، ويخرج الكمية الإجمالية (SUM quantity) لكل طلب.
  2. ⭐ استخدم LEFT JOIN لإيجاد المنتجات التي لم يتم شراؤها أبدًا (products LEFT JOIN order_items، مرشح لـ NULL).
  3. ⭐⭐ اربط users → orders → order_items → products وأخرج تفاصيل مشتريات كل مستخدم، بما في ذلك اسم المنتج ومبلغ السطر.
  4. ⭐⭐ استخدم LATERAL JOIN لإيجاد أغلى منتجين في كل فئة منتجات.
  5. ⭐⭐⭐ اكتب عبارة SQL باستخدام FULL JOIN لمقارنة جداول مستخدمي نظامين (system_a_users / system_b_users)، مع وضع علامة على السجلات الموجودة فقط في A، فقط في B، أو في كليهما، وعد كم يقع في كل فئة.
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%