สถานการณ์
มีข้อมูลนักเรียนหลายคนมีคะแนนเท่ากัน (StudentMarks = 79) ต้องการเลือกแค่ 1 คนต่อ 1 คะแนน (เช่น คนที่ Id น้อยที่สุด)
วิธีแก้: Subquery หา Min Id ต่อ Group
SELECT * FROM DemoTable1572 WHERE StudentId IN (SELECT MIN(StudentId) FROM DemoTable1572 GROUP BY StudentMarks);
ตัวอย่างการใช้งาน
1. สร้างตาราง
mysql> CREATE TABLE DemoTable1572 (
-> StudentId INT,
-> StudentMarks INT,
-> StudentName VARCHAR(20)
-> );
Query OK, 0 rows affected (0.56 sec)
2. เพิ่มข้อมูล (Sam และ Bob คะแนนเท่ากัน 79)
mysql> INSERT INTO DemoTable1572 VALUES(1, 79, 'Sam'); Query OK, 1 row affected (0.10 sec) mysql> INSERT INTO DemoTable1572 VALUES(2, 89, 'Chris'); Query OK, 1 row affected (0.12 sec) mysql> INSERT INTO DemoTable1572 VALUES(3, 98, 'David'); Query OK, 1 row affected (0.22 sec) mysql> INSERT INTO DemoTable1572 VALUES(4, 79, 'Bob'); Query OK, 1 row affected (0.10 sec)
4. Query: เลือกคนแรก (Min Id) ของแต่ละคะแนน
mysql> SELECT * FROM DemoTable1572
-> WHERE StudentId IN (SELECT MIN(StudentId) FROM DemoTable1572 GROUP BY StudentMarks);
ผลลัพธ์ (Sam Id=1 ถูกเลือก แทน Bob Id=4 สำหรับ Marks=79):
+-----------+--------------+-------------+ | StudentId | StudentMarks | StudentName | +-----------+--------------+-------------+ | 1 | 79 | Sam | | 2 | 89 | Chris | | 3 | 98 | David | +-----------+--------------+-------------+ 3 rows in set (0.10 sec)
วิธีอื่นๆ (MySQL 8.0+ แนะนำ)
1. ROW_NUMBER() - ควบคุมได้ละเอียดที่สุด
SELECT StudentId, StudentMarks, StudentName FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY StudentMarks ORDER BY StudentId) AS rn
FROM DemoTable1572
) t WHERE rn = 1;
2. JOIN กับ Derived Table
SELECT d.* FROM DemoTable1572 d JOIN (SELECT StudentMarks, MIN(StudentId) AS MinId FROM DemoTable1572 GROUP BY StudentMarks) m ON d.StudentMarks = m.StudentMarks AND d.StudentId = m.MinId;
3. GROUP BY ด้วย ANY_VALUE (MySQL 5.7+ ONLY_FULL_GROUP_BY)
SELECT StudentMarks, ANY_VALUE(StudentId), ANY_VALUE(StudentName) FROM DemoTable1572 GROUP BY StudentMarks;
เปรียบเทียบวิธีเลือกแถวใน Group
| วิธี | ควบคุมได้ว่าเลือกแถวไหน | Performance | MySQL Version |
|---|---|---|---|
| ANY_VALUE() | ไม่ได้ (Random) | เร็ว | 5.7+ |
| MIN/MAX Id + JOIN | ได้ (Min/Max Id) | ดี | All |
| ROW_NUMBER() | ได้หมด (Order By ใดๆ) | ดีมาก | 8.0+ |
| Subquery IN | ได้ (Min/Max) | ปานกลาง | All |
กรณีการใช้งานจริง
- Deduplication: เก็บแถวแรก/ล่าสุดของแต่ละ Group
- Top-N per Group: สินค้าขายดี Top 3 ต่อ Category
- Latest Record: สถานะล่าสุดของแต่ละ Device/User
- First Purchase: ลูกค้าซื้อครั้งแรกของแต่ละ Product