0Pricing
SQL Interview Prep · บทเรียน

เก็บแถวล่าสุดของแต่ละคีย์

รูปแบบระเบียนล่าสุดของลูกค้าแต่ละรายด้วยการแบ่งพาร์ทิชันตามคีย์และจัดเรียงตามวันที่

เก็บแถวล่าสุดของแต่ละคีย์ เป็นบทเรียน SQL Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน SQL Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส SQL Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คำถามแถวล่าสุดต่อคีย์

“คืนคำสั่งซื้อที่ล่าสุดของลูกค้าแต่ละราย” “รับสถานะล่าสุดของอุปกรณ์ทุกเครื่อง” ปัญหาแถวล่าสุดต่อคีย์นี้เป็นโจทย์สัมภาษณ์งานด้านเอสคิวแอลที่พบบ่อยมาก เพราะปรากฏอยู่เสมอในงานวิเคราะห์ข้อมูลจริง

นี่คือปัญหาเฉพาะของการหาแถวอันดับหนึ่งต่อกลุ่ม: แบ่งกลุ่มตามคีย์ จัดเรียงตามเวลาจากใหม่ไปเก่า แล้วเก็บแถวแรก บทเรียนนี้จะฝึกใช้รูปแบบดังกล่าวและทางเลือกอื่น ๆ

เหตุใด 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 ของโพสต์เกรส

PostgreSQL มีรูปแบบคำสั่งที่กระชับ: DISTINCT ON (key) จะเก็บแถวแรกของแต่ละคีย์ตาม ORDER BY โดย ORDER BY ต้องเริ่มด้วยคอลัมน์คีย์เดียวกัน จากนั้นจึงตามด้วยตัวตัดสินกรณีเสมอหรือเวลา

รูปแบบนี้อ่านง่ายและทำงานได้รวดเร็วในโพสต์เกรส แต่ไม่สามารถใช้ได้กับทุกระบบ ควรกล่าวถึงในฐานะทางเลือกเฉพาะระบบ ขณะที่ใช้ 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 ในโพสต์เกรสสามารถใช้ดัชนีเดียวกัน และมักเป็นทางเลือกแบบตารางเดียวที่เร็วที่สุด
  • สำหรับบันทึกเหตุการณ์ที่มีการเพิ่มข้อมูลจำนวนมาก ควรพิจารณาตาราง “ล่าสุด” ที่จัดเก็บผลลัพธ์ไว้ล่วงหน้าและปรับปรุงแบบเพิ่มทีละส่วน

ข้อผิดพลาดที่พบบ่อย

ระวังเรื่องต่อไปนี้:

  • ใช้ MAX(date) แล้วคืนเฉพาะวันที่ ไม่ใช่ข้อมูลทั้งแถว
  • ลืมตัวตัดสินกรณีเสมอ ทำให้ผลลัพธ์ไม่แน่นอนเมื่อวันที่เท่ากัน
  • กรองตามเงื่อนไขหลังการจัดอันดับ ซึ่งอาจเลือกแถวที่ควรถูกตัดออก
  • สับสนระหว่าง “แถวล่าสุดหนึ่งแถว” (ROW_NUMBER) กับ “ทุกแถวล่าสุดที่ค่าเท่ากัน” (RANK)

ตรวจสอบอย่างรวดเร็ว

เลือกคำสั่งค้นหาแถวล่าสุดต่อคีย์ที่ถูกต้อง

สรุป: แถวล่าสุดต่อคีย์

รูปแบบคือ: PARTITION BY คีย์, ORDER BY เวลาประทับ DESC (พร้อมตัวตัดสินกรณีเสมอที่ไม่ซ้ำกัน), เก็บ rn = 1

  • MAX(date) ให้วันที่ ไม่ใช่ข้อมูลทั้งแถว
  • เพิ่มตัวตัดสินกรณีเสมอเสมอเพื่อให้ผลลัพธ์แน่นอน
  • ใช้ RANK() = 1 หากต้องการทุกแถวที่มีเวลาประทับล่าสุดเท่ากัน
  • เงื่อนไขตัวกรองควรอยู่ในแบบสอบถามภายใน ก่อนการจัดอันดับ
  • DISTINCT ON ของโพสต์เกรสเป็นทางเลือกเฉพาะระบบที่กระชับและรวดเร็ว

คำถามที่พบบ่อย

บทเรียน “เก็บแถวล่าสุดของแต่ละคีย์” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “เก็บแถวล่าสุดของแต่ละคีย์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส SQL Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส SQL Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “เก็บแถวล่าสุดของแต่ละคีย์”

รูปแบบระเบียนล่าสุดของลูกค้าแต่ละรายด้วยการแบ่งพาร์ทิชันตามคีย์และจัดเรียงตามวันที่ คุณปฏิบัติ SQL Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน SQL Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน SQL Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “เก็บแถวล่าสุดของแต่ละคีย์” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน SQL Interview Prep นี้ได้ไหม

ได้ บทเรียน SQL Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER
  2. จัดการค่าที่เสมอกันในแถว N อันดับแรก
  3. ลบแถวซ้ำอย่างปลอดภัย
  4. เก็บแถวล่าสุดของแต่ละคีย์
← กลับไปที่ SQL Interview Prep