المرجع المتقدم إلى لغة SQL


مرحباً! لو وصلت لهنا، غالباً أنت تعرف أساسيات SQL وتبحث عن الخطوة التالية. هذا الدرس بيركز على المفاهيم المتقدمة اللي بتخليك تكتب استعلامات أقوى وأكثر كفاءة. جهز نفسك، بنبدأ على طول!

دوال النافذة (Window Functions)

دوال النافذة هي قوة خارقة في SQL تسمح لك بإجراء عمليات حسابية على مجموعة من الصفوف المرتبطة بالصف الحالي، بدون تجميع (Grouping) الصفوف كلها. يعني بتقدر تشوف الصف الحالي ضمن سياق مجموعة أوسع من الصفوف.

الملاحظة: الفرق الأساسي بين دوال النافذة ودوال التجميع (مثل SUM() أو AVG() العادية) هو أن دوال التجميع تُرجع صفاً واحداً للمجموعة، بينما دوال النافذة تُرجع نتيجة لكل صف في المجموعة، مع الحفاظ على تفاصيل الصفوف الأصلية.

الصيغة العامة لدوال النافذة هي FUNCTION() OVER (PARTITION BY ... ORDER BY ...):

  • PARTITION BY: بتقسم مجموعة البيانات إلى أقسام (مثل GROUP BY) لكن كل قسم يحتفظ بصفوفه الأصلية.
  • ORDER BY: بترتب الصفوف داخل كل قسم.

أمثلة شائعة:

  • ROW_NUMBER(): بتعطي رقم تسلسلي فريد لكل صف داخل قسم معين.
  • RANK() / DENSE_RANK(): بتعطي رتبة للصفوف، مع اختلاف بسيط في التعامل مع القيم المتساوية.
  • LAG() / LEAD(): بتجيب قيمة من الصف السابق أو التالي داخل القسم.
  • دوال التجميع كدوال نافذة: SUM() OVER(), AVG() OVER(), MAX() OVER(), إلخ.

مثال: إيجاد أعلى مبيعات لكل منتج:

SELECT
    product_id,
    sale_date,
    sale_amount,
    ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY sale_amount DESC, sale_date DESC) as rn
FROM
    sales
WHERE
    rn = 1; -- هذا لن يعمل مباشرة، يجب أن يكون في CTE أو Subquery

التصحيح باستخدام CTE:

WITH RankedSales AS (
    SELECT
        product_id,
        sale_date,
        sale_amount,
        ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY sale_amount DESC, sale_date DESC) as rn
    FROM
        sales
)
SELECT
    product_id,
    sale_date,
    sale_amount
FROM
    RankedSales
WHERE
    rn = 1;

التعبيرات الجدولية المشتركة (Common Table Expressions - CTEs)

الـ CTEs، أو كما تُعرف بالـ WITH clause، هي طريقة لتعريف مجموعة نتائج مؤقتة ومُسمّاة يمكنك الرجوع إليها ضمن جملة SELECT، INSERT، UPDATE، أو DELETE واحدة.

فوائد الـ CTEs:
  • تحسين القراءة: بتخلي الاستعلامات المعقدة أسهل في الفهم والتقسيم لأجزاء منطقية.
  • إعادة الاستخدام: تقدر تستخدم نفس الـ CTE عدة مرات داخل نفس الاستعلام.
  • الاستعلامات التكرارية: أساسية لكتابة الاستعلامات التكرارية (Recursive CTEs) اللي بتتعامل مع البيانات الهرمية.

الصيغة بسيطة:

WITH cte_name AS (
    -- استعلامك هنا
    SELECT column1, column2
    FROM table_name
    WHERE condition
)
-- بعدين تستخدم الـ CTE كأنه جدول عادي
SELECT *
FROM cte_name
WHERE another_condition;

مثال: إيجاد متوسط الرواتب لكل قسم، ثم إظهار الموظفين اللي رواتبهم أعلى من هذا المتوسط:

WITH DepartmentAvgSalary AS (
    SELECT
        department_id,
        AVG(salary) as avg_dept_salary
    FROM
        employees
    GROUP BY
        department_id
)
SELECT
    e.employee_name,
    e.salary,
    d.avg_dept_salary
FROM
    employees e
JOIN
    DepartmentAvgSalary d ON e.department_id = d.department_id
WHERE
    e.salary > d.avg_dept_salary;

أنواع الربط المتقدمة (Advanced Joins)

لو كنت بتعرف INNER JOIN و LEFT JOIN، فهذي خطوتك التالية.

FULL OUTER JOIN

بيرجع كل الصفوف من الجدول الأيسر وكل الصفوف من الجدول الأيمن. إذا مافي تطابق، بيحط NULL في الأعمدة المقابلة من الجدول الآخر.

مثال: إظهار كل الموظفين وكل الأقسام، حتى لو مافي موظفين في قسم معين أو موظف ماله قسم:

SELECT
    e.employee_name,
    d.department_name
FROM
    employees e
FULL OUTER JOIN
    departments d ON e.department_id = d.department_id;

SELF JOIN

هذا الربط بيكون لما تربط الجدول بنفسه. مفيد جداً لما تكون عندك علاقات هرمية داخل نفس الجدول، زي علاقة الموظف بمديره (المدير هو كمان موظف).

مثال: إظهار كل موظف واسم مديره:

SELECT
    e.employee_name AS Employee,
    m.employee_name AS Manager
FROM
    employees e
JOIN
    employees m ON e.manager_id = m.employee_id;

الاستعلامات الفرعية مع EXISTS / NOT EXISTS

الـ Subqueries (الاستعلامات الفرعية) هي استعلامات داخل استعلام آخر. EXISTS و NOT EXISTS هم أدوات قوية للتحقق من وجود (أو عدم وجود) صفوف بناءً على شرط معين، وغالباً ما تكون أكثر كفاءة من IN أو NOT IN في بعض السيناريوهات، خاصةً مع مجموعات البيانات الكبيرة.

الملاحظة: EXISTS و NOT EXISTS بيرجعون TRUE أو FALSE فقط، وما بيهتمون بالقيم اللي بترجعها الاستعلام الفرعي. هم فقط يتأكدون من وجود أي صف.

EXISTS

بيتحقق إذا كان الاستعلام الفرعي بيرجع أي صفوف. إذا رجع صف واحد على الأقل، بيرجع TRUE.

مثال: إظهار الأقسام اللي فيها موظفين رواتبهم فوق 50000:

SELECT
    d.department_name
FROM
    departments d
WHERE
    EXISTS (
        SELECT 1
        FROM employees e
        WHERE e.department_id = d.department_id
        AND e.salary > 50000
    );

NOT EXISTS

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

مثال: إظهار الأقسام اللي ما فيها أي موظف:

SELECT
    d.department_name
FROM
    departments d
WHERE
    NOT EXISTS (
        SELECT 1
        FROM employees e
        WHERE e.department_id = d.department_id
    );

هذه كانت لمحة سريعة عن بعض المفاهيم المتقدمة في SQL. الممارسة هي مفتاح الإتقان، جرب هذه الأوامر على قواعد بياناتك وشوف كيف بتغير طريقة كتابتك للاستعلامات!