PostgreSQL: عمليات المجموعات والاستعلامات المدمجة في…
آخر تحديث: 2026-08-26
1. ما ستتعلمه
- UNION (دمج مع إزالة التكرار)
- UNION ALL (دمج مع الحفاظ على التكرار)
- INTERSECT / INTERSECT ALL (التقاطع)
- EXCEPT / EXCEPT ALL (الفرق)
- ORDER BY في عمليات المجموعات
- معالجة NULL في عمليات المجموعات
- اختيار عمليات المجموعات مقابل JOIN
2. القصة
Bob هو محلل بيانات في منصة تجارة إلكترونية. المدير التنفيذي يسأله ثلاثة أسئلة:
- أي المنتجات كانت الأكثر مبيعاً في الربع الأول والثاني والثالث؟ (UNION ALL للدمج)
- أي المنتجات كانت الأكثر مبيعاً في كل ربع سنة؟ (INTERSECT = دائمة الخضرة)
- أي المنتجات باعت جيداً في الربع ا��أول لكن ليس في الربع الثاني؟ (EXCEPT = المتراجعة)
يدرك Bob أن هذه الأسئلة تتطابق تماماً مع عمليات المجموعات الثلاث في SQL: UNION، INTERSECT، EXCEPT.
3. المفهوم
(1) نظرة عامة على عمليات المجموعات
| العملية | المعنى | إزالة التكرار | التشبيه |
|---|---|---|---|
| UNION | دمج مجموعات النتائج | نعم | A ∪ B |
| UNION ALL | دمج مجموعات النتائج | لا | A ∪ B (مع التكرارات) |
| INTERSECT | التقاطع | نعم | A ∩ B |
| INTERSECT ALL | التقاطع | لا | A ∩ B (مع عدد التكرارات) |
| EXCEPT | في A لكن ليس ف�� B | نعم | A - B |
| EXCEPT ALL | في A لكن ليس في B | لا | A - B (مع عدد التكرارات) |
flowchart TD
subgraph Union
U1((A)) --- U2((B))
U1 & U2 --> U3["A ∪ B"]
end
subgraph Intersect
I1((A)) --- I2((B))
I1 ∩ I2 --> I3["A ∩ B"]
end
subgraph Except
E1((A)) --- E2((B))
E1 - E2 --> E3["A - B"]
end
style U3 fill:#c8e6c9
style I3 fill:#e1f5fe
style E3 fill:#fff9c4
(2) UNION و UNION ALL
(1) ▶ مثال
SELECT product_id, product_name FROM hot_products_q1
UNION
SELECT product_id, product_name FROM hot_products_q2
UNION
SELECT product_id, product_name FROM hot_products_q3
ORDER BY product_id;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
UNION تزيل التكرار تلقائياً: إذا كان منتج الأكثر مبيعاً في كل من الربع الأول والثاني، يظهر مرة واحدة فقط.
(2) ▶ مثال
SELECT product_id, product_name, 'Q1' AS quarter FROM hot_products_q1
UNION ALL
SELECT product_id, product_name, 'Q2' AS quarter FROM hot_products_q2
UNION ALL
SELECT product_id, product_name, 'Q3' AS quarter FROM hot_products_q3
ORDER BY quarter, product_id;
product_id | product_name | quarter
------------+--------------+---------
101 | Widget Pro | Q1
102 | Gadget Mini | Q1
101 | Widget Pro | Q2
103 | Server Rack | Q2
101 | Widget Pro | Q3
104 | Cable Max | Q3
| السيناريو | الموصى به | السبب |
|---|---|---|
| دمج مصادر مختلفة بدون تكرارات | UNION ALL | لا حاجة لإزالة التكرار، أسرع |
| دمج مع تكرارات محتملة يجب إزالتها | UNION | إزالة تلقائية للتكرار |
| دمج ووسم المصدر | UNION ALL + عمود وسم | الاحتفاظ بالتكرارات وتمييز المصادر |
(3) ▶ مثال
SELECT 'NA' AS region, COUNT(*) AS order_count, SUM(amount) AS total FROM orders_na
UNION ALL
SELECT 'EU', COUNT(*), SUM(amount) FROM orders_eu
UNION ALL
SELECT 'APAC', COUNT(*), SUM(amount) FROM orders_apac;
Output:
count
-------
5
(1 row)
(3) INTERSECT و INTERSECT ALL
(4) ▶ مثال
SELECT product_id, product_name FROM hot_products_q1
INTERSECT
SELECT product_id, product_name FROM hot_products_q2
INTERSECT
SELECT product_id, product_name FROM hot_products_q3;
product_id | product_name
------------+--------------
101 | Widget Pro
Widget Pro هو المنتج الدائم الخضرة الوحيد الذي كان الأكثر مبيعاً كل ربع سنة.
(5) ▶ مثال
لنفترض أن المنتج 101 يظهر مرتين في قائمة الأكثر مبيعاً للربع الأول، مرة واحدة في الربع الثاني، و3 مرات في الربع الثالث:
SELECT product_id FROM hot_products_q1_detail
INTERSECT ALL
SELECT product_id FROM hot_products_q2_detail;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
INTERSECT ALL تعيد MIN(عدد التكرارات): المنتج 101 يعيد min(2, 1) = صف واحد.
| العملية | إزالة التكرار | معالجة الصفوف المكررة | الاستخدام النموذجي |
|---|---|---|---|
| INTERSECT | نعم | الاحتفاظ بصف واحد فقط | إيجاد العناصر المشتركة |
| INTERSECT ALL | لا | الاحتفاظ بأقل عدد تكرارات | مطابقة تردد التكرار بدقة |
(6) ▶ مثال
SELECT customer_id FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2023
INTERSECT
SELECT customer_id FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2024
INTERSECT
SELECT customer_id FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2025;
Output:
CREATE TABLE
العملاء المخلصون الذين قدموا طلبات في جميع السنوات الثلاث المتتالية.
(4) EXCEPT و EXCEPT ALL
(7) ▶ مثال
SELECT product_id, product_name FROM hot_products_q1
EXCEPT
SELECT product_id, product_name FROM hot_products_q2;
product_id | product_name
------------+--------------
102 | Gadget Mini
Gadget Mini كان الأكثر مبيعاً في الربع الأول لكنه غاب عن قائمة الربع الثاني — تراجع.
(8) ▶ مثال
SELECT product_id, product_name FROM hot_products_q2
EXCEPT
SELECT product_id, product_name FROM hot_products_q1;
product_id | product_name
------------+--------------
103 | Server Rack
Server Rack دخل حديثاً قائمة الأكثر مبيعاً في الربع الثاني.
(9) ▶ مثال
SELECT product_id FROM order_items_2024
EXCEPT ALL
SELECT product_id FROM order_items_2025;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
إذا ظهر المنتج 101 خمس مرات في 2024 وثلاث مرات في 2025، EXCEPT ALL تعيد 5 - 3 = صفين.
| العملية | إزالة التكرار | معالجة الصفوف المكررة | الاستخدام النموذجي |
|---|---|---|---|
| EXCEPT | نعم | الاحتفاظ بصف واحد فقط | إيجاد الاختلافات |
| EXCEPT ALL | لا | الاحتفاظ بفارق عدد التكرارات | حساب الفائض الدقيق |
4. نقاط رئيسية
(1) قواعد عمليات المجموعات
القاعدة الأولى: يجب أن يكون عدد الأعمدة متساوياً.
SELECT id, name FROM table_a
UNION
SELECT id, name, price FROM table_b;
ERROR: each UNION query must have the same number of columns
القاعدة الثانية: يجب أن تكون أنواع الأعمدة المتقابلة متوافقة.
| القاعدة | المتطلب | نتيجة المخالفة |
|---|---|---|
| عدد الأعمدة | يجب أن يكون متساوياً | خطأ ترجمة |
| نوع العمود | يجب أن يكون متوافقاً | تحويل ضمني أو خطأ |
| اسم العمود | يأخذ اسم عمود الاستعلام الأول | انتبه للأسماء المستعارة |
(10) ▶ مثال
SELECT product_id, product_name AS name FROM products_active
UNION ALL
SELECT sku AS product_id, title AS name FROM products_legacy
ORDER BY product_id;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) موضع ORDER BY في عمليات المجموعات
ORDER BY قد تظهر فقط بعد الاستعلام الأخير وتنطبق على مجموعة النتائج بالكامل.
(11) ▶ مثال
SELECT product_id, product_name FROM hot_products_q1
UNION ALL
SELECT product_id, product_name FROM hot_products_q2
ORDER BY product_id;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(12) ▶ مثال
(SELECT product_id FROM hot_products_q1
EXCEPT
SELECT product_id FROM hot_products_q2)
UNION ALL
(SELECT product_id FROM hot_products_q2
EXCEPT
SELECT product_id FROM hot_products_q3)
ORDER BY product_id;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
احسب الفرق أولاً، ثم ادمج. بدون أقواس، UNION لها أولوية أقل من INTERSECT/EXCEPT.
| العملية | الأولوية | التجميع |
|---|---|---|
| INTERSECT | الأعلى | من اليسار لليمين |
| EXCEPT | المتوسط | من اليسار لليمين |
| UNION / UNION ALL | المتوسط | من اليسار لليمين |
(3) معالجة NULL في عمليات المجموعات
تعامل عمليات المجموعات NULL كمتساوية (على عكس المقارنات العادية حيث NULL <> NULL).
(13) ▶ مثال
SELECT NULL AS val
UNION
SELECT NULL AS val;
val
-----
(1 row)
NULLs المعاملتان تعاملان كمتطابقتين؛ UNION تزيل التكرار إلى صف واحد.
(14) ▶ مثال
SELECT NULL AS val
INTERSECT
SELECT NULL AS val;
val
-----
(1 row)
NULL تطابق NULL؛ INTERSECT تعيد صفاً واحداً.
| السيناريو | سلوك NULL | الفرق عن المقارنة العادية |
|---|---|---|
| UNION | NULLs تعامل كمتساوية، تزال تكراراتها | NULL = NULL العادية هي UNKNOWN |
| INTERSECT | NULLs تعامل كمتساوية، تتطابق | NULL = NULL العادية هي UNKNOWN |
| EXCEPT | NULLs تعامل كمتساوية، تلغى | NULL <> NULL العادية هي UNKNOWN |
(4) عمليات المجموعات مقابل JOIN
(15) ▶ مثال
SELECT a.product_id
FROM hot_products_q1 a
INNER JOIN hot_products_q2 b ON a.product_id = b.product_id;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
مكافئة لـ:
SELECT product_id FROM hot_products_q1
INTERSECT
SELECT product_id FROM hot_products_q2;
| البعد | عملية المجموعات | JOIN |
|---|---|---|
| الدلالات | عملية مجموعات على الصفوف | دمج الأعمدة |
| أعمدة المخرجات | تأخذ أعمدة الجانب الأيسر | أعمدة كلا الجدولين متاحة |
| إزالة التكرار | UNION/INTERSECT/EXCEPT تزيل تلقائياً | تحتاج DISTINCT يدوياً |
| مطابقة NULL | NULL = NULL | NULL <> NULL |
| الأداء | البيانات الكبيرة قد ترتب لإزالة التكرار | Hash Join المفهرس قد يكون أسرع |
| الأفضل لـ | دمج/تقاطع/فرق مجموعات نتائج متشابهة الهيكل | ضم جداول غير متجانسة لجلب الأعمدة |
5. تطبيق عملي
(16) ▶ مثال
SELECT
order_id AS transaction_id,
amount AS credit,
0 AS debit,
'order' AS type,
created_at
FROM orders
UNION ALL
SELECT
refund_id,
0,
refund_amount,
'refund',
created_at
FROM refunds
ORDER BY created_at;
Output:
CREATE TABLE
(17) ▶ مثال
SELECT user_id, email FROM registered_users
EXCEPT
SELECT user_id, email FROM activated_users;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(18) ▶ مثال
SELECT customer_id FROM order_items WHERE product_id = 101
INTERSECT
SELECT customer_id FROM order_items WHERE product_id = 102;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(19) ▶ مثال
SELECT 'retained' AS status, COUNT(*) AS cnt FROM (
SELECT customer_id FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2024
INTERSECT
SELECT customer_id FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2025
) t
UNION ALL
SELECT 'churned', COUNT(*) FROM (
SELECT customer_id FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2024
EXCEPT
SELECT customer_id FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2025
) t;
Output:
count
-------
5
(1 row)
(20) ▶ مثال
SELECT
product_id,
SUM(CASE WHEN quarter = 'Q1' THEN 1 ELSE 0 END) AS q1_count,
SUM(CASE WHEN quarter = 'Q2' THEN 1 ELSE 0 END) AS q2_count,
SUM(CASE WHEN quarter = 'Q3' THEN 1 ELSE 0 END) AS q3_count
FROM (
SELECT product_id, 'Q1' AS quarter FROM hot_products_q1
UNION ALL
SELECT product_id, 'Q2' FROM hot_products_q2
UNION ALL
SELECT product_id, 'Q3' FROM hot_products_q3
) combined
GROUP BY product_id
ORDER BY product_id;
Output:
count
-------
5
(1 row)
6. مثال شامل
تحليل Bob للمنتجات الأكثر مبيعاً عبر الأرباع — مخرج واحد للمنتجات الدائمة الخضرة والجديدة والمتراجعة:
WITH q1 AS (
SELECT product_id, product_name FROM hot_products WHERE quarter = 'Q1'
),
q2 AS (
SELECT product_id, product_name FROM hot_products WHERE quarter = 'Q2'
),
q3 AS (
SELECT product_id, product_name FROM hot_products WHERE quarter = 'Q3'
),
evergreen AS (
SELECT product_id, product_name, 'evergreen' AS trend FROM q1
INTERSECT
SELECT product_id, product_name, 'evergreen' FROM q2
INTERSECT
SELECT product_id, product_name, 'evergreen' FROM q3
),
new_q2 AS (
SELECT product_id, product_name, 'new_in_q2' AS trend FROM q2
EXCEPT
SELECT product_id, product_name, 'new_in_q2' FROM q1
),
new_q3 AS (
SELECT product_id, product_name, 'new_in_q3' AS trend FROM q3
EXCEPT
SELECT product_id, product_name, 'new_in_q3' FROM q2
),
churned_q2 AS (
SELECT product_id, product_name, 'churned_in_q2' AS trend FROM q1
EXCEPT
SELECT product_id, product_name, 'churned_in_q2' FROM q2
),
churned_q3 AS (
SELECT product_id, product_name, 'churned_in_q3' AS trend FROM q2
EXCEPT
SELECT product_id, product_name, 'churned_in_q3' FROM q3
)
SELECT * FROM evergreen
UNION ALL
SELECT * FROM new_q2
UNION ALL
SELECT * FROM new_q3
UNION ALL
SELECT * FROM churned_q2
UNION ALL
SELECT * FROM churned_q3
ORDER BY trend, product_id;
product_id | product_name | trend
------------+--------------+---------------
101 | Widget Pro | evergreen
103 | Server Rack | new_in_q2
104 | Cable Max | new_in_q3
102 | Gadget Mini | churned_in_q2
103 | Server Rack | churned_in_q3
7. تدفق تنفيذ عمليات المجموعات
flowchart TD
A["الاستعلام A"] --> C{العملية}
B["الاستعلام B"] --> C
C -->|UNION| D["دمج + إزالة التكرار"]
C -->|UNION ALL| E["دمج (الحفاظ على التكرارات)"]
C -->|INTERSECT| F["مطابقة + إزالة التكرار"]
C -->|EXCEPT| G["A - B + إزالة التكرار"]
D --> H["ORDER BY (اختياري)"]
E --> H
F --> H
G --> H
H --> I["النتيجة النهائية"]
style D fill:#c8e6c9
style F fill:#e1f5fe
style G fill:#fff9c4
| الخطوة | العملية | الوصف |
|---|---|---|
| 1 | تنفيذ كل استعلام فرعي | ينفذ بشكل مستقل؛ مجموعات النتائج يجب أن تشترك في الهيكل |
| 2 | عملية المجموعات | UNION/INTERSECT/EXCEPT |
| 3 | إزالة التكرار (إذا لزم) | UNION/INTERSECT/EXCEPT تزيل التكرار افتراضياً |
| 4 | ORDER BY | تنطبق على مجموعة النتائج النهائية |
| 5 | LIMIT | تحديد صفوف المخرجات النهائية |
❓ أسئلة شائعة
SELECT * FROM (A UNION B) AS t صالحة؛ ضعها بين أقواس وأضف اسماً مستعار��ً.📖 ملخص
- UNION تدمج وتزيل التكرار؛ UNION ALL تدمج وتحافظ على التكرارات (أداء أفضل)
- INTERSECT تأخذ التقاطع؛ EXCEPT تأخذ الفرق
- اللاحقة ALL تحافظ على عدد التكرارات: INTERSECT ALL / EXCEPT ALL
- عمليات المجموعات تتطلب نفس عدد الأعمدة وأنواع متوافقة
- ORDER BY قد تظهر فقط في نهاية العبارة
- NULL تعامل كمتساوية في عمليات المجموعات (على عكس المقارنات العادية)
- الأولوية: INTERSECT = EXCEPT > UNION؛ استخدم الأقواس للتحكم بها
- عمليات المجموعات تناسب دمج/تقاطع/فرق مجموعات نتائج متشابهة الهيكل؛ JOIN تناسب ضم جداول غير متجانسة
📝 تمارين
- ⭐ استخدم UNION ALL لدمج جداول طلبات 2024 و 2025، مضيفاً عمود وسم السنة، مرتباً تنازلياً حسب المبلغ.
- ⭐ استخدم EXCEPT لإيجاد المستخدمين الموجودين في جدول customers لكن ليس في جدول active_users.
- ⭐⭐ استخدم INTERSECT لإيجاد معرفات المنتجات الأكثر مبيعاً التي تظهر في جميع الأرباع الثلاثة، واضمم جدول products لإخراج أسماء المنتجات.
- ⭐⭐ استخدم CTE + EXCEPT لتحليل تراجع العملاء: العملاء الذين لديهم طلبات في 2024 لكن لا شيء في 2025.
- ⭐⭐⭐ اكتب عبارة SQL واحدة تجمع UNION ALL + INTERSECT + EXCEPT: أخرج تقريراً يغطي ثلاث فئات منتجات (دائمة الخضرة / جديدة / متراجعة)، كل منها بعمود وسم، وأخيراً استخدم GROUP BY لعد المنتجات في كل فئة.