الاحتفاظ بأحدث صف لكل مفتاح
نمط أحدث سجل لكل عميل باستخدام التقسيم حسب المفتاح والترتيب حسب التاريخ
الاحتفاظ بأحدث صف لكل مفتاح درس مجاني في SQL Interview Prep على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في SQL Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة SQL Interview Prep 4 دروس في المجموع.
سؤال الأحدث لكل مفتاح
«أعِد أحدث طلب لكل عميل.» «احصل على أحدث حالة لكل جهاز.» تُعد مشكلة الصف الأحدث لكل مفتاح من أكثر مهام مقابلات SQL شيوعًا، لأنها تظهر باستمرار في أعمال التحليلات الواقعية.
وهي حالة متخصصة من مشكلة أعلى صف لكل مجموعة: قسّم البيانات بحسب المفتاح، ورتّبها بحسب الطابع الزمني ترتيبًا تنازليًا، واحتفظ بالصف الأول. يدرّبك هذا الدرس على النمط وبدائله.
لماذا لا تكفي MAX وحدها
قد تكون الإجابة الأولى المغرية هي استخدام MAX(order_date) مع التجميع بحسب العميل. فهذا يعيد تاريخ أحدث طلب، لكنه لا يعيد بقية أعمدة ذلك الطلب، مثل معرّف الطلب أو المبلغ أو الحالة.
إذا أراد المُحاوِر الصف الكامل لأحدث طلب، فإن استخدام MAX مع GROUP BY يتطلب ربطًا إضافيًا يعيدك إلى الجدول باستخدام المفتاح والتاريخ الأقصى، وهو أسلوب مطوّل وقد يفشل عند وجود تعادل. دوال النوافذ أنظف.
-- Gives the date, not the full row
SELECT customer_id, MAX(order_date) AS last_order
FROM orders
GROUP BY customer_id;نمط ROW_NUMBER
قسّم البيانات بحسب المفتاح، ورتّبها بحسب الطابع الزمني ترتيبًا تنازليًا، وسيحصل الصف الأحدث على rn = 1. احتفظ بهذه الصفوف فقط، وستحصل على السجل الكامل الأحدث لكل مفتاح.
هذه هي الإجابة المعتادة. فهي تعيد صفًا واحدًا بالضبط لكل مفتاح، حتى عند تعادل الطوابع الزمنية، وهو ما يُفهم عادةً من عبارة «الصف الأحدث».
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY order_date DESC
) AS rn
FROM orders
)
SELECT customer_id, order_id, order_date, amount
FROM ranked
WHERE rn = 1;كسر التعادل بين الطوابع الزمنية
قد يتشارك طلبان للعميل نفسه في order_date، سواء في اليوم نفسه أو بسبب تطابق الطوابع الزمنية. ومن دون عامل كسر تعادل، يكون اختيار الصف الذي سيحصل على rn = 1 عشوائيًا، وقد يتغير بين عمليات التشغيل.
أضف مفتاحًا ثانويًا فريدًا، مثل order_id DESC، كي يكون الصف الأحدث محددًا بشكل حتمي. ويتحقق المُحاوِرون تحديدًا مما إذا كنت قد انتبهت إلى هذه الحالة الحدّية.
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY order_date DESC, order_id DESC
) AS rnالأحدث مقابل جميع الصفوف المتعادلة
حدّد معنى «الأحدث» عندما تتعادل الطوابع الزمنية:
- إذا أردت صفًا واحدًا بالضبط لكل مفتاح → استخدم
ROW_NUMBERمع عامل كسر تعادل. - إذا أردت جميع الصفوف التي تشترك في الطابع الزمني الأقصى → استخدم
RANK() = 1بدلًا من ذلك، إذ يعيد كل صفوف الأحدث المتعادلة.
إن طرح هذا السؤال التوضيحي يبيّن أنك تفهم الدلالات، وليس الصياغة النحوية فحسب.
WITH ranked AS (
SELECT *,
RANK() OVER (
PARTITION BY customer_id ORDER BY order_date DESC
) AS rnk
FROM orders
)
SELECT * FROM ranked WHERE rnk = 1;البديل باستخدام الاستعلام الفرعي المرتبط
قبل أن تصبح دوال النوافذ متاحة على نطاق واسع، كانت الإجابة عن سؤال الأحدث لكل مفتاح تعتمد على استعلام فرعي مرتبط: احتفظ بالصف فقط إذا لم يوجد صف آخر للمفتاح نفسه يملك تاريخًا أكبر.
يعمل هذا الأسلوب، لكنه ينفّذ الاستعلام الداخلي لكل صف، ولذلك يكون أبطأ في الجداول الكبيرة وغير مريح عند وجود تعادل. اذكره لإظهار اتساع خبرتك، لكن فضّل إجابة دالة النافذة لأسباب تتعلق بالأداء.
SELECT o.*
FROM orders o
WHERE o.order_date = (
SELECT MAX(o2.order_date)
FROM orders o2
WHERE o2.customer_id = o.customer_id
);اختصار DISTINCT ON في Postgres
يوفر PostgreSQL صياغة موجزة: تحتفظ DISTINCT ON (key) بالصف الأول لكل مفتاح وفقًا لـ ORDER BY. ويجب أن تبدأ ORDER BY بأعمدة المفتاح نفسها، ثم بعامل كسر التعادل أو الطابع الزمني.
هذه الصياغة أنيقة وسريعة في Postgres، لكنها غير محمولة بين الأنظمة. اذكرها كميزة خاصة باللهجة، مع إبقاء ROW_NUMBER خيارك الافتراضي المحمول.
SELECT DISTINCT ON (customer_id)
customer_id, order_id, order_date, amount
FROM orders
ORDER BY customer_id, order_date DESC, order_id DESC;الصف الأحدث مع شرط
تضيف الأسئلة الواقعية عوامل تصفية، مثل: «أحدث طلب مكتمل لكل عميل». طبّق عامل التصفية قبل الترتيب، حتى لا تُرقّم إلا الصفوف المطابقة للشروط.
ضع الشرط في WHERE للاستعلام الداخلي، إذ يُنفّذ قبل دالة النافذة، ثم خذ الصفوف التي تحقق rn = 1 في الاستعلام الخارجي. أما التصفية بعد الترتيب فستعيد الصف الخطأ.
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer_id ORDER BY order_date DESC, order_id DESC
) AS rn
FROM orders
WHERE status = 'completed'
)
SELECT * FROM ranked WHERE rn = 1;مثال تطبيقي: أحدث حالة للجهاز
يسجل جدول status_log الأعمدة device_id وstatus وlogged_at. للحصول على الحالة الحالية لكل جهاز، قسّم البيانات بحسب device_id، ورتّبها بحسب logged_at DESC، واحتفظ بالصفوف التي تحقق rn = 1.
هذا هو الأساس الذي تعتمد عليه لوحات المعلومات لعرض «الحالة الحالية» لعدد كبير من الكيانات انطلاقًا من سجل أحداث لا تتم فيه عمليات التحديث. وتستخدم الوصفة نفسها في استعلامات أحدث سعر وأحدث موقع وأحدث إصدار.
WITH latest AS (
SELECT device_id, status, logged_at,
ROW_NUMBER() OVER (
PARTITION BY device_id ORDER BY logged_at DESC
) AS rn
FROM status_log
)
SELECT device_id, status, logged_at
FROM latest
WHERE rn = 1;ملاحظات حول الأداء
نقاط تستحق الإشارة إليها للحصول على تقييم متقدم:
- يتيح الفهرس على
(customer_id, order_date DESC)للمحرك قراءة أحدث صف لكل مفتاح بكفاءة. - يمسح أسلوب النافذة الجدول مرة واحدة، بينما لا يفعل الاستعلام الفرعي المرتبط ذلك.
- يمكن لـ
DISTINCT ONفي Postgres استخدام الفهرس نفسه، وغالبًا ما يكون الخيار الأسرع على جدول واحد. - بالنسبة إلى سجلات الأحداث التي تكثر فيها عمليات الإضافة، فكّر في إنشاء جدول «أحدث قيمة» مادي يُحدّث تدريجيًا.
الأخطاء الشائعة
انتبه إلى ما يلي:
- استخدام
MAX(date)وإعادة التاريخ فقط، لا الصف الكامل. - نسيان عامل كسر التعادل، مما يؤدي إلى نتائج غير حتمية عند تعادل التواريخ.
- تطبيق التصفية على الشرط بعد الترتيب، مما قد يؤدي إلى اختيار صف كان ينبغي استبعاده.
- الخلط بين «صف واحد هو الأحدث» باستخدام
ROW_NUMBERو«جميع الصفوف الأحدث المتعادلة» باستخدامRANK.
اختبار سريع
اختر الاستعلام الصحيح لإعادة الصف الأحدث لكل مفتاح.
مراجعة: الصف الأحدث لكل مفتاح
النمط هو: PARTITION BY key, ORDER BY timestamp DESC (plus a unique tiebreaker), keep rn = 1.
- تعيد
MAX(date)التاريخ، لا الصف الكامل. - أضف دائمًا عامل كسر تعادل لضمان حتمية النتائج.
- استخدم
RANK() = 1إذا أردت جميع الصفوف المتعادلة عند أحدث طابع زمني. - يجب وضع شروط التصفية في الاستعلام الداخلي، قبل الترتيب.
- يُعد
DISTINCT ONفي Postgres بديلًا موجزًا وسريعًا خاصًا باللهجة.
الأسئلة الشائعة
هل درس «الاحتفاظ بأحدث صف لكل مفتاح» مجاني؟
نعم — نص درس «الاحتفاظ بأحدث صف لكل مفتاح» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة SQL Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة SQL Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «الاحتفاظ بأحدث صف لكل مفتاح»؟
نمط أحدث سجل لكل عميل باستخدام التقسيم حسب المفتاح والترتيب حسب التاريخ تتمرن على SQL Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ SQL Interview Prep؟
لا تُشترط خبرة سابقة. SQL Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «الاحتفاظ بأحدث صف لكل مفتاح»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس SQL Interview Prep هذا؟
نعم. كل درس في SQL Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- صفوف Top-N لكل مجموعة باستخدام ROW_NUMBER
- معالجة التعادلات في Top-N
- إزالة تكرار الصفوف بأمان
- الاحتفاظ بأحدث صف لكل مفتاح