๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€ ๋ฉด์ ‘์„ ์œ„ํ•œ ๊ณ ๊ธ‰ SQL: ์„œ๋ธŒ์ฟผ๋ฆฌ, ํ”ผ๋ฒ—, ์ฟผ๋ฆฌ ์ตœ์ ํ™” ์™„๋ฒฝ ๊ฐ€์ด๋“œ (2026๋…„)

๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€ ๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” ๊ณ ๊ธ‰ SQL ์ฃผ์ œ๋ฅผ ์‹ฌ์ธต์ ์œผ๋กœ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ์ƒ๊ด€ ์„œ๋ธŒ์ฟผ๋ฆฌ, ์กฐ๊ฑด๋ถ€ ์ง‘๊ณ„๋ฅผ ํ™œ์šฉํ•œ ํ”ผ๋ฒ— ์ฟผ๋ฆฌ, EXPLAIN ๋ถ„์„, ์ธ๋ฑ์‹ฑ ์ „๋žต, ๊ทธ๋ฆฌ๊ณ  ํ”ํ•œ ์•ˆํ‹ฐํŒจํ„ด๊นŒ์ง€ ์‹ค์ „ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€ ๋ฉด์ ‘์„ ์œ„ํ•œ ๊ณ ๊ธ‰ SQL ์„œ๋ธŒ์ฟผ๋ฆฌ, ํ”ผ๋ฒ— ์ฟผ๋ฆฌ, ์ฟผ๋ฆฌ ์ตœ์ ํ™” ๊ฐ€์ด๋“œ

๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€ ๋ฉด์ ‘์—์„œ SQL์€ ๋‹จ์ˆœํ•œ SELECT ๋ฌธ์„ ๋„˜์–ด ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฟผ๋ฆฌ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๋Šฅ๋ ฅ์„ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. 2026๋…„ ํ˜„์žฌ ๊ธฐ์—…๋“ค์€ ์„œ๋ธŒ์ฟผ๋ฆฌ์˜ ์‹คํ–‰ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•˜๊ณ , ํ–‰ ๋ฐ์ดํ„ฐ๋ฅผ ์—ด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ”ผ๋ฒ— ๊ธฐ๋ฒ•์„ ๊ตฌ์‚ฌํ•˜๋ฉฐ, ์‹คํ–‰ ๊ณ„ํš์„ ๋ถ„์„ํ•˜์—ฌ ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ„์„๊ฐ€๋ฅผ ์ฐพ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€ ๋ฉด์ ‘์—์„œ ๊ฐ€์žฅ ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋“ฑ์žฅํ•˜๋Š” ๊ณ ๊ธ‰ SQL ์ฃผ์ œ๋“ค์„ ์‹ค์ „ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ฒด๊ณ„์ ์œผ๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋ฉด์ ‘๊ด€์ด ์‹ค์ œ๋กœ ํ‰๊ฐ€ํ•˜๋Š” ๊ฒƒ

๋ฉด์ ‘๊ด€์€ ๋‹จ์ˆœํžˆ ์ •๋‹ต ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋Šฅ๋ ฅ๋งŒ ๋ณด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์„œ๋ธŒ์ฟผ๋ฆฌ์™€ JOIN์˜ ์„ฑ๋Šฅ ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋Š”์ง€, ์™œ ํŠน์ • ์ ‘๊ทผ๋ฒ•์„ ์„ ํƒํ–ˆ๋Š”์ง€ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๊ทผ๊ฑฐ๋ฅผ ์ œ์‹œํ•  ์ˆ˜ ์žˆ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ์…‹์—์„œ ์ฟผ๋ฆฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ• ์ง€ ์˜ˆ์ธกํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ์ข…ํ•ฉ์ ์œผ๋กœ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

์ƒ๊ด€ ์„œ๋ธŒ์ฟผ๋ฆฌ์™€ ์ผ๋ฐ˜ ์„œ๋ธŒ์ฟผ๋ฆฌ์˜ ์ฐจ์ด

์„œ๋ธŒ์ฟผ๋ฆฌ๋Š” ๋‹ค๋ฅธ ์ฟผ๋ฆฌ ๋‚ด๋ถ€์— ์ค‘์ฒฉ๋œ SELECT ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ผ๋ฐ˜ ์„œ๋ธŒ์ฟผ๋ฆฌ(non-correlated subquery)๋Š” ์™ธ๋ถ€ ์ฟผ๋ฆฌ์™€ ๋…๋ฆฝ์ ์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ํ‰๊ท  ์ฃผ๋ฌธ ๊ธˆ์•ก๋ณด๋‹ค ํฐ ์ฃผ๋ฌธ์„ ์ฐพ๋Š” ๋‹ค์Œ ์˜ˆ์‹œ์—์„œ, ๋‚ด๋ถ€ SELECT๋Š” ์ „์ฒด ํ…Œ์ด๋ธ”์— ๋Œ€ํ•ด ๋‹จ ํ•œ ๋ฒˆ ์‹คํ–‰๋˜์–ด ์Šค์นผ๋ผ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

sql
-- 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)๋Š” ์™ธ๋ถ€ ์ฟผ๋ฆฌ์˜ ๊ฐ ํ–‰์— ๋Œ€ํ•ด ๋ฐ˜๋ณต ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์ฟผ๋ฆฌ๋Š” ๊ฐ ์ง์›์˜ ๋ถ€์„œ ํ‰๊ท  ๊ธ‰์—ฌ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋น„๊ตํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์™ธ๋ถ€ ์ฟผ๋ฆฌ๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š” ํ–‰๋งˆ๋‹ค ๋‚ด๋ถ€ ์ฟผ๋ฆฌ๊ฐ€ ์žฌํ‰๊ฐ€๋ฉ๋‹ˆ๋‹ค.

sql
-- 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
);

์ƒ๊ด€ ์„œ๋ธŒ์ฟผ๋ฆฌ๋Š” ์ง๊ด€์ ์ด์ง€๋งŒ, ํ–‰ ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ•˜๋ฉด O(N*M) ๋ณต์žก๋„๋กœ ์„ฑ๋Šฅ์ด ๊ธ‰๊ฒฉํžˆ ์ €ํ•˜๋ฉ๋‹ˆ๋‹ค. ๋ฉด์ ‘์—์„œ๋Š” ์ด๋ฅผ CTE(Common Table Expression)๋‚˜ ์œˆ๋„์šฐ ํ•จ์ˆ˜๋กœ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ•˜๋ฉด ๋†’์€ ์ ์ˆ˜๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‹จ์ผ ํŒจ์Šค๋กœ ์‚ฐ์ถœํ•˜๋Š” CTE ํ™œ์šฉ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

sql
-- 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;

CTE๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ถ€์„œ๋ณ„ ํ‰๊ท ์„ ํ•œ ๋ฒˆ๋งŒ ๊ณ„์‚ฐํ•˜๊ณ  JOIN์œผ๋กœ ์—ฐ๊ฒฐํ•˜๋ฏ€๋กœ, ํ…Œ์ด๋ธ” ์Šค์บ” ํšŸ์ˆ˜๊ฐ€ ํฌ๊ฒŒ ์ค„์–ด๋“ญ๋‹ˆ๋‹ค. ๋ฉด์ ‘ ์ƒํ™ฉ์—์„œ ์ƒ๊ด€ ์„œ๋ธŒ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•œ ํ›„ ์ฆ‰์‹œ CTE ๊ธฐ๋ฐ˜ ์ตœ์ ํ™” ๋ฒ„์ „์„ ์ œ์‹œํ•˜๋ฉด, ์„ฑ๋Šฅ ์˜์‹(performance awareness)์„ ํšจ๊ณผ์ ์œผ๋กœ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€ ๊ธฐ์ˆ  ์Šคํฌ๋ฆฌ๋‹์˜ ์„œ๋ธŒ์ฟผ๋ฆฌ ํŒจํ„ด

๋ฉด์ ‘์—์„œ ์ž์ฃผ ๋“ฑ์žฅํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ํŒจํ„ด์€ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ์ž…๋‹ˆ๋‹ค. 2026๋…„์— ์ฃผ๋ฌธ์„ ํ•œ ๊ณ ๊ฐ์„ ์ฐพ๋Š” ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ๋ฒ•์„ ๋น„๊ตํ•˜๋ฉด, EXISTS๋Š” ์ฒซ ๋ฒˆ์งธ ๋งค์นญ ํ–‰์„ ๋ฐœ๊ฒฌํ•˜๋Š” ์ฆ‰์‹œ ํƒ์ƒ‰์„ ์ค‘๋‹จํ•˜๋Š” ๋ฐ˜๋ฉด, IN์€ ์„œ๋ธŒ์ฟผ๋ฆฌ์˜ ์ „์ฒด ๊ฒฐ๊ณผ ์ง‘ํ•ฉ์„ ๋จผ์ € ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

sql
-- 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'
);

๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ์…‹์—์„œ๋Š” EXISTS๊ฐ€ IN๋ณด๋‹ค ํšจ์œจ์ ์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ semi-join์œผ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์กฐ๊ธฐ ์ข…๋ฃŒ(early termination)๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋ฉด์ ‘์—์„œ ์ด ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ์ฟผ๋ฆฌ ์—”์ง„ ๋‚ด๋ถ€ ๋™์ž‘์— ๋Œ€ํ•œ ์ดํ•ด๋„๋ฅผ ์ฆ๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

SELECT ์ ˆ ๋‚ด์˜ ์Šค์นผ๋ผ ์„œ๋ธŒ์ฟผ๋ฆฌ๋„ ๋นˆ์ถœ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ๊ฐ ์ œํ’ˆ๊ณผ ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ์ด ๋งค์ถœ์„ ํ•จ๊ป˜ ์กฐํšŒํ•˜๋Š” ๋‹ค์Œ ์˜ˆ์‹œ๋Š”, ๋ถ„์„ ๋ฆฌํฌํŠธ ์ž‘์„ฑ ์‹œ ์ž์ฃผ ๋งˆ์ฃผ์น˜๋Š” ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.

sql
-- 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;

์ด ์ฟผ๋ฆฌ ์—ญ์‹œ ์ƒ๊ด€ ์„œ๋ธŒ์ฟผ๋ฆฌ์ด๋ฏ€๋กœ ์นดํ…Œ๊ณ ๋ฆฌ ์ˆ˜๊ฐ€ ๋งŽ์•„์ง€๋ฉด ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค๋ฌด์—์„œ๋Š” CTE๋‚˜ ์œˆ๋„์šฐ ํ•จ์ˆ˜(SUM OVER PARTITION BY)๋กœ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.

Data Analytics ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

์กฐ๊ฑด๋ถ€ ์ง‘๊ณ„๋ฅผ ํ™œ์šฉํ•œ ํ”ผ๋ฒ— ์ฟผ๋ฆฌ

ํ–‰ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ๋ฅผ ์—ด ๊ธฐ๋ฐ˜์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํ”ผ๋ฒ—(Pivot)์€ ๋ฐ์ดํ„ฐ ๋ถ„์„ ๋ฉด์ ‘์˜ ํ•ต์‹ฌ ์ฃผ์ œ์ž…๋‹ˆ๋‹ค. ํ‘œ์ค€ SQL์—์„œ๋Š” CASE WHEN๊ณผ ์ง‘๊ณ„ ํ•จ์ˆ˜๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ํ”ผ๋ฒ—์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์‹œ๋Š” ์ œํ’ˆ๋ณ„ ์›”๊ฐ„ ๋งค์ถœ์„ ํ•˜๋‚˜์˜ ํ–‰์— ์›”๋ณ„ ์—ด๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

sql
-- 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;

์ด ํŒจํ„ด์€ DBMS์— ๊ด€๊ณ„์—†์ด ๋™์ž‘ํ•˜๋ฏ€๋กœ ๋ฉด์ ‘์—์„œ ๊ฐ€์žฅ ์•ˆ์ „ํ•œ ์ ‘๊ทผ๋ฒ•์ž…๋‹ˆ๋‹ค. ์กฐ๊ฑด๋ถ€ ์ง‘๊ณ„๋Š” ๋งค์ถœ ๋ถ„์„ ์™ธ์—๋„ ์‚ฌ์šฉ์ž ํ–‰๋™ ๋ถ„์„์— ๊ด‘๋ฒ”์œ„ํ•˜๊ฒŒ ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๋””๋ฐ”์ด์Šค ์œ ํ˜•๋ณ„ ์„ธ์…˜ ๋ถ„ํฌ๋ฅผ ๋ถ„์„ํ•˜๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

sql
-- 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;

HAVING ์ ˆ์„ ํ†ตํ•ด ์˜๋ฏธ ์žˆ๋Š” ํ™œ๋™๋Ÿ‰์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์€ ๋…ธ์ด์ฆˆ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ์‹ค๋ฌด์  ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋ฉด์ ‘์—์„œ ์ด๋Ÿฐ ์„ธ๋ถ€ ์‚ฌํ•ญ์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํฌํ•จํ•˜๋ฉด ์‹ค๋ฌด ๊ฒฝํ—˜์„ ๊ฐ„์ ‘์ ์œผ๋กœ ์–ดํ•„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PostgreSQL์˜ CROSSTAB์„ ํ™œ์šฉํ•œ ๋™์  ํ”ผ๋ฒ—

PostgreSQL ํ™˜๊ฒฝ์—์„œ๋Š” tablefunc ํ™•์žฅ์˜ CROSSTAB ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋” ์ฒด๊ณ„์ ์ธ ํ”ผ๋ฒ—์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ถ„๊ธฐ๋ณ„ ์ œํ’ˆ ๋งค์ถœ์„ ํ”ผ๋ฒ—ํ•˜๋Š” ๋‹ค์Œ ์˜ˆ์‹œ๋Š” PostgreSQL ํŠนํ™” ๋ฉด์ ‘์—์„œ ์ฐจ๋ณ„ํ™” ํฌ์ธํŠธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

sql
-- 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์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋Š” ํ–‰ ์‹๋ณ„์ž, ์นดํ…Œ๊ณ ๋ฆฌ, ๊ฐ’ ์ˆœ์„œ๋กœ ์ •๋ ฌ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ฟผ๋ฆฌ์ด๋ฉฐ, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” ํ”ผ๋ฒ— ์—ด์ด ๋  ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ ์Šคํ‚ค๋งˆ๋Š” AS ์ ˆ์—์„œ ๋ช…์‹œ์ ์œผ๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. CROSSTAB์€ ์กฐ๊ฑด๋ถ€ ์ง‘๊ณ„๋ณด๋‹ค ๊ฐ€๋…์„ฑ์ด ๋†’์ง€๋งŒ, PostgreSQL ์ „์šฉ์ด๋ผ๋Š” ์ ์—์„œ ๋ฉด์ ‘ ์‹œ DBMS ๋ฒ”์šฉ์„ฑ์„ ํ•จ๊ป˜ ์–ธ๊ธ‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

SQL ์ฟผ๋ฆฌ ์ตœ์ ํ™”: EXPLAIN ๊ณ„ํš๊ณผ ๋น„์šฉ ๋ถ„์„

์ฟผ๋ฆฌ ์ตœ์ ํ™” ์—ญ๋Ÿ‰์€ ์‹œ๋‹ˆ์–ด ๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€ ๋ฉด์ ‘์—์„œ ํ•„์ˆ˜์ ์œผ๋กœ ๊ฒ€์ฆํ•˜๋Š” ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค. EXPLAIN ANALYZE๋Š” ์ฟผ๋ฆฌ ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ์ƒ์„ฑํ•œ ์‹คํ–‰ ๊ณ„ํš๊ณผ ์‹ค์ œ ์‹คํ–‰ ํ†ต๊ณ„๋ฅผ ํ•จ๊ป˜ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

sql
-- 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 ์ถœ๋ ฅ์—์„œ ํ™•์ธํ•ด์•ผ ํ•  ํ•ต์‹ฌ ์ง€ํ‘œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. Seq Scan์€ ์ธ๋ฑ์Šค ์—†์ด ์ „์ฒด ํ…Œ์ด๋ธ”์„ ์ˆœ์ฐจ ํƒ์ƒ‰ํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ๋Œ€ํ˜• ํ…Œ์ด๋ธ”์—์„œ ๋‚˜ํƒ€๋‚˜๋ฉด ์ธ๋ฑ์Šค ์ถ”๊ฐ€๋ฅผ ๊ฒ€ํ† ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Nested Loop์€ ์†Œ๊ทœ๋ชจ ๊ฒฐ๊ณผ์…‹์—๋Š” ์ ํ•ฉํ•˜์ง€๋งŒ, ์–‘์ชฝ ํ…Œ์ด๋ธ”์ด ํฌ๋ฉด Hash Join์ด๋‚˜ Merge Join์ด ๋” ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. BUFFERS ์˜ต์…˜์€ ๋””์Šคํฌ I/O์™€ ์บ์‹œ ํžˆํŠธ ๋น„์œจ์„ ๋ณด์—ฌ์ฃผ์–ด ๋ฉ”๋ชจ๋ฆฌ ํŠœ๋‹ ๋ฐฉํ–ฅ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.

๋ฉด์ ‘์—์„œ EXPLAIN ์ถœ๋ ฅ์„ ํ•ด์„ํ•˜๋Š” ๋Šฅ๋ ฅ์„ ๋ณด์—ฌ์ฃผ๋ฉด, ๋‹จ์ˆœํžˆ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ˆ˜์ค€์„ ๋„˜์–ด ์šด์˜ ํ™˜๊ฒฝ์—์„œ์˜ ์„ฑ๋Šฅ ๊ด€๋ฆฌ ์—ญ๋Ÿ‰๊นŒ์ง€ ์ž…์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉด์ ‘ ์ˆ˜์ค€์˜ SQL ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ์ธ๋ฑ์‹ฑ ์ „๋žต

์ ์ ˆํ•œ ์ธ๋ฑ์Šค๋Š” ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์„ ๊ทน์ ์œผ๋กœ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค. ๋ฉด์ ‘์—์„œ๋Š” ์–ด๋–ค ์—ด์— ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š”์ง€, ๋ณตํ•ฉ ์ธ๋ฑ์Šค์˜ ์—ด ์ˆœ์„œ๊ฐ€ ์™œ ์ค‘์š”ํ•œ์ง€, ์ปค๋ฒ„๋ง ์ธ๋ฑ์Šค๊ฐ€ ๋ฌด์—‡์ธ์ง€๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

sql
-- 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);

๋ณตํ•ฉ ์ธ๋ฑ์Šค์—์„œ ์—ด ์ˆœ์„œ๋Š” ์„ ํƒ๋„(selectivity)์™€ ์ฟผ๋ฆฌ ํŒจํ„ด์— ๋”ฐ๋ผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. WHERE ์ ˆ์—์„œ ๋“ฑํ˜ธ ์กฐ๊ฑด์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์—ด์„ ์•ž์—, ๋ฒ”์œ„ ์กฐ๊ฑด์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์—ด์„ ๋’ค์— ๋ฐฐ์น˜ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ธ ์›์น™์ž…๋‹ˆ๋‹ค. ์ปค๋ฒ„๋ง ์ธ๋ฑ์Šค์˜ INCLUDE ์ ˆ์€ ์ธ๋ฑ์Šค์— ์ถ”๊ฐ€ ์—ด์„ ํฌํ•จ์‹œ์ผœ ํ…Œ์ด๋ธ” ํž™ ์ ‘๊ทผ ์—†์ด Index Only Scan์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

๋ถ€๋ถ„ ์ธ๋ฑ์Šค(Partial Index)๋Š” ํŠน์ • ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ํ–‰๋งŒ ์ธ๋ฑ์‹ฑํ•˜์—ฌ ์ธ๋ฑ์Šค ํฌ๊ธฐ๋ฅผ ์ค„์ด๊ณ  ์“ฐ๊ธฐ ์„ฑ๋Šฅ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ตœ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค.

sql
-- 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';

๋Œ€์‹œ๋ณด๋“œ ์ฟผ๋ฆฌ๊ฐ€ ํ•ญ์ƒ ์ตœ๊ทผ ๋ฐ์ดํ„ฐ๋งŒ ์กฐํšŒํ•œ๋‹ค๋ฉด, ๋ถ€๋ถ„ ์ธ๋ฑ์Šค๋ฅผ ํ†ตํ•ด ์ „์ฒด ํ…Œ์ด๋ธ” ๋Œ€๋น„ ํ›จ์”ฌ ์ž‘์€ ์ธ๋ฑ์Šค๋กœ ๋™์ผํ•œ ์„ฑ๋Šฅ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ธ๋ฑ์Šค ์•ˆํ‹ฐํŒจํ„ด์— ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

์ธ๋ฑ์Šค๋ฅผ ๋ฌด์กฐ๊ฑด ๋งŽ์ด ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ํ•ด๊ฒฐ์ฑ…์ด ์•„๋‹™๋‹ˆ๋‹ค. ๊ฐ ์ธ๋ฑ์Šค๋Š” INSERT, UPDATE, DELETE ์—ฐ์‚ฐ๋งˆ๋‹ค ์œ ์ง€๋ณด์ˆ˜ ๋น„์šฉ์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ธ๋ฑ์Šค๋Š” ์“ฐ๊ธฐ ์„ฑ๋Šฅ๋งŒ ์ €ํ•˜์‹œํ‚ค๋ฏ€๋กœ, pg_stat_user_indexes ๋ทฐ๋ฅผ ํ†ตํ•ด ์ธ๋ฑ์Šค ์‚ฌ์šฉ ํ˜„ํ™ฉ์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ”ํ•œ ์ฟผ๋ฆฌ ์•ˆํ‹ฐํŒจํ„ด

๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” ์•ˆํ‹ฐํŒจํ„ด ์ค‘ ํ•˜๋‚˜๋Š” ์ธ๋ฑ์Šค ์—ด์— ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. WHERE ์ ˆ์—์„œ ์—ด์— ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•˜๋ฉด ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ํ•ด๋‹น ์—ด์˜ ์ธ๋ฑ์Šค๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

sql
-- 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';

EXTRACT ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•˜๋ฉด order_date ์—ด์˜ ๋ชจ๋“  ๊ฐ’์„ ๋ณ€ํ™˜ํ•œ ํ›„ ๋น„๊ตํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ธ๋ฑ์Šค ์Šค์บ”์ด ๋ถˆ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค. ๋ฒ”์œ„ ๋น„๊ต๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด B-tree ์ธ๋ฑ์Šค์˜ ์ •๋ ฌ ์ˆœ์„œ๋ฅผ ๊ทธ๋Œ€๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ ๋‹ค๋ฅธ ์œ„ํ—˜ํ•œ ์•ˆํ‹ฐํŒจํ„ด์€ NOT IN๊ณผ NULL ๊ฐ’์˜ ์กฐํ•ฉ์ž…๋‹ˆ๋‹ค. ์„œ๋ธŒ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์— NULL์ด ํ•˜๋‚˜๋ผ๋„ ํฌํ•จ๋˜๋ฉด NOT IN ์กฐ๊ฑด์€ ํ•ญ์ƒ ๋นˆ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. SQL์˜ 3๊ฐ’ ๋…ผ๋ฆฌ(TRUE, FALSE, UNKNOWN) ๋•Œ๋ฌธ์— NULL๊ณผ์˜ ๋น„๊ต๋Š” UNKNOWN์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , NOT IN์€ ๋ชจ๋“  ๋น„๊ต๊ฐ€ TRUE์ผ ๋•Œ๋งŒ ํ–‰์„ ํฌํ•จ์‹œํ‚ค๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

sql
-- 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
);

NOT EXISTS๋Š” NULL ๊ฐ’์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š์œผ๋ฉฐ, ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ anti-join์œผ๋กœ ํšจ์œจ์ ์œผ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฉด์ ‘์—์„œ NOT IN์˜ NULL ํ•จ์ •์„ ์ž๋ฐœ์ ์œผ๋กœ ์–ธ๊ธ‰ํ•˜๋ฉด SQL์˜ ๊ทผ๋ณธ์ ์ธ ๋™์ž‘ ์›๋ฆฌ๋ฅผ ๊นŠ์ด ์ดํ•ดํ•˜๊ณ  ์žˆ์Œ์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ „ ์—ฐ์Šต์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค

๊ณ ๊ธ‰ SQL ๊ฐœ๋…์„ ํ•™์Šตํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ๋ฐ์ดํ„ฐ์…‹์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , EXPLAIN ์ถœ๋ ฅ์„ ๋ถ„์„ํ•˜๋ฉฐ, ๋‹ค์–‘ํ•œ ์ธ๋ฑ์‹ฑ ์ „๋žต์„ ์‹คํ—˜ํ•ด ๋ณด๋Š” ๊ณผ์ •์ด ๋ฉด์ ‘ ํ•ฉ๊ฒฉ์˜ ์—ด์‡ ์ž…๋‹ˆ๋‹ค. ๋ฐ˜๋ณต์ ์ธ ์‹ค์Šต์„ ํ†ตํ•ด ํŒจํ„ด์„ ์ฒดํ™”ํ•˜๋ฉด ๋ฉด์ ‘ ์ƒํ™ฉ์—์„œ๋„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ตœ์ ์˜ ์ ‘๊ทผ๋ฒ•์„ ์ œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Data Analytics ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

๊ฒฐ๋ก 

๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€ ๋ฉด์ ‘์—์„œ ๊ณ ๊ธ‰ SQL ์—ญ๋Ÿ‰์€ ๊ธฐ์ˆ ์  ์ฐจ๋ณ„ํ™”์˜ ํ•ต์‹ฌ ์š”์†Œ์ž…๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ ๋‹ค๋ฃฌ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ์ƒ๊ด€ ์„œ๋ธŒ์ฟผ๋ฆฌ vs ์ผ๋ฐ˜ ์„œ๋ธŒ์ฟผ๋ฆฌ: ์‹คํ–‰ ๋ฐฉ์‹์˜ ์ฐจ์ด๋ฅผ ์ดํ•ดํ•˜๊ณ , ์ƒ๊ด€ ์„œ๋ธŒ์ฟผ๋ฆฌ์˜ ์„ฑ๋Šฅ ํ•œ๊ณ„๋ฅผ CTE๋‚˜ ์œˆ๋„์šฐ ํ•จ์ˆ˜๋กœ ๊ทน๋ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • EXISTS vs IN: ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ์…‹์—์„œ EXISTS๊ฐ€ ์„ ํ˜ธ๋˜๋Š” ์ด์œ ์™€ semi-join ์ตœ์ ํ™” ์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ์กฐ๊ฑด๋ถ€ ์ง‘๊ณ„ ํ”ผ๋ฒ—: CASE WHEN + ์ง‘๊ณ„ ํ•จ์ˆ˜ ํŒจํ„ด์€ DBMS์— ๊ด€๊ณ„์—†์ด ๋™์ž‘ํ•˜๋Š” ๋ฒ”์šฉ ํ”ผ๋ฒ— ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค
  • CROSSTAB: PostgreSQL ํ™˜๊ฒฝ์—์„œ ๋” ์ฒด๊ณ„์ ์ธ ํ”ผ๋ฒ—์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ, DBMS ํŠนํ™” ์ง€์‹์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • EXPLAIN ANALYZE: ์‹คํ–‰ ๊ณ„ํš์„ ์ฝ๊ณ  Seq Scan, Nested Loop, ๋ฒ„ํผ ์‚ฌ์šฉ๋Ÿ‰ ๋“ฑ ํ•ต์‹ฌ ์ง€ํ‘œ๋ฅผ ํ•ด์„ํ•˜๋Š” ๋Šฅ๋ ฅ์€ ์‹œ๋‹ˆ์–ด ๋ ˆ๋ฒจ์˜ ํ•„์ˆ˜ ์—ญ๋Ÿ‰์ž…๋‹ˆ๋‹ค
  • ์ธ๋ฑ์‹ฑ ์ „๋žต: ๋ณตํ•ฉ ์ธ๋ฑ์Šค์˜ ์—ด ์ˆœ์„œ, ์ปค๋ฒ„๋ง ์ธ๋ฑ์Šค, ๋ถ€๋ถ„ ์ธ๋ฑ์Šค์˜ ํ™œ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌ์ฒด์ ์œผ๋กœ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • ์•ˆํ‹ฐํŒจํ„ด ํšŒํ”ผ: ์ธ๋ฑ์Šค ์—ด์— ํ•จ์ˆ˜ ์ ์šฉ ๊ธˆ์ง€, NOT IN์˜ NULL ํ•จ์ • ๋“ฑ ์‹ค๋ฌด์—์„œ ํ”ํžˆ ๋ฐœ์ƒํ•˜๋Š” ํ•จ์ •์„ ์ธ์‹ํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅธ ๋Œ€์•ˆ์„ ์ œ์‹œํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

์ด๋Ÿฌํ•œ ์ฃผ์ œ๋“ค์„ ๊นŠ์ด ์žˆ๊ฒŒ ์ค€๋น„ํ•˜๋ฉด, ๋ฉด์ ‘์—์„œ ๋‹จ์ˆœํ•œ ์ฟผ๋ฆฌ ์ž‘์„ฑ์ž๊ฐ€ ์•„๋‹Œ ๋ฐ์ดํ„ฐ ์—”์ง€๋‹ˆ์–ด๋ง ๊ฐ๊ฐ์„ ๊ฐ–์ถ˜ ๋ถ„์„๊ฐ€๋กœ ํ‰๊ฐ€๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

ํƒœ๊ทธ

#SQL
#Data Analytics
#Interview
#PostgreSQL
#Query Optimization

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

dbt data build tool for data analysts modeling testing

2026๋…„ ๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€๋ฅผ ์œ„ํ•œ dbt ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ๋ชจ๋ธ๋ง, ํ…Œ์ŠคํŠธ, ๋ฉด์ ‘ ์งˆ๋ฌธ

dbt ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ, ๋จธํ‹ฐ๋ฆฌ์–ผ๋ผ์ด์ œ์ด์…˜ ์ „๋žต, ๋ฐ์ดํ„ฐ ํ’ˆ์งˆ ํ…Œ์ŠคํŠธ, Jinja ๋งคํฌ๋กœ, ๊ทธ๋ฆฌ๊ณ  ์‹ค๋ฌด ๋ฉด์ ‘์—์„œ ์ž์ฃผ ๋“ฑ์žฅํ•˜๋Š” ์งˆ๋ฌธ๊นŒ์ง€ ํฌ๊ด„์ ์œผ๋กœ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€๋ฅผ ์œ„ํ•œ SQL: ์œˆ๋„์šฐ ํ•จ์ˆ˜, CTE, ๊ณ ๊ธ‰ ์ฟผ๋ฆฌ

๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€๋ฅผ ์œ„ํ•œ SQL: ์œˆ๋„์šฐ ํ•จ์ˆ˜, CTE, ๊ณ ๊ธ‰ ์ฟผ๋ฆฌ ๊ธฐ๋ฒ•

SQL ์œˆ๋„์šฐ ํ•จ์ˆ˜, CTE(๊ณตํ†ต ํ…Œ์ด๋ธ” ์‹), ๊ณ ๊ธ‰ ๋ถ„์„ ์ฟผ๋ฆฌ๋ฅผ ์‹ค์šฉ์ ์ธ ์ฝ”๋“œ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ๋ถ„์„๊ฐ€ ๋ฉด์ ‘ ์ค€๋น„์™€ ์‹ค๋ฌด์— ํ•„์ˆ˜์ ์ธ ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ ์• ๋„๋ฆฌํ‹ฑ์Šค ๋ฉด์ ‘ ์งˆ๋ฌธ 2026

2026๋…„ ๋ฐ์ดํ„ฐ ์• ๋„๋ฆฌํ‹ฑ์Šค ๋ฉด์ ‘ ์งˆ๋ฌธ TOP 25

2026๋…„ ๋ฐ์ดํ„ฐ ์• ๋„๋ฆฌํ‹ฑ์Šค ๋ฉด์ ‘ ๋Œ€๋น„ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. SQL, Python, Power BI, ํ†ต๊ณ„, ํ–‰๋™ ๋ฉด์ ‘์—์„œ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” 25๊ฐœ ์งˆ๋ฌธ์„ ์ฝ”๋“œ ์˜ˆ์‹œ์™€ ํ•จ๊ป˜ ์ƒ์„ธํžˆ ํ•ด์„คํ•ฉ๋‹ˆ๋‹ค.