เก็บแถวล่าสุดของแต่ละคีย์
รูปแบบระเบียนล่าสุดของลูกค้าแต่ละรายด้วยการแบ่งพาร์ทิชันตามคีย์และจัดเรียงตามวันที่
เก็บแถวล่าสุดของแต่ละคีย์ เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER
- จัดการค่าที่เสมอกันในแถว N อันดับแรก
- ลบแถวซ้ำอย่างปลอดภัย
- เก็บแถวล่าสุดของแต่ละคีย์