تطبيق: تحليل البيانات

تطبيق: تحليل البيانات

📋 متطلبات المشروع

يغطي هذا الدرس سيناريوهين عمليين — تحليل تقارير المبيعات وإحصائيات أداء الموظفين — مع تطبيق الدوال التجميعية، واستعلامات التجميع، والتعبيرات الشرطية، وJOINs، والاستعلامات الفرعية التي تعلمتها في الدروس السابقة.

هيكل جداول قاعدة البيانات

نستخدم قاعدة بيانات موحدة من 4 جداول:

SQL
-- جدول الأقسام
CREATE TABLE departments (
    department_id   INT PRIMARY KEY,
    department_name VARCHAR(50) NOT NULL,
    city            VARCHAR(50),
    budget          DECIMAL(12,2)
);

-- جدول الموظفين
CREATE TABLE employees (
    employee_id   INT PRIMARY KEY,
    first_name    VARCHAR(50) NOT NULL,
    last_name     VARCHAR(50) NOT NULL,
    email         VARCHAR(100),
    salary        DECIMAL(10,2),
    department_id INT,
    hire_date     DATE,
    FOREIGN KEY (department_id) REFERENCES departments(department_id)
);

-- جدول الطلبات
CREATE TABLE orders (
    order_id     INT PRIMARY KEY,
    customer_id  INT,
    employee_id  INT,
    order_date   DATE,
    total_amount DECIMAL(12,2),
    status       VARCHAR(20),
    FOREIGN KEY (employee_id) REFERENCES employees(employee_id)
);

-- جدول المنتجات
CREATE TABLE products (
    product_id   INT PRIMARY KEY,
    product_name VARCHAR(100) NOT NULL,
    price        DECIMAL(10,2),
    stock        INT,
    department_id INT,
    FOREIGN KEY (department_id) REFERENCES departments(department_id)
);

بيانات الاختبار

SQL
-- بيانات الأقسام
INSERT INTO departments VALUES (1, 'Technology', 'Beijing', 500000);
INSERT INTO departments VALUES (2, 'Sales', 'Shanghai', 300000);
INSERT INTO departments VALUES (3, 'Marketing', 'Guangzhou', 200000);
INSERT INTO departments VALUES (4, 'Finance', 'Shenzhen', 150000);

-- بيانات الموظفين
INSERT INTO employees VALUES (1, 'Zhang', 'Wei', 'zhangwei@co.com', 12000, 1, '2023-03-15');
INSERT INTO employees VALUES (2, 'Li', 'Na', 'lina@co.com', 9500, 1, '2023-06-20');
INSERT INTO employees VALUES (3, 'Wang', 'Qiang', 'wangqiang@co.com', 8000, 2, '2022-11-10');
INSERT INTO employees VALUES (4, 'Zhao', 'Min', 'zhaomin@co.com', 11000, 2, '2024-01-05');
INSERT INTO employees VALUES (5, 'Liu', 'Yang', 'liuyang@co.com', 7500, 3, '2024-04-18');
INSERT INTO employees VALUES (6, 'Chen', 'Jing', 'chenjing@co.com', 9000, NULL, '2025-02-28');

-- بيانات الطلبات
INSERT INTO orders VALUES (1001, 101, 3, '2026-01-10', 2500.00, 'completed');
INSERT INTO orders VALUES (1002, 102, 3, '2026-02-15', 1800.00, 'completed');
INSERT INTO orders VALUES (1003, 103, 4, '2026-02-20', 3200.00, 'shipped');
INSERT INTO orders VALUES (1004, 101, 1, '2026-03-05', 950.00, 'pending');
INSERT INTO orders VALUES (1005, 104, 4, '2026-03-10', 4100.00, 'completed');
INSERT INTO orders VALUES (1006, 105, 3, '2026-04-01', 2800.00, 'cancelled');
INSERT INTO orders VALUES (1007, 101, 1, '2026-04-15', 5600.00, 'completed');
INSERT INTO orders VALUES (1008, 106, 4, '2026-05-02', 1500.00, 'completed');
INSERT INTO orders VALUES (1009, 102, 3, '2026-05-20', 3800.00, 'shipped');
INSERT INTO orders VALUES (1010, 107, 1, '2026-06-01', 2200.00, 'pending');
INSERT INTO orders VALUES (1011, 103, 4, '2026-06-10', 6300.00, 'completed');
INSERT INTO orders VALUES (1012, 108, 3, '2026-06-15', 1900.00, 'completed');

-- بيانات المنتجات
INSERT INTO products VALUES (1, 'Laptop', 6999.00, 50, 1);
INSERT INTO products VALUES (2, 'Wireless Mouse', 129.00, 200, 1);
INSERT INTO products VALUES (3, 'Office Desk', 899.00, 30, 2);
INSERT INTO products VALUES (4, 'Projector', 3500.00, 15, 3);
INSERT INTO products VALUES (5, 'Printer', 2200.00, 25, 1);
INSERT INTO products VALUES (6, 'Meeting Chair', 599.00, 40, 2);
INSERT INTO products VALUES (7, 'Whiteboard', 450.00, 60, 3);

🏗️ تمارين الاستعلام

الاستعلام 1: تقرير أداء مبيعات الموظفين

المتطلب: إنشاء تقرير أداء مبيعات لكل موظف، يشمل عدد الطلبات، إجمالي المبيعات، متوسط مبلغ الطلبات، الطلبات حسب الحالة، وتصنيف الأداء.

SQL
SELECT 
    CONCAT(e.first_name, e.last_name) AS employee_name,
    d.department_name AS department,
    COUNT(o.order_id) AS total_orders,
    COALESCE(SUM(o.total_amount), 0) AS total_sales,
    COALESCE(ROUND(AVG(o.total_amount), 2), 0) AS avg_order_amount,
    SUM(CASE WHEN o.status = 'completed' THEN 1 ELSE 0 END) AS completed,
    SUM(CASE WHEN o.status = 'shipped'   THEN 1 ELSE 0 END) AS shipped,
    SUM(CASE WHEN o.status = 'pending'   THEN 1 ELSE 0 END) AS pending,
    SUM(CASE WHEN o.status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled,
    CASE
        WHEN COALESCE(SUM(o.total_amount), 0) >= 10000 THEN 'S - متميز'
        WHEN COALESCE(SUM(o.total_amount), 0) >= 5000  THEN 'A - ممتاز'
        WHEN COALESCE(SUM(o.total_amount), 0) >= 2000  THEN 'B - جيد'
        WHEN COUNT(o.order_id) > 0                      THEN 'C - يحتاج تحسين'
        ELSE 'لا يوجد أداء'
    END AS performance_rating
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN orders o ON e.employee_id = o.employee_id
GROUP BY e.employee_id, e.first_name, e.last_name, d.department_name
ORDER BY total_sales DESC;

النتيجة:

TEXT
employee_name | department | total_orders | total_sales | avg_order_amount | completed | shipped | pending | cancelled | performance_rating
--------------+------------+--------------+-------------+------------------+-----------+---------+---------+-----------+-------------------
ZhaoMin       | Sales      |            4 |    15100.00 |          3775.00 |         3 |       1 |       0 |         0 | S - متميز
WangQiang     | Sales      |            4 |    10900.00 |          2725.00 |         2 |       1 |       0 |         1 | S - متميز
ZhangWei      | Technology |            3 |     8750.00 |          2916.67 |         1 |       0 |       2 |         0 | A - ممتاز
LiNa          | Technology |            0 |        0.00 |             0.00 |         0 |       0 |       0 |         0 | لا يوجد أداء
LiuYang       | Marketing  |            0 |        0.00 |             0.00 |         0 |       0 |       0 |         0 | لا يوجد أداء
ChenJing      | NULL       |            0 |        0.00 |             0.00 |         0 |       0 |       0 |         0 | لا يوجد أداء

شرح الكود:

  1. LEFT JOIN — يحتفظ بجميع الموظفين، حتى الذين ليس لديهم طلبات
  2. COALESCE(SUM(...), 0) — يعرض 0 للموظفين الذين ليس لديهم طلبات
  3. SUM(CASE WHEN ... THEN 1 ELSE 0 END) — عدّ شرطي للطلبات حسب الحالة (تحويل الصفوف إلى أعمدة)
  4. CASE — يُعيّن تصنيف الأداء بناءً على إجمالي المبيعات
  5. ROUND(AVG(...), 2) — يُقرب المتوسط إلى منزلتين عشريتين

الاستعلام 2: مقارنة مبيعات الأقسام الربعية

المتطلب: تجميع المبيعات حسب القسم والربع، وحساب نسبة مبيعات كل قسم، وإيجاد القسم الأكثر مبيعًا لكل ربع.

SQL
SELECT 
    d.department_name AS department,
    CONCAT('Q', QUARTER(o.order_date)) AS quarter,
    COUNT(o.order_id) AS order_count,
    SUM(o.total_amount) AS quarterly_sales,
    ROUND(
        SUM(o.total_amount) / 
        SUM(SUM(o.total_amount)) OVER (PARTITION BY QUARTER(o.order_date)) * 100, 
    2) AS percentage,
    RANK() OVER (
        PARTITION BY QUARTER(o.order_date) 
        ORDER BY SUM(o.total_amount) DESC
    ) AS quarter_rank
FROM orders o
INNER JOIN employees e ON o.employee_id = e.employee_id
INNER JOIN departments d ON e.department_id = d.department_id
WHERE o.status != 'cancelled'
GROUP BY d.department_name, QUARTER(o.order_date)
ORDER BY quarter, quarterly_sales DESC;

النتيجة:

TEXT
department | quarter | order_count | quarterly_sales | percentage | quarter_rank
-----------+---------+-------------+-----------------+------------+-------------
Sales      | Q1      |           2 |         4300.00 |      69.35 |            1
Technology | Q1      |           1 |          950.00 |      15.32 |            2
Sales      | Q2      |           2 |         5600.00 |      26.54 |            1
Technology | Q2      |           1 |         5600.00 |      26.54 |            1
Marketing  | Q2      |           1 |         3800.00 |      18.01 |            3

شرح الكود:

  1. QUARTER(o.order_date) — يستخرج الربع من تاريخ الطلب (Q1-Q4)
  2. SUM(o.total_amount) OVER (PARTITION BY ...) — دالة نافذة تحسب إجمالي المبيعات لكل ربع
  3. percentage — مبيعات كل قسم ÷ إجمالي مبيعات الربع × 100
  4. RANK() OVER (PARTITION BY ... ORDER BY ...) — يُرتب داخل كل ربع

الاستعلام 3: تقسيم قيمة العملاء (نموذج RFM مبسط)

المتطلب: تقسيم العملاء بناءً على Recency (تاريخ الشراء الأخير)، Frequency (عدد المشتريات)، و Monetary (إجمالي الإنفاق).

SQL
WITH customer_stats AS (
    SELECT 
        customer_id,
        MAX(order_date) AS last_purchase_date,
        COUNT(order_id) AS purchase_count,
        SUM(total_amount) AS total_spend
    FROM orders
    WHERE status != 'cancelled'
    GROUP BY customer_id
)
SELECT 
    customer_id,
    last_purchase_date,
    purchase_count,
    total_spend,
    CASE
        WHEN purchase_count >= 3 AND total_spend >= 5000 THEN 'عميل VIP'
        WHEN purchase_count >= 2 AND total_spend >= 3000 THEN 'عميل رئيسي'
        WHEN purchase_count >= 2                       THEN 'عميل نشط'
        WHEN total_spend >= 2000                       THEN 'عميل محتمل'
        ELSE 'عميل عادي'
    END AS customer_tier
FROM customer_stats
ORDER BY total_spend DESC;

النتيجة:

TEXT
customer_id | last_purchase_date | purchase_count | total_spend | customer_tier
------------+-------------------+----------------+-------------+---------------
        103 | 2026-06-10        |              2 |     9500.00 | عميل VIP
        101 | 2026-04-15        |              2 |     3450.00 | عميل رئيسي
        104 | 2026-03-10        |              1 |     4100.00 | عميل محتمل
        102 | 2026-05-20        |              2 |     5600.00 | عميل VIP
        106 | 2026-05-02        |              1 |     1500.00 | عميل عادي
        105 | 2026-04-01        |              0 |        0.00 | عميل عادي
        107 | 2026-06-01        |              1 |     2200.00 | عميل محتمل
        108 | 2026-06-15        |              1 |     1900.00 | عميل عادي

شرح الكود:

  1. WITH customer_stats AS (...) — CTE (تعبير الجدول المشترك) يحسب أولاً إحصائيات كل عميل
  2. داخل CTE: MAX(order_date) لآخر عملية شراء، COUNT لعدد المشتريات، SUM لإجمالي الإنفاق
  3. CASE الخارجي يقسم العملاء بناءً على شروط متعددة الأبعاد
  4. يستبعد الطلبات الملغاة (status != 'cancelled')

الاستعلام 4: تحليل ارتباط المخزون والمبيعات للمنتجات

المتطلب: تحليل مخزون المنتجات حسب القسم، بالاشتراك مع بيانات المبيعات لتقييم دوران المخزون.

SQL
SELECT 
    d.department_name AS department,
    COUNT(DISTINCT p.product_id) AS product_count,
    SUM(p.stock) AS total_stock,
    COALESCE(SUM(p.price * p.stock), 0) AS inventory_value,
    COUNT(DISTINCT o.order_id) AS related_orders,
    COALESCE(SUM(o.total_amount), 0) AS order_total,
    CASE 
        WHEN SUM(p.price * p.stock) > 0 
        THEN ROUND(COALESCE(SUM(o.total_amount), 0) / SUM(p.price * p.stock) * 100, 2)
        ELSE 0
    END AS turnover_rate_pct
FROM departments d
LEFT JOIN products p ON d.department_id = p.department_id
LEFT JOIN employees e ON d.department_id = e.department_id
LEFT JOIN orders o ON e.employee_id = o.employee_id AND o.status != 'cancelled'
GROUP BY d.department_id, d.department_name
ORDER BY inventory_value DESC;

النتيجة:

TEXT
department | product_count | total_stock | inventory_value | related_orders | order_total | turnover_rate_pct
-----------+---------------+-------------+-----------------+----------------+-------------+------------------
Technology |             3 |         275 |       369945.00 |              4 |      8750.00 |             2.36
Sales      |             2 |          70 |        68930.00 |              8 |     26000.00 |            37.71
Marketing  |             2 |          75 |        86250.00 |              0 |         0.00 |             0.00

شرح الكود:

  1. LEFT JOIN متعدد الجداول: الأقسام → المنتجات + الموظفين → الطلبات
  2. COUNT(DISTINCT ...) — عدّ بدون تكرار لتجنب التكرار
  3. p.price * p.stock — يحسب قيمة مخزون المنتج
  4. معدل الدوران = إجمالي الطلبات ÷ قيمة المخزون × 100%

الاستعلام 5: ترتيب أداء الموظفين ومقارنة الأقسام

المتطلب: ترتيب الموظفين حسب الأداء العام ومقارنتهم بمتوسط القسم.

SQL
WITH emp_performance AS (
    SELECT 
        e.employee_id,
        CONCAT(e.first_name, e.last_name) AS name,
        d.department_name AS department,
        e.salary AS base_salary,
        COUNT(o.order_id) AS order_count,
        COALESCE(SUM(o.total_amount), 0) AS sales_amount,
        COALESCE(AVG(o.total_amount), 0) AS avg_order_amount
    FROM employees e
    LEFT JOIN departments d ON e.department_id = d.department_id
    LEFT JOIN orders o ON e.employee_id = o.employee_id AND o.status != 'cancelled'
    GROUP BY e.employee_id, e.first_name, e.last_name, d.department_name, e.salary
)
SELECT 
    name,
    department,
    base_salary,
    order_count,
    sales_amount,
    ROUND(avg_order_amount, 2) AS avg_order_amount,
    RANK() OVER (ORDER BY sales_amount DESC) AS company_rank,
    RANK() OVER (PARTITION BY department ORDER BY sales_amount DESC) AS dept_rank,
    ROUND(sales_amount - AVG(sales_amount) OVER (PARTITION BY department), 2) AS vs_dept_avg
FROM emp_performance
ORDER BY sales_amount DESC;

النتيجة:

TEXT
name      | department | base_salary | order_count | sales_amount | avg_order_amount | company_rank | dept_rank | vs_dept_avg
----------+------------+-------------+-------------+--------------+------------------+--------------+-----------+------------
ZhaoMin   | Sales      |    11000.00 |           4 |     15100.00 |          3775.00 |            1 |         1 |     3550.00
WangQiang | Sales      |     8000.00 |           3 |     10900.00 |          3633.33 |            2 |         2 |     -650.00
ZhangWei  | Technology |    12000.00 |           2 |      8750.00 |          4375.00 |            3 |         1 |     8750.00
LiNa      | Technology |     9500.00 |           0 |         0.00 |             0.00 |            4 |         2 |    -8750.00
LiuYang   | Marketing  |     7500.00 |           0 |         0.00 |             0.00 |            4 |         1 |        0.00
ChenJing  | NULL       |     9000.00 |           0 |         0.00 |             0.00 |            4 |         1 |        0.00

شرح الكود:

  1. CTE emp_performance — يحسب أولاً بيانات الأداء الأساسية لكل موظف
  2. RANK() OVER (ORDER BY ...) — ترتيب على مستوى الشركة
  3. RANK() OVER (PARTITION BY ... ORDER BY ...) — ترتيب داخل القسم
  4. AVG(sales_amount) OVER (PARTITION BY department) — يحسب متوسط مبيعات القسم
  5. vs_dept_avg — الفرق بين مبيعات الفرد ومتوسط القسم

❓ أسئلة شائعة

س: ما الفرق بين CTE (عبارة WITH) والاستعلامات الفرعية? ج: يتمتع CTE بقراءة أفضل ويمكن الإشارة إليه عدة مرات في نفس الاستعلام؛ يتم إعادة تنفيذ الاستعلامات الفرعية في كل مرة يتم الإشارة إليها. يدعم CTE أيضًا الاستعلامات المتكررة. للاستعلامات المعقدة، يُوصى بـ CTE.

س: ما الفرق بين دوال النافذة و GROUP BY? ج: يقوم GROUP BY بدمج عدة صفوف في صف واحد (تقليل عدد الصفوف بعد التجميع)؛ تحتفظ دوال النافذة بجميع الصفوف الأصلية وتضيف نتائج تجميعية لكل صف. إذا كنت تحتاج إلى كل من التجميع والتفاصيل، استخدم دوال النافذة.

س: هل هناك حد لعمق تداخل CASE WHEN? ج: نظريًا لا، لكن الإفراط في التداخل يشير إلى منطق معقد للغاية. فكّر في استخدام CTEs لتفكيك الخطوات، أو معالجة المنطق المعقد في طبقة التطبيق.

س: كيف أُحسّن استعلامات التجميع مع حجم بيانات كبير? ج: أنشئ فهارس على الأعمدة المستخدمة في GROUP BY و WHERE؛ استخدم CTEs لتفكيك الاستعلامات المعقدة؛ تجنب استخدام الدوال على الأعمدة المجمعة (مثل تغيير WHERE YEAR(date) = 2026 إلى WHERE date >= '2026-01-01' AND date < '2027-01-01')؛ استخدم EXPLAIN لتحليل خطط التنفيذ.


📖 ملخص

المهارة التطبيق في هذا الدرس
الدوال التجميعية COUNT, SUM, AVG, MAX, MIN
GROUP BY إحصائيات حسب البُعد
HAVING تصفية نتائج التجميع
CASE WHEN إحصائيات شرطية (تحويل الصفوف إلى أعمدة)، تعيين التصنيف
JOIN استعلامات متعددة الجداول
استعلام فرعي / CTE بناء استعلامات معقدة خطوة بخطوة
دوال النافذة RANK() ترتيب، AVG() OVER() تحليل مقارن
COALESCE التعامل مع قيم NULL

النقاط الأساسية:


📝 تمارين

التمرين 1: اكتب استعلامًا لإنشاء "تقرير اتجاه المبيعات الشهري" — جمّع عدد الطلبات وإجمالي المبيعات ومتوسط مبلغ الطلبات حسب الشهر، واحسب معدل النمو من شهر لآخر: (الشهر الحالي - الشهر السابق) / الشهر السابق × 100%.

التمرين 2: اكتب استعلامًا للعثور على "أعلى موظف راتبًا في كل قسم" مع "عدد الطلبات المعالجة"، باستخدام CTE + دوال النافذة.

التمرين 3: اكتب استعلامًا لتقسيم العملاء حسب مبلغ الإنفاق (≥5000 كـ VIP، ≥3000 كـ رئيسي، ≥1000 كـ عادي، <1000 كـ عميل جديد)، وعدّ عدد العملاء وإجمالي الإنفاق لكل فئة.

التمرين 4: أنشئ عرض v_employee_dashboard يحتوي على اسم الموظف، والقسم، والراتب، وعدد الطلبات، وإجمالي المبيعات، وتصنيف الأداء. استخدم العرض للاستعلام عن أداء موظفي قسم "المبيعات".


الدرس التالي

👉 19-window-functions - دوال النافذة: تعمّق في ROW_NUMBER و RANK و DENSE_RANK و LAG و LEAD وغيرها من دوال النافذة لإتقان تقنيات تحليل البيانات المتقدمة!

Web-Tutorial.com

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

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

100%