แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER
รูปแบบมาตรฐานของการแบ่งพาร์ทิชันและจัดอันดับสำหรับโจทย์แถว 3 อันดับแรกต่อหมวดหมู่
แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER เป็นบทเรียน SQL Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน SQL Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส SQL Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คำถามเรื่อง N อันดับสูงสุดต่อกลุ่ม
คำถามสัมภาษณ์ SQL ที่พบบ่อยมากข้อหนึ่งฟังดูเรียบง่าย: "ส่งคืนพนักงานที่ได้รับเงินเดือนสูงสุด 3 อันดับแรกในแต่ละแผนก" ผู้สมัครที่เลือกใช้ 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 ตามลำดับการทำงานเชิงตรรกะ ดังนั้นเมื่อ WHERE ทำงาน นามแฝง rn จึงยังไม่มีอยู่
ผู้สัมภาษณ์ชอบใช้จุดนี้เป็นกับดัก วิธีแก้คือคำนวณฟังก์ชันหน้าต่างในแบบสอบถามย่อยหรือ 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 แล้วเลือกข้อมูลจาก CTE นั้นโดยใส่ตัวกรองไว้ใน 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;รูปแบบแบบสอบถามย่อย
หากระบบภาษาของผู้สัมภาษณ์เก่ากว่า หรือผู้สัมภาษณ์ชอบใช้แบบสอบถามย่อย ตรรกะแบบเดียวกันก็ใส่ไว้ในตารางที่สร้างขึ้นภายใน 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 ให้ส่งคืนสินค้า 2 อันดับแรกตามรายได้ในแต่ละภูมิภาค ใช้สูตรเดียวกัน: แบ่งข้อมูลตาม 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)ช่วยให้ระบบสร้างแถวที่เรียงลำดับแล้วภายในแต่ละกลุ่มได้อย่างมีประสิทธิภาพ - วิธีใช้ฟังก์ชันหน้าต่างจะอ่านตารางเพียงครั้งเดียว ซึ่งดีกว่าแบบสอบถามย่อยที่สัมพันธ์กันและทำงานซ้ำสำหรับแต่ละแถวมาก
- สำหรับกรณีเลือกอันดับ 1 ต่อกลุ่มจากข้อมูลขนาดใหญ่มาก ระบบบางประเภทอาจรองรับ
DISTINCT ON(Postgres) เป็นทางลัด แต่ROW_NUMBERเป็นมาตรฐานที่ใช้ได้กับระบบต่าง ๆ
ระบุวิธีตัดสินกรณีคะแนนเท่ากันและยืนยันค่า N ที่ต้องการเสมอ
ตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจของคุณเกี่ยวกับรูปแบบการเลือก N อันดับแรกต่อกลุ่ม
ทบทวน: N อันดับแรกต่อกลุ่ม
สรุปรูปแบบในประโยคเดียว: แบ่งข้อมูลตามกลุ่ม เรียงตามตัวชี้วัด กำหนด ROW_NUMBER แล้วเก็บ rn ≤ N ในแบบสอบถามภายนอก
LIMITจำกัดชุดข้อมูลทั้งหมด ไม่เคยจำกัดแยกตามกลุ่ม- คุณไม่สามารถกรองนามแฝงของฟังก์ชันหน้าต่างใน
WHEREได้ ต้องครอบไว้ใน CTE หรือแบบสอบถามย่อย - เพิ่มตัวตัดสินที่ไม่ซ้ำกันเพื่อให้ผลลัพธ์กำหนดได้แน่นอน
- อันดับ 1 จะเก็บแถวที่ชนะไว้ทั้งแถว ต่างจาก
MAX+GROUP BY
เปลี่ยนตัวเลขเพียงหนึ่งตัว คำสั่งเดิมก็ใช้แก้โจทย์อันดับ 1 อันดับ 5 หรือ N ใด ๆ ได้
คำถามที่พบบ่อย
บทเรียน “แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส SQL Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส SQL Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER”
รูปแบบมาตรฐานของการแบ่งพาร์ทิชันและจัดอันดับสำหรับโจทย์แถว 3 อันดับแรกต่อหมวดหมู่ คุณปฏิบัติ SQL Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน SQL Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน SQL Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน SQL Interview Prep นี้ได้ไหม
ได้ บทเรียน SQL Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER
- จัดการค่าที่เสมอกันในแถว N อันดับแรก
- ลบแถวซ้ำอย่างปลอดภัย
- เก็บแถวล่าสุดของแต่ละคีย์