SQL-5.3 Subqueries & CTE
เมื่อ query เดียวไม่พอ — คุณต้อง query ซ้อน query เช่น “หานักเรียนที่ได้คะแนนสูงกว่าค่าเฉลี่ย” — ค่าเฉลี่ยต้อง query ก่อน แล้วค่อยเอามาเปรียบเทียบ
Subquery ใน WHERE
Section titled “Subquery ใน WHERE”-- สร้างตาราง scoresCREATE TABLE scores ( score_id SERIAL PRIMARY KEY, student_id VARCHAR(6), course_id VARCHAR(6), score NUMERIC(5,2));
INSERT INTO scores VALUES (1,'STD001','CS101',85.50);INSERT INTO scores VALUES (2,'STD001','CS102',72.00);INSERT INTO scores VALUES (3,'STD002','CS101',92.00);INSERT INTO scores VALUES (4,'STD002','CS103',88.50);
-- หานักเรียนที่ได้คะแนนสูงกว่าค่าเฉลี่ยSELECT s.name, sc.scoreFROM students sINNER JOIN scores sc ON s.student_id = sc.student_idWHERE sc.score > ( SELECT AVG(score) FROM scores -- subquery: คำนวณค่าเฉลี่ย);-- ค่าเฉลี่ย = 84.50-- ได้: สมชาย (85.50), สมหญิง (92.00, 88.50)
-- หานักเรียนที่ลงวิชา CS101SELECT name FROM studentsWHERE student_id IN ( SELECT student_id FROM enrollments WHERE course_id = 'CS101');-- ได้: สมชาย, สมหญิงSheets ไม่มี subquery — แต่สามารถซ้อน function ได้:
=FILTER(B:B, C:C > AVERAGE(C:C))AVERAGE(C:C) ทำหน้าที่เหมือน subquery — คำนวณค่าเฉลี่ยก่อน แล้วใช้เป็นเงื่อนไข
avg_score = df['score'].mean()above_avg = df[df['score'] > avg_score]print(above_avg)// คำนวณค่าเฉลี่ยก่อน (subquery)double scores[] = {85.5, 72.0, 92.0, 88.5};double sum = 0;for (int i = 0; i < 4; i++) sum += scores[i];double avg = sum / 4;
// แล้วใช้เป็นเงื่อนไข (outer query)for (int i = 0; i < 4; i++) { if (scores[i] > avg) printf("Score %.1f > avg %.1f\n", scores[i], avg);}Subquery ใน FROM (Derived Table)
Section titled “Subquery ใน FROM (Derived Table)”-- นับจำนวนวิชาที่แต่ละคนลง แล้วดึงเฉพาะคนที่ลง >= 2 วิชาSELECT name, course_countFROM ( SELECT s.name, COUNT(e.course_id) AS course_count FROM students s LEFT JOIN enrollments e ON s.student_id = e.student_id GROUP BY s.name) AS student_courses -- ต้องตั้งชื่อ aliasWHERE course_count >= 2;-- ได้: สมชาย (2), สมหญิง (2)ใช้ QUERY function ซ้อน QUERY function (ยากมาก — ไม่แนะนำ)
# chain operations — Python อ่านง่ายกว่า SQL ตรงนี้result = (df.groupby('name')['course_id'] .count() .reset_index(name='course_count') .query('course_count >= 2'))// ใน C ต้อง 2 loops:// loop 1: สร้าง intermediate result (subquery)// loop 2: กรอง intermediate resultCTE — Common Table Expression (WITH Clause)
Section titled “CTE — Common Table Expression (WITH Clause)”CTE ทำสิ่งเดียวกับ subquery แต่ อ่านง่ายกว่ามาก:
-- แบบ subquery (อ่านยาก):SELECT name, course_countFROM ( SELECT s.name, COUNT(e.course_id) AS course_count FROM students s LEFT JOIN enrollments e ON s.student_id = e.student_id GROUP BY s.name) AS student_coursesWHERE course_count >= 2;
-- แบบ CTE (อ่านง่าย):WITH student_courses AS ( SELECT s.name, COUNT(e.course_id) AS course_count FROM students s LEFT JOIN enrollments e ON s.student_id = e.student_id GROUP BY s.name)SELECT name, course_countFROM student_coursesWHERE course_count >= 2;CTE หลายชั้น — pipeline style:
WITH-- ขั้น 1: รวมคะแนนstudent_scores AS ( SELECT student_id, AVG(score) AS avg_score FROM scores GROUP BY student_id),-- ขั้น 2: เพิ่มชื่อscored_students AS ( SELECT s.name, ss.avg_score FROM students s INNER JOIN student_scores ss ON s.student_id = ss.student_id)-- ขั้น 3: กรองเฉพาะคนที่เกิน 80SELECT name, ROUND(avg_score, 2) AS avg_scoreFROM scored_studentsWHERE avg_score > 80ORDER BY avg_score DESC;Sheets ไม่มี CTE — ต้องใช้ helper columns แยกขั้นตอนแทน ซึ่งก็คือแนวคิดเดียวกัน: ทำทีละขั้น ไม่ยัดทุกอย่างในสูตรเดียว
# Python pipeline = method chaining# เทียบเท่า CTE:step1 = scores.groupby('student_id')['score'].mean().reset_index()step1.columns = ['student_id', 'avg_score']
step2 = students.merge(step1, on='student_id')
result = step2[step2['avg_score'] > 80].sort_values('avg_score', ascending=False)print(result)// CTE ใน C = intermediate variables// step 1: คำนวณ avgdouble avg_scores[MAX_STUDENTS];// step 2: merge กับ names// step 3: filter// C บังคับให้ทำทีละขั้นอยู่แล้วเมื่อไหร่ใช้อะไร
Section titled “เมื่อไหร่ใช้อะไร”| สถานการณ์ | ใช้ |
|---|---|
| ค่าเดียวสำหรับ comparison | Subquery ใน WHERE |
| list ค่าสำหรับ IN | Subquery ใน WHERE |
| ผลลัพธ์ intermediate ที่ต้อง query ต่อ | Subquery ใน FROM หรือ CTE |
| Query ยาวๆ ที่ต้องอ่านง่าย | CTE (WITH) — แนะนำเสมอ |
| ใช้ intermediate result หลายครั้ง | CTE — ประกาศครั้งเดียว ใช้ซ้ำได้ |