SQL ขั้นสูงสำหรับสัมภาษณ์ Data Analyst: Subquery, Pivot และการเพิ่มประสิทธิภาพ Query 2026
คู่มือ SQL ขั้นสูงสำหรับเตรียมสัมภาษณ์ Data Analyst 2026: correlated subquery, pivot query ด้วย conditional aggregation, EXPLAIN ANALYZE, กลยุทธ์ indexing และ anti-pattern ที่ต้องหลีกเลี่ยง

การสัมภาษณ์งานในตำแหน่ง Data Analyst ในปัจจุบันไม่ได้วัดเพียงความสามารถในการเขียน SELECT พื้นฐานเท่านั้น แต่ยังต้องการทักษะ SQL ขั้นสูงที่สามารถจัดการกับข้อมูลที่ซับซ้อน วิเคราะห์ประสิทธิภาพของ Query และออกแบบโครงสร้างฐานข้อมูลที่เหมาะสมได้ ผู้สมัครที่เข้าใจเทคนิคอย่าง Correlated Subquery, Pivot Query, EXPLAIN Plan และ Indexing Strategy จะมีความได้เปรียบอย่างชัดเจนในกระบวนการคัดเลือก บทความนี้รวบรวมหัวข้อ SQL ขั้นสูงที่พบบ่อยที่สุดในการสัมภาษณ์ตำแหน่ง Data Analyst พร้อมตัวอย่างโค้ดที่นำไปใช้งานได้จริง
ในการสัมภาษณ์งาน ผู้สัมภาษณ์มักไม่ได้ต้องการเพียงคำตอบที่ถูกต้อง แต่ต้องการเห็นกระบวนการคิดด้วย การอธิบายว่าเหตุใดจึงเลือกใช้ CTE แทน Correlated Subquery หรือเหตุใด EXISTS จึงเหมาะสมกว่า IN ในบางสถานการณ์ จะช่วยแสดงถึงความเข้าใจเชิงลึกที่ทำให้โดดเด่นจากผู้สมัครคนอื่น
Correlated Subquery กับ Regular Subquery: ความแตกต่างที่สำคัญ
Regular Subquery คือ Subquery ที่ทำงานอิสระจาก Query หลัก โดยจะถูกประมวลผลเพียงครั้งเดียวแล้วนำผลลัพธ์ไปใช้กับ Query ภายนอก ตัวอย่างเช่น การหาคำสั่งซื้อที่มีมูลค่าสูงกว่าค่าเฉลี่ยของทั้งหมด
-- regular_subquery_threshold.sql
-- Find all orders above the average order value
SELECT
order_id,
customer_id,
total_amount
FROM orders
WHERE total_amount > (
-- Executes once, returns a single scalar value
SELECT AVG(total_amount)
FROM orders
);ในทางตรงกันข้าม Correlated Subquery จะอ้างอิงข้อมูลจาก Query ภายนอก ทำให้ต้องถูกประมวลผลซ้ำทุกครั้งสำหรับแต่ละแถวของ Query หลัก สิ่งนี้ส่งผลต่อประสิทธิภาพอย่างมากเมื่อทำงานกับข้อมูลจำนวนมาก ตัวอย่างต่อไปนี้แสดงการค้นหาพนักงานที่มีเงินเดือนสูงกว่าค่าเฉลี่ยของแผนกตนเอง
-- correlated_subquery_department.sql
-- Employees earning above their department average
SELECT
e.employee_id,
e.name,
e.department,
e.salary
FROM employees e
WHERE e.salary > (
-- Re-evaluated for each employee's department
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.department = e.department
);วิธีที่ดีกว่าสำหรับ Query ข้างต้นคือการใช้ Common Table Expression (CTE) เพื่อคำนวณค่าเฉลี่ยของแต่ละแผนกเพียงครั้งเดียว แล้วจึง JOIN กลับมาเปรียบเทียบ วิธีนี้ลดจำนวนการสแกนตารางลงอย่างมาก
-- optimized_with_cte.sql
-- Same result, single pass over the data
WITH dept_avg AS (
SELECT
department,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department
)
SELECT
e.employee_id,
e.name,
e.department,
e.salary
FROM employees e
JOIN dept_avg d ON e.department = d.department
WHERE e.salary > d.avg_salary;การเปลี่ยนจาก Correlated Subquery เป็น CTE + JOIN เป็นเทคนิคที่ผู้สัมภาษณ์มักต้องการเห็น เพราะแสดงให้เห็นว่าผู้สมัครคำนึงถึงประสิทธิภาพของ Query ไม่ใช่เพียงแค่ความถูกต้องของผลลัพธ์
Subquery Patterns ที่ต้องรู้
EXISTS กับ IN: เลือกใช้อย่างไร
หนึ่งในคำถามที่พบบ่อยในการสัมภาษณ์คือความแตกต่างระหว่าง EXISTS กับ IN เมื่อใช้กับ Subquery ในแง่ของผลลัพธ์ ทั้งสองให้คำตอบเดียวกัน แต่ในแง่ของประสิทธิภาพนั้นแตกต่างกันอย่างมีนัยสำคัญ โดยเฉพาะเมื่อ Subquery ส่งคืนข้อมูลจำนวนมาก EXISTS จะหยุดทำงานทันทีเมื่อพบแถวแรกที่ตรงเงื่อนไข ในขณะที่ IN ต้องประมวลผล Subquery ทั้งหมดก่อน
-- exists_vs_in.sql
-- Customers who placed at least one order in 2026 (EXISTS — preferred)
SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
AND o.order_date >= '2026-01-01'
);
-- Equivalent with IN (slower on large datasets)
SELECT customer_id, name
FROM customers
WHERE customer_id IN (
SELECT customer_id
FROM orders
WHERE order_date >= '2026-01-01'
);Scalar Subquery ใน SELECT
อีกรูปแบบหนึ่งที่ควรรู้จักคือ Scalar Subquery ใน SELECT clause ซึ่งส่งคืนค่าเดียวสำหรับแต่ละแถว วิธีนี้มีประโยชน์เมื่อต้องการแสดงข้อมูลรวม (Aggregated Data) ควบคู่กับข้อมูลรายบุคคล แต่ควรระมัดระวังเรื่องประสิทธิภาพเพราะทำงานคล้าย Correlated Subquery
-- scalar_subquery_select.sql
-- Each product with its category's total revenue
SELECT
p.product_id,
p.product_name,
p.category,
(
SELECT SUM(oi.quantity * oi.unit_price)
FROM order_items oi
JOIN products p2 ON oi.product_id = p2.product_id
WHERE p2.category = p.category
) AS category_total_revenue
FROM products p;สำหรับกรณีนี้ ในสถานการณ์จริงควรพิจารณาใช้ Window Function หรือ CTE แทนเพื่อประสิทธิภาพที่ดีกว่า การเข้าใจทั้งวิธีที่ใช้งานได้และวิธีที่เหมาะสมที่สุดเป็นสิ่งที่ผู้สัมภาษณ์ให้ความสำคัญ
Pivot Query ด้วย Conditional Aggregation
Pivot Query เป็นเทคนิคที่ Data Analyst ต้องใช้บ่อยมาก เพราะการแปลงข้อมูลจากแถวเป็นคอลัมน์ (Row to Column) ช่วยให้ข้อมูลอ่านง่ายขึ้นและเหมาะสำหรับการนำเสนอในรายงาน วิธีที่นิยมที่สุดคือการใช้ CASE ร่วมกับ Aggregate Function
ตัวอย่างต่อไปนี้แสดงการสร้างรายงานรายได้รายเดือนของแต่ละผลิตภัณฑ์ โดยแปลงเดือนจากแถวเป็นคอลัมน์
-- pivot_monthly_revenue.sql
-- Monthly revenue pivot: one row per product, one column per month
SELECT
product_id,
product_name,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 1
THEN quantity * unit_price ELSE 0 END) AS jan_revenue,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 2
THEN quantity * unit_price ELSE 0 END) AS feb_revenue,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 3
THEN quantity * unit_price ELSE 0 END) AS mar_revenue,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 4
THEN quantity * unit_price ELSE 0 END) AS apr_revenue,
-- Repeat for remaining months
SUM(quantity * unit_price) AS total_revenue
FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE o.order_date >= '2026-01-01'
GROUP BY product_id, product_name
ORDER BY total_revenue DESC;อีกตัวอย่างหนึ่งที่พบบ่อยในงาน Data Analyst คือการวิเคราะห์พฤติกรรมผู้ใช้ตามประเภทอุปกรณ์ Query ด้านล่างแสดงการนับจำนวน Session ตามประเภทอุปกรณ์และคำนวณสัดส่วนของ Mobile
-- pivot_user_activity.sql
-- User activity: sessions by day of week and device type
SELECT
user_id,
COUNT(CASE WHEN device_type = 'mobile' THEN 1 END) AS mobile_sessions,
COUNT(CASE WHEN device_type = 'desktop' THEN 1 END) AS desktop_sessions,
COUNT(CASE WHEN device_type = 'tablet' THEN 1 END) AS tablet_sessions,
ROUND(
COUNT(CASE WHEN device_type = 'mobile' THEN 1 END) * 100.0 / COUNT(*),
1
) AS mobile_pct
FROM user_sessions
WHERE session_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY user_id
HAVING COUNT(*) >= 5 -- Only users with meaningful activity
ORDER BY mobile_pct DESC;Dynamic Pivot ด้วย CROSSTAB
สำหรับผู้ที่ใช้ PostgreSQL ฟังก์ชัน CROSSTAB จาก Extension tablefunc เป็นอีกทางเลือกหนึ่งในการสร้าง Pivot Table โดยเฉพาะเมื่อต้องการโครงสร้างที่ชัดเจนและอ่านง่ายกว่าการใช้ CASE หลายบรรทัด
-- crosstab_dynamic_pivot.sql
-- Enable the extension (once per database)
CREATE EXTENSION IF NOT EXISTS tablefunc;
-- Revenue by product and quarter using CROSSTAB
SELECT *
FROM crosstab(
$$
SELECT
product_name,
'Q' || EXTRACT(QUARTER FROM order_date)::TEXT AS quarter,
SUM(quantity * unit_price) AS revenue
FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE EXTRACT(YEAR FROM o.order_date) = 2026
GROUP BY product_name, quarter
ORDER BY product_name, quarter
$$,
$$ VALUES ('Q1'), ('Q2'), ('Q3'), ('Q4') $$
) AS pivot_table(
product_name TEXT,
q1_revenue NUMERIC,
q2_revenue NUMERIC,
q3_revenue NUMERIC,
q4_revenue NUMERIC
);ข้อควรทราบคือ CROSSTAB เป็นฟีเจอร์เฉพาะของ PostgreSQL ไม่สามารถใช้ใน MySQL หรือ SQL Server ได้โดยตรง ในการสัมภาษณ์ ควรระบุให้ชัดเจนว่ากำลังใช้ฟีเจอร์เฉพาะของ Database ใด เพราะแสดงให้เห็นถึงความเข้าใจในความแตกต่างระหว่าง Database Engine ต่าง ๆ
พร้อมที่จะพิชิตการสัมภาษณ์ Data Analytics แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
EXPLAIN Plan และ Cost Analysis
ความสามารถในการอ่านและวิเคราะห์ EXPLAIN Plan ถือเป็นทักษะที่แยกระหว่าง Data Analyst ทั่วไปกับ Data Analyst ขั้นสูงได้อย่างชัดเจน คำสั่ง EXPLAIN ANALYZE จะแสดงแผนการทำงานจริงของ Query รวมถึงเวลาที่ใช้ในแต่ละขั้นตอนและจำนวน Buffer ที่อ่าน
-- explain_analyze_example.sql
-- Analyze a slow query to identify bottlenecks
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT
c.customer_id,
c.name,
COUNT(o.order_id) AS order_count,
SUM(o.total_amount) AS lifetime_value
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2025-01-01'
GROUP BY c.customer_id, c.name
HAVING SUM(o.total_amount) > 1000
ORDER BY lifetime_value DESC;เมื่ออ่านผลลัพธ์ของ EXPLAIN ANALYZE มีจุดสำคัญที่ต้องสังเกตดังนี้
- Seq Scan vs Index Scan: Seq Scan บนตารางขนาดใหญ่มักเป็นสัญญาณของ Query ที่ช้า ควรตรวจสอบว่ามี Index ที่เหมาะสมหรือไม่
- Actual Time: เปรียบเทียบเวลาจริงกับ Estimated Cost เพื่อดูว่า Planner ประเมินได้แม่นยำหรือไม่
- Rows: จำนวนแถวจริงที่ประมวลผลเทียบกับที่ Planner คาดการณ์ ถ้าต่างกันมากอาจต้องอัปเดต Statistics
- Buffers: จำนวน Shared Hit (อ่านจาก Cache) เทียบกับ Shared Read (อ่านจาก Disk) ยิ่งมี Hit มากยิ่งดี
- Sort Method: ตรวจสอบว่าเป็น In-Memory Sort หรือ Disk Sort เพราะ Disk Sort ช้ากว่ามาก
Indexing Strategies สำหรับ Query ที่ใช้บ่อย
หลังจากระบุ Bottleneck จาก EXPLAIN Plan แล้ว ขั้นตอนถัดไปคือการสร้าง Index ที่เหมาะสม การออกแบบ Index ที่ดีสามารถเปลี่ยน Query ที่ใช้เวลาหลายวินาทีให้เหลือเพียงไม่กี่มิลลิวินาที
-- indexing_strategies.sql
-- Index on order_date: filters a small percentage of rows
CREATE INDEX idx_orders_date ON orders (order_date);
-- Composite index for queries filtering on both columns
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, order_date);
-- Covering index: includes columns needed in SELECT
-- Enables Index Only Scan (no table access)
CREATE INDEX idx_orders_covering
ON orders (customer_id, order_date)
INCLUDE (total_amount, status);สิ่งสำคัญที่ต้องเข้าใจเกี่ยวกับ Composite Index คือ ลำดับของคอลัมน์มีความสำคัญมาก Index (customer_id, order_date) จะถูกใช้เมื่อกรองด้วย customer_id อย่างเดียว หรือกรองด้วยทั้ง customer_id และ order_date แต่จะไม่ถูกใช้เมื่อกรองด้วย order_date อย่างเดียว ให้นึกถึงหลัก "Leftmost Prefix" เสมอ
Partial Index: Index เฉพาะข้อมูลที่ต้องการ
Partial Index คือ Index ที่สร้างเฉพาะบางส่วนของตาราง โดยใช้เงื่อนไข WHERE ในการกำหนดขอบเขต วิธีนี้ทำให้ Index มีขนาดเล็กลง ใช้พื้นที่น้อยลง และค้นหาได้เร็วขึ้น
-- partial_index.sql
-- Only index active orders (smaller, faster index)
CREATE INDEX idx_orders_active
ON orders (customer_id, order_date)
WHERE status = 'active';
-- Only index recent data for dashboard queries
CREATE INDEX idx_orders_recent
ON orders (order_date)
WHERE order_date >= '2026-01-01';Partial Index เหมาะอย่างยิ่งสำหรับกรณีที่ Query ส่วนใหญ่เข้าถึงเฉพาะข้อมูลบางส่วนเท่านั้น เช่น Dashboard ที่แสดงเฉพาะข้อมูลปัจจุบัน หรือระบบที่ต้องค้นหาเฉพาะรายการที่ยังดำเนินการอยู่
หนึ่งในข้อผิดพลาดที่พบบ่อยที่สุดคือการใช้ฟังก์ชันกับคอลัมน์ที่มี Index ซึ่งจะทำให้ Database ไม่สามารถใช้ Index ได้เลย ต้องทำ Seq Scan แทน ตรวจสอบ Query ทั้งหมดให้แน่ใจว่าไม่มีการห่อคอลัมน์ที่มี Index ด้วยฟังก์ชัน
Query Anti-Patterns ที่ต้องหลีกเลี่ยง
การใช้ฟังก์ชันกับคอลัมน์ที่มี Index
ข้อผิดพลาดนี้เกิดขึ้นบ่อยมากในการเขียน Query กรองข้อมูลตามวันที่ การใช้ EXTRACT หรือฟังก์ชันอื่น ๆ กับคอลัมน์ที่มี Index จะทำให้ Database ต้องคำนวณค่าของทุกแถวก่อนเปรียบเทียบ ส่งผลให้ Index ไม่ถูกใช้งาน
-- anti_patterns.sql
-- BAD: function on indexed column disables the index
SELECT * FROM orders
WHERE EXTRACT(YEAR FROM order_date) = 2026;
-- GOOD: range comparison uses the index
SELECT * FROM orders
WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01';กับดัก NOT IN กับ NULL
อีกหนึ่ง Anti-Pattern ที่อันตรายมากคือการใช้ NOT IN กับ Subquery ที่อาจมีค่า NULL เนื่องจากการเปรียบเทียบใด ๆ กับ NULL ใน SQL จะได้ผลลัพธ์เป็น UNKNOWN ไม่ใช่ TRUE หรือ FALSE ดังนั้น หากมีค่า NULL แม้แต่ค่าเดียวใน Subquery ผลลัพธ์ของ NOT IN จะเป็นชุดว่างเสมอ
-- not_in_null_trap.sql
-- BAD: returns empty if any customer_id is NULL in subquery
SELECT * FROM customers
WHERE customer_id NOT IN (
SELECT customer_id FROM orders
);
-- GOOD: NOT EXISTS handles NULLs correctly
SELECT * FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id
);ในการสัมภาษณ์ การรู้จัก Anti-Pattern เหล่านี้และอธิบายได้ว่าเหตุใดจึงเป็นปัญหาจะช่วยสร้างความน่าเชื่อถือได้อย่างมาก เพราะแสดงให้เห็นว่าผู้สมัครมีประสบการณ์จริงในการทำงานกับข้อมูล
การเตรียมตัวที่มีประสิทธิภาพที่สุดคือการฝึกเขียน Query จริงกับข้อมูลจริง ลองสร้างฐานข้อมูลทดสอบ สร้าง Index แล้วใช้ EXPLAIN ANALYZE ดูผลกระทบ การเห็นความแตกต่างของ Execution Time ด้วยตนเองจะช่วยให้เข้าใจได้ลึกซึ้งกว่าการอ่านทฤษฎีเพียงอย่างเดียว แนะนำให้เตรียมตัวในหัวข้อเหล่านี้ตามลำดับ: Subquery Patterns → Pivot Queries → EXPLAIN Plan → Indexing → Anti-Patterns
พร้อมที่จะพิชิตการสัมภาษณ์ Data Analytics แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
สรุป
หัวข้อ SQL ขั้นสูงที่กล่าวมาทั้งหมดล้วนเป็นทักษะสำคัญสำหรับการสัมภาษณ์ตำแหน่ง Data Analyst สามารถสรุปประเด็นหลักได้ดังนี้
- Correlated Subquery กับ Regular Subquery — ต้องเข้าใจความแตกต่างด้านประสิทธิภาพ และรู้วิธีเปลี่ยน Correlated Subquery เป็น CTE + JOIN เมื่อเหมาะสม
- EXISTS กับ IN — EXISTS เหมาะสำหรับตรวจสอบการมีอยู่ของข้อมูลมากกว่า IN โดยเฉพาะกับชุดข้อมูลขนาดใหญ่ และ NOT EXISTS จัดการ NULL ได้ถูกต้องกว่า NOT IN
- Pivot Query — เทคนิค Conditional Aggregation ด้วย CASE เป็นวิธีมาตรฐานที่ใช้ได้กับทุก Database ส่วน CROSSTAB เป็นทางเลือกเพิ่มเติมสำหรับ PostgreSQL
- EXPLAIN Plan — ทักษะการอ่าน Execution Plan จะช่วยระบุ Bottleneck และตัดสินใจเลือกวิธีเพิ่มประสิทธิภาพได้อย่างแม่นยำ
- Indexing — การออกแบบ Index ที่เหมาะสม ทั้ง Single Column, Composite, Covering และ Partial Index สามารถปรับปรุงประสิทธิภาพของ Query ได้อย่างมหาศาล
- Anti-Patterns — การหลีกเลี่ยงการใช้ฟังก์ชันกับคอลัมน์ที่มี Index และการระวังกับดัก NOT IN กับ NULL เป็นความรู้ที่แสดงถึงประสบการณ์จริง
การเตรียมตัวอย่างเป็นระบบในหัวข้อเหล่านี้จะช่วยเพิ่มความมั่นใจและโอกาสในการผ่านการสัมภาษณ์ตำแหน่ง Data Analyst ได้อย่างมีนัยสำคัญ
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

25 คำถามสัมภาษณ์งาน Data Analytics ที่พบบ่อยที่สุดในปี 2026
รวมคำถามสัมภาษณ์งาน Data Analytics ที่ถูกถามบ่อยที่สุดในปี 2026 ครอบคลุม SQL, Python, Power BI, สถิติ และคำถามเชิงพฤติกรรม พร้อมคำตอบโดยละเอียดและตัวอย่างโค้ด

Pandas 3.0 ในปี 2026: API ใหม่, Breaking Changes และคำถามสัมภาษณ์งาน
คู่มือฉบับสมบูรณ์เกี่ยวกับ Pandas 3.0 ครอบคลุม Copy-on-Write, PyArrow string backend, pd.col() expressions, breaking changes และคำถามสัมภาษณ์ data analytics

SQL สำหรับ Data Analyst: Window Functions, CTEs และ Advanced Queries ฉบับสมบูรณ์
คู่มือ SQL ขั้นสูงสำหรับ Data Analyst ครอบคลุม Window Functions, CTEs, Recursive CTEs และเทคนิค Gaps-and-Islands พร้อมตัวอย่างโค้ดที่ใช้ได้จริง