Skip to content

SQL-5.3 Subqueries & CTE

เมื่อ query เดียวไม่พอ — คุณต้อง query ซ้อน query เช่น “หานักเรียนที่ได้คะแนนสูงกว่าค่าเฉลี่ย” — ค่าเฉลี่ยต้อง query ก่อน แล้วค่อยเอามาเปรียบเทียบ

-- สร้างตาราง scores
CREATE 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.score
FROM students s
INNER JOIN scores sc ON s.student_id = sc.student_id
WHERE sc.score > (
SELECT AVG(score) FROM scores -- subquery: คำนวณค่าเฉลี่ย
);
-- ค่าเฉลี่ย = 84.50
-- ได้: สมชาย (85.50), สมหญิง (92.00, 88.50)
-- หานักเรียนที่ลงวิชา CS101
SELECT name FROM students
WHERE student_id IN (
SELECT student_id FROM enrollments
WHERE course_id = 'CS101'
);
-- ได้: สมชาย, สมหญิง
-- นับจำนวนวิชาที่แต่ละคนลง แล้วดึงเฉพาะคนที่ลง >= 2 วิชา
SELECT name, course_count
FROM (
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 -- ต้องตั้งชื่อ alias
WHERE course_count >= 2;
-- ได้: สมชาย (2), สมหญิง (2)

CTE — Common Table Expression (WITH Clause)

Section titled “CTE — Common Table Expression (WITH Clause)”

CTE ทำสิ่งเดียวกับ subquery แต่ อ่านง่ายกว่ามาก:

-- แบบ subquery (อ่านยาก):
SELECT name, course_count
FROM (
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
WHERE 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_count
FROM student_courses
WHERE 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: กรองเฉพาะคนที่เกิน 80
SELECT name, ROUND(avg_score, 2) AS avg_score
FROM scored_students
WHERE avg_score > 80
ORDER BY avg_score DESC;

เมื่อไหร่ใช้อะไร

Section titled “เมื่อไหร่ใช้อะไร”
สถานการณ์ใช้
ค่าเดียวสำหรับ comparisonSubquery ใน WHERE
list ค่าสำหรับ INSubquery ใน WHERE
ผลลัพธ์ intermediate ที่ต้อง query ต่อSubquery ใน FROM หรือ CTE
Query ยาวๆ ที่ต้องอ่านง่ายCTE (WITH) — แนะนำเสมอ
ใช้ intermediate result หลายครั้งCTE — ประกาศครั้งเดียว ใช้ซ้ำได้