0Pricing
Coding Interview Prep · درس

ثاني أعلى راتب بخمس طرق

مقارنة حلول الاستعلامات الفرعية وLIMIT/OFFSET ودوال النوافذ

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

السؤال الذي يواجهه الجميع

يُعد سؤال «اعثر على ثاني أعلى راتب» أكثر أسئلة مقابلات SQL شيوعًا على الإطلاق. ويحبّه المُحاوِرون لأنه يملك إجابات صحيحة متعددة، إضافةً إلى عدة مطبّات دقيقة.

افترض وجود جدول employee يحتوي على العمودين id وsalary. مهمتك هي إعادة قيمة الراتب المميزة التي تأتي في المرتبة الثانية من حيث الارتفاع.

  • إذا كانت الرواتب هي 300 و200 و200 و100، فالإجابة هي 200، وليست الصف الثاني.
  • إذا لم يوجد راتب مميز ثانٍ، فالإجابة المتوقعة عادةً هي NULL.

خلال المشاهد التالية، سنحل المسألة بخمس طرق مختلفة، ونناقش الحالات التي تتفوق فيها كل طريقة.

CREATE TABLE employee (
  id     INT PRIMARY KEY,
  salary INT
);

الطريقة 1: MAX للقيم الأقل من MAX

الحل الأكثر بداهة هو أن ثاني أعلى راتب هو أكبر راتب يقلّ تمامًا عن القيمة العظمى الإجمالية.

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

ميزة إضافية: إذا لم يوجد راتب مميز ثانٍ، فسيجمع MAX الخارجي صفر صفوف ويعيد NULL تلقائيًا. وهذه القيمة المجانية NULL هي بالضبط ما يريده المُحاوِرون.

SELECT MAX(salary) AS second_highest
FROM employee
WHERE salary < (SELECT MAX(salary) FROM employee);

لماذا يعالج الاستعلام الفرعي القيم المكررة

لاحظ أننا لم نستخدم DISTINCT في الطريقة 1، ومع ذلك عولجت القيم المكررة بشكل صحيح.

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

هذه هي الفكرة الأساسية: التجميعات تدمج القيم المكررة نيابةً عنك. يبالغ كثير من المرشحين في تعقيد الحل باستخدام DISTINCT، مع أن التجميع ينفذ بالفعل ما يلزم.

الطريقة 2: LIMIT مع OFFSET

في MySQL وPostgreSQL، يمكنك ترتيب الرواتب غير المكررة تنازليًا وتجاوز الراتب الأول.

  • يتجاوز OFFSET 1 أعلى راتب.
  • يُبقي LIMIT 1 على الراتب التالي فقط.

يُعد DISTINCT ضروريًا هنا، وإلا فستجعل الرواتب العليا المكررة OFFSET 1 يصل إلى تكرار للقيمة العظمى بدلًا من الراتب الثاني الحقيقي.

تنبيه: إذا لم توجد قيمة ثانية غير مكررة، فسيُرجع هذا الحل صفر صفوف، وليس NULL. سنعالج هذه الحالة الحدية في الدرس 4.

SELECT DISTINCT salary
FROM employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1;

الطريقة 3: FETCH لـ SQL Server وOracle

لا يدعم SQL Server وOracle الحديثان الصياغة LIMIT ... OFFSET. وبدلًا من ذلك، يستخدمان صياغة معيار ANSI، وهي OFFSET ... FETCH.

المنطق مطابق للطريقة 2: ترتيب الرواتب غير المكررة تنازليًا، وتجاوز صف واحد، ثم جلب صف واحد. إن معرفة الصياغة المقابلة بين لهجات SQL تُظهر للمحاور خبرة عملية حقيقية.

SELECT DISTINCT salary
FROM employee
ORDER BY salary DESC
OFFSET 1 ROWS
FETCH NEXT 1 ROWS ONLY;

الطريقة 4: دالة النافذة DENSE_RANK

يستخدم الأسلوب الحديث القابل للتوسع دالة نافذة. تُسند DENSE_RANK الترتيب 1 إلى أعلى راتب، والترتيب 2 إلى الراتب التالي غير المكرر، وتمنح الرواتب المتساوية الترتيب نفسه من دون فجوات.

نحسب الترتيب في استعلام فرعي، ثم نرشّح الترتيب 2 في الاستعلام الخارجي. تذكّر أنه لا يمكنك التصفية باستخدام دالة نافذة مباشرةً في WHERE، لذلك لا بد من تغليفها باستعلام فرعي.

SELECT salary AS second_highest
FROM (
  SELECT salary,
         DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
  FROM employee
) ranked
WHERE rnk = 2;

لماذا نستخدم DENSE_RANK لا RANK أو ROW_NUMBER

يؤثر اختيار دالة الترتيب في دلالات القيم «غير المكررة»:

  • تمنح ROW_NUMBER كل صف رقمًا فريدًا، لذلك إذا كان شخصان يتقاضيان 300 فسيكون الصفان 1 و2، وسيكون الترتيب 2 تكرارًا لأعلى راتب. وهذا خطأ.
  • تترك RANK فجوات بعد حالات التعادل: يحصل راتبا 300 على الترتيب 1، ثم يقفز الراتب التالي إلى الترتيب 3. وبذلك ستفقده عند الترتيب 2. وهذا خطأ.
  • تمنح DENSE_RANK القيم المتساوية الترتيب نفسه من دون فجوات، لذلك يكون الترتيب 2 دائمًا هو الراتب الثاني غير المكرر. وهذا صحيح.

الطريقة 5: العد باستخدام استعلام فرعي مترابط

هذه حيلة كلاسيكية من حقبة ما قبل دوال النوافذ: يكون الراتب هو الراتب رقم N من الأعلى إذا كان عدد الرواتب غير المكررة الأكبر منه تمامًا يساوي N-1.

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

ويمكن تعميمه بسهولة لإيجاد الراتب رقم N من الأعلى بتغيير العدد إلى N - 1، ولهذا يحب المحاورون رؤيته.

SELECT salary AS second_highest
FROM employee e
WHERE 1 = (
  SELECT COUNT(DISTINCT e2.salary)
  FROM employee e2
  WHERE e2.salary > e.salary
);

مثال تطبيقي كامل من البداية إلى النهاية

لنفترض أن الرواتب هي: 500، 500، 350، 350، 100.

  • الطريقة 1: قيمة MAX هي 500، وأكبر قيمة أقل من 500 هي 350. إذن الإجابة هي 350.
  • الطريقة 4 (DENSE_RANK): 500 ← الترتيب 1، و350 ← الترتيب 2، و100 ← الترتيب 3. إذن الترتيب 2 هو 350.
  • الطريقة 5: بالنسبة إلى الراتب 350، يوجد راتب واحد غير مكرر فقط (500) أكبر منه. إذن ينطبق الشرط، والإجابة هي 350.

تتفق الطرق الخمس جميعًا: ثاني أعلى راتب غير مكرر هو 350، حتى مع وجود قيم مكررة.

أي طريقة ينبغي أن تختار

إرشادات المقابلة:

  • ابدأ بتحديد المطلوب: «هل تريد الرواتب غير المكررة، وتريد NULL إذا لم يوجد أي منها؟» إن طلب التوضيح يكسبك نقاطًا.
  • DENSE_RANK هو الإجابة الافتراضية الأقوى؛ إذ يمكن تعميمه بسهولة إلى الراتب رقم N وإلى كل مجموعة.
  • MAX-below-MAX هو أفضل حل في سطر واحد، ويعيد NULL دون معالجة إضافية.
  • LIMIT/OFFSET مختصر، لكنه خاص بلهجة معينة ولا يعيد أي صفوف في الحالة الحدية.

إن ذكر المفاضلات بصوت واضح هو ما يميز إجابة متوسطة المستوى عن إجابة مبتدئ.

أخطاء شائعة يجب تجنبها

انتبه إلى هذه الفخاخ التي يضعها المحاورون:

  • استخدام ROW_NUMBER بدلًا من DENSE_RANK، مما يؤدي إلى ظهور أعلى راتب مرتين.
  • نسيان DISTINCT في إصدار LIMIT/OFFSET عند وجود قيم مكررة لأعلى راتب.
  • افتراض أن ORDER BY salary DESC LIMIT 1,1 يعيد قيمة غير مكررة، وهذا غير صحيح.
  • إرجاع الصف الثاني بدلًا من القيمة الثانية.

تحقق سريع

اختبر مدى فهمك لاختيار دالة الترتيب.

الخلاصة

أصبحت لديك الآن خمس طرق لإيجاد ثاني أعلى راتب:

  • MAX below MAX - محمولة بين الأنظمة، وتعيد NULL دون معالجة إضافية.
  • LIMIT/OFFSET وOFFSET/FETCH - مختصرة، لكنها خاصة بلهجة معينة.
  • DENSE_RANK - الخيار الافتراضي القابل للتوسع، ويتعامل مع حالات التعادل بشكل صحيح.
  • Correlated count - أنيق ويمكن تعميمه لإيجاد الراتب رقم N.

أهم النقاط: اسأل عما إذا كانت القيم غير المكررة مطلوبة، وفضّل DENSE_RANK للتعامل مع التعادلات، وتذكّر أي الطرق تعيد NULL وأيها لا تعيد صفوفًا عندما لا توجد قيمة ثانية.

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

هل درس «ثاني أعلى راتب بخمس طرق» مجاني؟

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

ماذا ستتعلم في «ثاني أعلى راتب بخمس طرق»؟

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

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

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

كم من الوقت يستغرق درس «ثاني أعلى راتب بخمس طرق»؟

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

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

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

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

  1. ثاني أعلى راتب بخمس طرق
  2. القيمة الأعلى رقم N باستخدام DENSE_RANK
  3. أعلى دخل في كل قسم
  4. إرجاع NULL عند عدم وجود قيمة رقم N
← العودة إلى Coding Interview Prep