أساسيات PHP

SQL: الاستعلامات المتقدمة

13 دقيقة الدرس 34 من 45

GROUP BY - تجميع البيانات

شرط GROUP BY يجمع الصفوف التي لها نفس القيم في أعمدة محددة، مما يسمح لك بإجراء حسابات تجميعية على كل مجموعة.

صيغة GROUP BY الأساسية

SELECT column1, aggregate_function(column2)
FROM table
GROUP BY column1;

مثال: عد المنتجات حسب الفئة

SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category;

المخرجات:

+-------------+---------------+
| category    | product_count |
+-------------+---------------+
| Electronics | 25            |
| Books       | 48            |
| Furniture   | 12            |
+-------------+---------------+

دوال تجميعية متعددة

-- الحصول على إحصائيات الفئة
SELECT
    category,
    COUNT(*) AS product_count,
    AVG(price) AS avg_price,
    MIN(price) AS min_price,
    MAX(price) AS max_price,
    SUM(stock) AS total_stock
FROM products
GROUP BY category;

GROUP BY بأعمدة متعددة

-- عد المنتجات حسب الفئة والحالة
SELECT
    category,
    is_active,
    COUNT(*) AS count
FROM products
GROUP BY category, is_active;

المخرجات:

+-------------+-----------+-------+
| category    | is_active | count |
+-------------+-----------+-------+
| Electronics | 1         | 22    |
| Electronics | 0         | 3     |
| Books       | 1         | 45    |
| Books       | 0         | 3     |
+-------------+-----------+-------+

HAVING - تصفية المجموعات

شرط HAVING يرشح المجموعات التي تم إنشاؤها بواسطة GROUP BY. إنه مثل WHERE لكن للبيانات المجمعة.

WHERE مقابل HAVING

  • WHERE: يرشح الصفوف الفردية قبل التجميع
  • HAVING: يرشح المجموعات بعد التجميع
  • WHERE: لا يمكن استخدام الدوال التجميعية (COUNT, SUM, AVG)
  • HAVING: يمكن استخدام الدوال التجميعية

مثال: الفئات بأكثر من 10 منتجات

SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category
HAVING COUNT(*) > 10;

مثال: الفئات ذات القيمة العالية

-- الفئات بمتوسط سعر أكثر من 100 دولار
SELECT
    category,
    COUNT(*) AS products,
    AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING AVG(price) > 100
ORDER BY avg_price DESC;

الجمع بين WHERE و HAVING

-- المنتجات النشطة، مجمعة حسب الفئة، بأكثر من 5 عناصر
SELECT
    category,
    COUNT(*) AS active_count,
    AVG(price) AS avg_price
FROM products
WHERE is_active = 1              -- تصفية الصفوف قبل التجميع
GROUP BY category
HAVING COUNT(*) > 5              -- تصفية المجموعات بعد التجميع
ORDER BY active_count DESC;

الاستعلامات الفرعية (الاستعلامات المتداخلة)

الاستعلام الفرعي هو استعلام متداخل داخل استعلام آخر. يمكن استخدامه في شروط SELECT أو FROM أو WHERE أو HAVING.

استعلام فرعي في شرط WHERE

-- العثور على المنتجات أغلى من متوسط السعر
SELECT name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products);

استعلام فرعي مع IN

-- العثور على المستخدمين الذين قدموا طلبات
SELECT username, email
FROM users
WHERE id IN (SELECT DISTINCT user_id FROM orders);

استعلام فرعي في SELECT

-- إظهار كل منتج مع متوسط سعر الفئة
SELECT
    name,
    price,
    category,
    (SELECT AVG(price)
     FROM products p2
     WHERE p2.category = products.category) AS category_avg_price
FROM products;

استعلام فرعي في FROM (جدول مشتق)

-- الحصول على الفئات بإحصائياتها، ثم التصفية
SELECT *
FROM (
    SELECT
        category,
        COUNT(*) AS product_count,
        AVG(price) AS avg_price
    FROM products
    GROUP BY category
) AS category_stats
WHERE avg_price > 50;

EXISTS و NOT EXISTS

يختبر ما إذا كان الاستعلام الفرعي يرجع أي صفوف.

مثال EXISTS

-- العثور على المستخدمين الذين قدموا طلباً واحداً على الأقل
SELECT username, email
FROM users
WHERE EXISTS (
    SELECT 1
    FROM orders
    WHERE orders.user_id = users.id
);

مثال NOT EXISTS

-- العثور على المستخدمين الذين لم يقدموا طلباً أبداً
SELECT username, email
FROM users
WHERE NOT EXISTS (
    SELECT 1
    FROM orders
    WHERE orders.user_id = users.id
);

نصيحة للأداء: EXISTS غالباً أسرع من IN لمجموعات البيانات الكبيرة لأنه يتوقف عن البحث بمجرد العثور على صف مطابق واحد.

UNION - دمج النتائج

UNION يدمج النتائج من عبارات SELECT متعددة في مجموعة نتائج واحدة.

UNION الأساسي

-- دمج المنتجات النشطة والمميزة
SELECT id, name, 'active' AS type
FROM products
WHERE is_active = 1

UNION

SELECT id, name, 'featured' AS type
FROM products
WHERE is_featured = 1;

UNION مقابل UNION ALL

  • UNION: يزيل الصفوف المكررة (أبطأ)
  • UNION ALL: يحتفظ بجميع الصفوف بما في ذلك المكررات (أسرع)
-- UNION ALL: أسرع، يحتفظ بالمكررات
SELECT name FROM products WHERE category = 'Electronics'
UNION ALL
SELECT name FROM products WHERE price > 100;

متطلبات UNION

  • يجب أن تحتوي جميع عبارات SELECT على نفس عدد الأعمدة
  • يجب أن تحتوي الأعمدة على أنواع بيانات متوافقة
  • أسماء الأعمدة تأتي من SELECT الأول

عبارات CASE

أضف منطقاً شرطياً إلى استعلاماتك باستخدام تعبيرات CASE.

CASE البسيط

SELECT
    name,
    price,
    CASE
        WHEN price < 50 THEN 'رخيص'
        WHEN price < 200 THEN 'متوسط'
        WHEN price < 500 THEN 'غالي'
        ELSE 'فاخر'
    END AS price_category
FROM products;

CASE مع GROUP BY

-- عد المنتجات في كل نطاق سعري
SELECT
    CASE
        WHEN price < 50 THEN 'الميزانية'
        WHEN price < 200 THEN 'متوسط النطاق'
        ELSE 'متميز'
    END AS price_range,
    COUNT(*) AS product_count
FROM products
GROUP BY
    CASE
        WHEN price < 50 THEN 'الميزانية'
        WHEN price < 200 THEN 'متوسط النطاق'
        ELSE 'متميز'
    END;

دوال التاريخ والوقت

التاريخ/الوقت الحالي

SELECT NOW();              -- التاريخ والوقت الحالي: 2024-01-15 14:30:45
SELECT CURDATE();          -- التاريخ الحالي: 2024-01-15
SELECT CURTIME();          -- الوقت الحالي: 14:30:45

حساب التاريخ

-- الطلبات من آخر 7 أيام
SELECT * FROM orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY);

-- الطلبات من الشهر الماضي
SELECT * FROM orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH);

-- تاريخ مستقبلي
SELECT DATE_ADD(NOW(), INTERVAL 30 DAY) AS delivery_date;

تنسيق التاريخ

SELECT
    created_at,
    DATE_FORMAT(created_at, '%Y-%m-%d') AS date_only,
    DATE_FORMAT(created_at, '%M %d, %Y') AS formatted_date,
    DATE_FORMAT(created_at, '%W') AS day_of_week
FROM orders;

استخراج التاريخ

SELECT
    YEAR(created_at) AS year,
    MONTH(created_at) AS month,
    DAY(created_at) AS day,
    HOUR(created_at) AS hour
FROM orders;

دوال النصوص

عمليات النصوص الشائعة

SELECT
    CONCAT(first_name, ' ', last_name) AS full_name,
    UPPER(username) AS uppercase_name,
    LOWER(email) AS lowercase_email,
    LENGTH(description) AS desc_length,
    SUBSTRING(name, 1, 10) AS short_name,
    TRIM('  text  ') AS trimmed,
    REPLACE(description, 'old', 'new') AS updated_desc
FROM users;

الدوال الرياضية

SELECT
    price,
    ROUND(price) AS rounded,
    ROUND(price, 2) AS two_decimals,
    CEIL(price) AS rounded_up,
    FLOOR(price) AS rounded_down,
    ABS(-50) AS absolute_value,
    POWER(2, 3) AS power_result,      -- 2^3 = 8
    SQRT(16) AS square_root           -- 4
FROM products;

أمثلة استعلامات متقدمة عملية

مثال 1: تقرير المبيعات حسب الشهر

SELECT
    DATE_FORMAT(created_at, '%Y-%m') AS month,
    COUNT(*) AS total_orders,
    SUM(total_amount) AS monthly_revenue,
    AVG(total_amount) AS avg_order_value
FROM orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
ORDER BY month DESC;

مثال 2: أفضل 5 عملاء حسب الإنفاق

SELECT
    users.username,
    users.email,
    COUNT(orders.id) AS order_count,
    SUM(orders.total_amount) AS total_spent
FROM users
INNER JOIN orders ON users.id = orders.user_id
GROUP BY users.id, users.username, users.email
ORDER BY total_spent DESC
LIMIT 5;

مثال 3: المنتجات أقل من متوسط السعر حسب الفئة

SELECT
    p1.name,
    p1.category,
    p1.price,
    (SELECT AVG(p2.price)
     FROM products p2
     WHERE p2.category = p1.category) AS category_avg
FROM products p1
WHERE p1.price < (
    SELECT AVG(p2.price)
    FROM products p2
    WHERE p2.category = p1.category
)
ORDER BY p1.category, p1.price;

مثال 4: تحليل مشتريات العملاء

SELECT
    username,
    email,
    CASE
        WHEN total_orders = 0 THEN 'لا توجد طلبات'
        WHEN total_orders < 5 THEN 'عرضي'
        WHEN total_orders < 20 THEN 'منتظم'
        ELSE 'VIP'
    END AS customer_tier,
    total_orders,
    total_spent
FROM (
    SELECT
        u.username,
        u.email,
        COUNT(o.id) AS total_orders,
        COALESCE(SUM(o.total_amount), 0) AS total_spent
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    GROUP BY u.id, u.username, u.email
) AS customer_stats
ORDER BY total_spent DESC;

مثال 5: نظام تنبيه المخزون

SELECT
    name,
    stock,
    CASE
        WHEN stock = 0 THEN 'نفد المخزون'
        WHEN stock < 5 THEN 'حرج'
        WHEN stock < 20 THEN 'منخفض'
        ELSE 'جيد'
    END AS stock_status,
    price * stock AS inventory_value
FROM products
WHERE is_active = 1
HAVING stock_status IN ('نفد المخزون', 'حرج', 'منخفض')
ORDER BY
    CASE stock_status
        WHEN 'نفد المخزون' THEN 1
        WHEN 'حرج' THEN 2
        WHEN 'منخفض' THEN 3
    END;

تمرين: استعلامات التحليلات المتقدمة

اكتب استعلامات SQL للسيناريوهات التالية:

  1. ابحث عن أفضل 3 فئات منتجات الأكثر شعبية حسب إجمالي عدد المبيعات
  2. قائمة المستخدمين الذين أنفقوا أكثر من متوسط إنفاق العملاء
  3. إظهار اتجاهات الإيرادات الشهرية للأشهر الستة الماضية
  4. ابحث عن المنتجات التي لم يتم طلبها أبداً
  5. احسب النسبة المئوية للطلبات في كل حالة (معلق، تم الشحن، تم التسليم، ملغى)
  6. احصل على اليوم من الأسبوع الذي فيه معظم الطلبات
  7. قائمة الفئات التي يكون متوسط سعر المنتج فيها أكثر من 200 دولار
  8. ابحث عن المستخدمين الذين قدموا طلبات في يناير ولكن ليس في فبراير

نصائح تحسين الاستعلام

كتابة استعلامات فعالة

  • استخدم الفهارس: أضف فهارس على الأعمدة المستخدمة في WHERE و JOIN و ORDER BY و GROUP BY
  • اختر أعمدة محددة: تجنب SELECT *
  • حدد النتائج: استخدم LIMIT للاختبار والترقيم
  • تجنب الدوال على الأعمدة المفهرسة: WHERE YEAR(date) = 2024 يمنع استخدام الفهرس
  • استخدم EXISTS بدلاً من IN: للاستعلامات الفرعية الكبيرة
  • تجنب أحرف البدل في البداية: LIKE '%text' لا يمكنه استخدام الفهارس
  • استخدم EXPLAIN: تحليل خطة تنفيذ الاستعلام
-- تحقق من أداء الاستعلام
EXPLAIN SELECT * FROM products WHERE category = 'Electronics';

ملخص الدوال التجميعية الشائعة

الدالة الوصف مثال
COUNT() عد الصفوف COUNT(*)
SUM() مجموع القيم SUM(price)
AVG() المتوسط AVG(rating)
MIN() القيمة الدنيا MIN(price)
MAX() القيمة القصوى MAX(salary)
GROUP_CONCAT() دمج القيم GROUP_CONCAT(name)

ما التالي؟

في الدرس التالي، ستتقن SQL JOINs - تعلم كيفية دمج البيانات من جداول متعددة مترابطة باستخدام INNER JOIN و LEFT JOIN و RIGHT JOIN وتقنيات ربط أكثر تقدماً.