مرحباً! لو وصلت لهنا، غالباً أنت تعرف أساسيات 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. الممارسة هي مفتاح الإتقان، جرب هذه الأوامر على قواعد بياناتك وشوف كيف بتغير طريقة كتابتك للاستعلامات!