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

แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER

รูปแบบมาตรฐานของการแบ่งพาร์ทิชันและจัดอันดับสำหรับโจทย์แถว 3 อันดับแรกต่อหมวดหมู่

แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding 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) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER”

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

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

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

บทเรียน “แถว N อันดับแรกต่อกลุ่มด้วย ROW_NUMBER” ใช้เวลานานแค่ไหน

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

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

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

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

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