Coding Interview Prep · درس

صفوف Top-N لكل مجموعة باستخدام ROW_NUMBER

النمط الأساسي للتقسيم والترتيب الخاص بسؤال أفضل 3 لكل فئة

الدرس 1 من 413 خطوة

صفوف Top-N لكل مجموعة باستخدام ROW_NUMBER درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

سؤال أعلى N لكل مجموعة

يبدو أحد أكثر أسئلة مقابلات SQL شيوعًا بسيطًا: "أعد الموظفين الثلاثة الأعلى أجرًا في كل قسم." لكن المرشحين الذين يلجؤون إلى LIMIT مباشرة يفشلون، لأن LIMIT يقيّد مجموعة النتائج بأكملها، لا كل مجموعة على حدة.

يتحقق المحاور مما إذا كنت تعرف دوال النوافذ. والإجابة الأساسية هي: رقّم الصفوف داخل كل مجموعة، ثم احتفظ بالصفوف التي يكون رقمها ≤ N. يشرح هذا الدرس هذا النمط خطوةً خطوة.

لماذا لا يستطيع LIMIT حل المشكلة

افترض أنك كتبت الاستعلام أدناه. سيعيد 3 صفوف فقط عبر الجدول بأكمله، وليس 3 صفوف لكل قسم.

يعمل LIMIT (أو TOP أو FETCH FIRST) على مجموعة النتائج النهائية. لا يوجد LIMIT لكل مجموعة في SQL القياسي. وعندما يسمع المحاور أنك تقترح LIMIT 3 لحل مشكلة تخص كل مجموعة، فهذا يدل على أنك لم تستوعب مفهوم التقسيم إلى مجموعات بعد.

-- WRONG: only 3 rows total, not 3 per department
SELECT department, name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3;

التعرّف على ROW_NUMBER

‏ROW_NUMBER() هي دالة نافذة تعيّن عددًا صحيحًا فريدًا ومتصلًا لكل صف وفق ترتيب معين. وهي ترقّم النتائج بأكملها عند استخدامها بمفردها.

العنصر السحري هو PARTITION BY: إذ يعيد الترقيم من 1 لكل مجموعة. ادمج PARTITION BY department مع ORDER BY salary DESC لتحصل كل إدارة على ترتيبها الخاص 1 و2 و3، ... حسب الراتب.

SELECT
  name,
  department,
  salary,
  ROW_NUMBER() OVER (
    PARTITION BY department
    ORDER BY salary DESC
  ) AS rn
FROM employees;

قراءة النتائج المرقّمة

بعد تنفيذ الاستعلام السابق، سيحمل كل صف قيمة rn. داخل كل قسم يحصل أعلى راتب على rn = 1، ويحصل الراتب التالي على 2، وهكذا. وعند الانتقال إلى قسم جديد، يبدأ الترقيم مجددًا من 1.

  • المبيعات: Ana (1)، Bo (2)، Cal (3)، Dee (4)
  • الهندسة: Eve (1)، Fin (2)، Gus (3)

وعليه، فإن عبارة "أعلى 3 موظفين في كل قسم" تعني ببساطة "الاحتفاظ بالصفوف التي يكون فيها rn <= 3".

لا يمكنك تصفية rn في WHERE

الخطوة التالية الطبيعية هي WHERE rn <= 3، لكنها تفشل. تُحسب دوال النوافذ بعد جملة WHERE وفق ترتيب التنفيذ المنطقي، ولذلك لا يكون الاسم المستعار rn موجودًا بعد عند تنفيذ WHERE.

يحب المحاورون هذا الفخ. والحل هو حساب دالة النافذة في استعلام فرعي أو CTE، ثم تصفية نتيجة ذلك الاستعلام الداخلي في استعلام خارجي.

-- ERROR: rn does not exist in WHERE
SELECT name, department, salary,
       ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
WHERE rn <= 3;

حل CTE الأساسي

ضع عملية الترقيم داخل CTE باسم ranked، ثم نفّذ SELECT منه مع وضع عامل التصفية في جملة WHERE الخارجية. هذه هي الإجابة التي يريد المحاورون رؤيتها، كما أنها واضحة ونظيفة.

احفظ هذا الهيكل: قسّم حسب المجموعة، ورتّب حسب المقياس، ثم صفِّ باستخدام rn ≤ N في الاستعلام الخارجي. ويمكن تعميمه على أعلى 1 أو أعلى 5 أو أي قيمة N، وذلك بتغيير رقم واحد.

WITH ranked AS (
  SELECT
    name, department, salary,
    ROW_NUMBER() OVER (
      PARTITION BY department
      ORDER BY salary DESC
    ) AS rn
  FROM employees
)
SELECT name, department, salary
FROM ranked
WHERE rn <= 3
ORDER BY department, rn;

صيغة الاستعلام الفرعي

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

صيغتا CTE والجدول المشتق قابلتان للتبادل في هذه المسألة. اختر الصيغة التي يراها المحاور أكثر وضوحًا؛ فكلاهما صحيح بالقدر نفسه.

SELECT name, department, salary
FROM (
  SELECT name, department, salary,
         ROW_NUMBER() OVER (
           PARTITION BY department ORDER BY salary DESC
         ) AS rn
  FROM employees
) AS r
WHERE rn <= 3;

أعلى 1: الأفضل منفردًا في كل مجموعة

إن عبارة "اعثر على الموظف الأعلى أجرًا في كل قسم" ليست سوى حالة N = 1. اضبط عامل التصفية على rn = 1.

لماذا لا نستخدم MAX(salary) مع GROUP BY department؟ لأن MAX يعيد قيمة الراتب، لكنه لا يعيد بقية بيانات صف ذلك الموظف، مثل اسمه وتاريخ تعيينه وغير ذلك. أما ROW_NUMBER فتحافظ على الصف الفائز كاملًا، وهو ما يريده السؤال عادةً.

WITH ranked AS (
  SELECT *,
         ROW_NUMBER() OVER (
           PARTITION BY department ORDER BY salary DESC
         ) AS rn
  FROM employees
)
SELECT name, department, salary, hire_date
FROM ranked
WHERE rn = 1;

إضافة عامل كسر تعادل حتمي

تعيد ROW_NUMBER دائمًا N صفوف بالضبط، حتى عند تعادل الرواتب. لكن تحديد الصف المتعادل الذي يحصل على rn = 1 يكون اعتباطيًا ما لم تكسر التعادل. فإذا كان شخصان يتقاضيان 90000 واحتفظت فقط بالصف ذي rn = 1، فلن يكون الصف المختار متوقعًا بين عمليات التنفيذ المختلفة.

أضف مفتاح ترتيب ثانويًا وفريدًا، مثل employee_id، حتى تكون النتيجة ثابتة وقابلة لإعادة الإنتاج. ويقدّر المحاورون المرشحين الذين يذكرون الحتمية من تلقاء أنفسهم.

ROW_NUMBER() OVER (
  PARTITION BY department
  ORDER BY salary DESC, employee_id ASC
) AS rn

مثال عملي كامل

لنفترض وجود جدول sales يحتوي على region وproduct وrevenue، والمطلوب إعادة أعلى منتجين من حيث الإيرادات في كل منطقة. الوصفة نفسها: قسّم حسب region، ورتّب حسب revenue DESC، واحتفظ بالصفوف التي يكون فيها rn <= 2.

لاحظ أن عمود التقسيم وعمود المقياس فقط هما اللذان يتغيران. أما البنية فمتطابقة بصرف النظر عن مجال الأعمال.

WITH ranked AS (
  SELECT region, product, revenue,
         ROW_NUMBER() OVER (
           PARTITION BY region ORDER BY revenue DESC, product
         ) AS rn
  FROM sales
)
SELECT region, product, revenue
FROM ranked
WHERE rn <= 2
ORDER BY region, rn;

الأداء والنقاط التي ينبغي ذكرها

لإظهار فهم يتجاوز مجرد صحة الحل، اذكر ما يلي:

  • يساعد الفهرس (department, salary DESC) المحرك على إنتاج الصفوف المرتبة لكل قسم بكفاءة.
  • يمسح أسلوب دوال النوافذ الجدول مرة واحدة، وهو أفضل بكثير من استعلام فرعي مترابط يُنفَّذ لكل صف.
  • في حالات أعلى N من 1 على جداول ضخمة جدًا، تدعم بعض المحركات DISTINCT ON (في Postgres) كاختصار، لكن ROW_NUMBER هو الأسلوب القياسي القابل للنقل.

اذكر دائمًا عامل كسر التعادل، وتأكد من قيمة N المطلوبة.

تحقق سريع

اختبر مدى استيعابك لنمط أعلى N لكل مجموعة.

مراجعة: أعلى N لكل مجموعة

يمكن تلخيص النمط في جملة واحدة: قسّم حسب المجموعة، ورتّب حسب المقياس، وعيّن ROW_NUMBER، ثم احتفظ بالصفوف التي يكون فيها rn ≤ N في استعلام خارجي.

  • يقيّد LIMIT المجموعة بأكملها، وليس كل مجموعة على حدة.
  • لا يمكنك تصفية الاسم المستعار لدالة النافذة في WHERE؛ لذا ضعه داخل CTE أو استعلام فرعي.
  • أضف عامل كسر تعادل فريدًا للحصول على نتائج حتمية.
  • يحتفظ أعلى 1 بالصف الفائز كاملًا، بخلاف MAX + GROUP BY.

غيّر رقمًا واحدًا، وسيحل الاستعلام نفسه مسألة أعلى 1 أو أعلى 5 أو أي قيمة N.

البدء مجانًا

تعلم Coding Interview Prep مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
90
الدروس
360

الأسئلة الشائعة

هل درس «صفوف Top-N لكل مجموعة باستخدام ROW_NUMBER» مجاني؟

نعم — نص درس «صفوف Top-N لكل مجموعة باستخدام ROW_NUMBER» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

ماذا ستتعلم في «صفوف Top-N لكل مجموعة باستخدام ROW_NUMBER»؟

النمط الأساسي للتقسيم والترتيب الخاص بسؤال أفضل 3 لكل فئة تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟

لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «صفوف Top-N لكل مجموعة باستخدام ROW_NUMBER»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟

نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. صفوف Top-N لكل مجموعة باستخدام ROW_NUMBER
  2. معالجة التعادلات في Top-N
  3. إزالة تكرار الصفوف بأمان
  4. الاحتفاظ بأحدث صف لكل مفتاح
← العودة إلى Coding Interview Prep