์ŠคํŒŒ๋ฅดํƒ€ ๋‚ด์ผ๋ฐฐ์›€์บ ํ”„(25.12.01~)

์ŠคํŒŒ๋ฅดํƒ€ ๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_๋ณธ์บ ํ”„_data11๊ธฐ ๊น€์„ ์˜_TIL_Day 5

0๏ธโƒฃ 2025. 12. 29. 20:21

<์˜ค๋Š˜์˜ ํ•™์Šต>

  • 10์‹œ SQL ๋ผ์ด๋ธŒ ์„ธ์…˜ (Union[All] / Join) + ๋ฐ์ผ๋ฆฌ ํ€˜์ŠคํŠธ

<ํ•™์Šต๋‚ด์šฉ ์ •๋ฆฌ>

# SQL ๋ผ์ด๋ธŒ ์„ธ์…˜

1. Union / Union All

: SELECT ๊ฒฐ๊ณผ๋ฅผ ์„ธ๋กœ๋กœ ๋ถ™์—ฌ์„œ ํ•˜๋‚˜๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

ex. 1์›” ์‹ ์ฒญ์ž ๋ชฉ๋ก์ด ์žˆ๊ณ , 12์›” ์‹ ์ฒญ์ž ๋ชฉ๋ก์ด ๋”ฐ๋กœ ์žˆ์„ ๋•Œ

      ์›น ์œ ์ž… ๋กœ๊ทธ์™€ ์•ฑ ์œ ์ž… ๋กœ๊ทธ๊ฐ€ ๋”ฐ๋กœ ์žˆ๊ณ  “์ „์ฒด ์œ ์ž…”์„ ๋ณด๊ณ  ์‹ถ์„ ๋•Œ

      “ํ•™์ƒ ๋ชฉ๋ก”๊ณผ “๊ฐ•์‚ฌ ๋ชฉ๋ก”์„ ํ•ฉ์ณ์„œ “์ „์ฒด ์‚ฌ๋žŒ ๋ชฉ๋ก”์„ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ

  1. ๊ฐ SELECT๋Š” ๊ฐ™์€ ๊ฐœ์ˆ˜์˜ ์ปฌ๋Ÿผ์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•จ (์ปฌ๋Ÿผ๋ช…์ด ๊ฐ™์ง€ ์•Š์•„๋„ ๋จ.)
  2. ๊ฐ™์€ ์œ„์น˜์˜ ์ปฌ๋Ÿผ๋ผ๋ฆฌ ๋ฐ์ดํ„ฐ ํƒ€์ž…์ด ํ˜ธํ™˜๋˜์–ด์•ผ ํ•จ
  3. ๊ฒฐ๊ณผ ์ปฌ๋Ÿผ๋ช…์€ ์ฒซ ๋ฒˆ์งธ SELECT์˜ ์ปฌ๋Ÿผ๋ช…/๋ณ„์นญ์„ ๋”ฐ๋ผ๊ฐ
  • Union :
    - ์ค‘๋ณต์ œ๊ฑฐ ๋œ ์ƒํƒœ๋กœ, ๊ฒฐ๊ณผ๊ฐ€ ์ˆ˜์ง์œผ๋กœ ํ•ฉ์ณ์ง
    - ํ–‰์ด ๋Š˜์–ด๋‚จ
  • Union All
    - ์ค‘๋ณต์œ ์ง€๋œ ์ƒํƒœ, ๊ฒฐ๊ณผ๊ฐ€ ์ˆ˜์ง์œผ๋กœ ํ•ฉ์ณ์ง
SELECT col1, col2
FROM table_a
UNION [ALL]
SELECT col1, col2
FROM table_b;
SELECT student_id, student_name, 'SEOUL' AS tag
FROM basic.students
WHERE region='SEOUL'
--group by๋Š” select์•ˆ์— ์‚ฌ์šฉ ๊ฐ€๋Šฅ(*๋‹จ, union ๋’ค์˜ select์—๋„ ๋™์ผํ•œ group์ด ์ง€์ •๋˜์–ด์•ผ ํ•จ.)
UNION
SELECT student_id, student_name, 'new' AS tag
FROM basic.students
WHERE segment='new';
--order by๋Š” ๋งจ ๋งˆ์ง€๋ง‰์— (๋‘ ํ…Œ์ด๋ธ”์ด ํ•ฉ์ณ์ง„ ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ์ •๋ ฌ)

2. JOIN ๐Ÿ”ด

โญ๏ธ Join์„ ์œ„ํ•ด์„  ๊ฐ Table์— ๋Œ€ํ•œ ์ดํ•ด๊ฐ€ ํ•„์ˆ˜!
โญ๏ธ PK, FK๋ฅผ ํŒŒ์•…ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.

โญ๏ธ ์—ฌ๋Ÿฌ ๊ฐœ์˜ Table์„ ๋ฌถ์–ด์„œ ์–ด๋–ค ๋ชฉ์ ์˜ ๋ฐ์ดํ„ฐ ์ธ์‚ฌ์ดํŠธ๋ฅผ ์–ป์„ ๊ฒƒ์ธ์ง€ ๋ช…ํ™•ํžˆ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.

๐Ÿฏ https://sql-joins.leopard.in.ua/?utm_source=chatgpt.com

(1) KEY

  • PK(Primary Key, ๊ธฐ๋ณธํ‚ค): ํ•œ ํ…Œ์ด๋ธ”์—์„œ ๊ฐ ํ–‰์„ ์œ ์ผํ•˜๊ฒŒ ์‹๋ณ„ํ•˜๋Š” ์ปฌ๋Ÿผ
    -ํŠน์ง•: ์ค‘๋ณต๋˜๋ฉด ์•ˆ ๋จ, NULL์ด๋ฉด ์•ˆ ๋จ
  • FK(Foreign Key, ์™ธ๋ž˜ํ‚ค): ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์˜ PK๋ฅผ ์ฐธ์กฐํ•˜๋Š” ์ปฌ๋Ÿผ

(2) ๋‹ค์ค‘ Join

: (A์™€ B๋ฅผ ๋จผ์ € ๋ถ™์ธ ๊ฒฐ๊ณผ) ์— C๋ฅผ ๋˜ ๋ถ™์ด๋Š” ๊ฒƒ (A JOIN B JOIN C)

โญ๏ธ join์„ ํ•œ๋ฒˆ ํ•  ๋•Œ๋งˆ๋‹ค row ์ˆ˜๊ฐ€ ๋‚ด๊ฐ€ ์„ค์ •ํ•œ ๋‹จ์œ„(๋ ˆ๋ฒจ, ๊ธฐ์ค€ key)์— ๋งž๊ฒŒ ์œ ์ง€๋˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธ!

  • ์™œ row ์ˆ˜๊ฐ€ ๋ณ€ํ–ˆ์ง€? (1:N์ด๋ฉด ๋Š˜์–ด๋‚˜๋Š” ๊ฒŒ ์ •์ƒ)
  • ๋‚ด๊ฐ€ ๋ณด๊ณ  ์‹ถ์€ ๋‹จ์œ„(๋ ˆ๋ฒจ)๊ฐ€ ๋ญ์˜€์ง€? (๊ฐ•์ขŒ 1ํ–‰? ์‹ ์ฒญ 1ํ–‰? ๊ฒฐ์ œ 1ํ–‰?)
--courses + enrollments
SELECT
  c.course_id,
  c.course_name,
  e.enrollment_id
FROM basic.courses c
LEFT JOIN basic.enrollments e
ON c.course_id = e.course_id
ORDER BY c.course_id, e.enrollment_id;
--enrollments + payments
SELECT
  e.enrollment_id,
  p.payment_id,
  p.payment_status,
  p.amount
FROM basic.enrollments e
LEFT JOIN basic.payments p
ON e.enrollment_id = p.enrollment_id
ORDER BY e.enrollment_id, p.paid_at;
--courses + enrollments + payments
--๋’ค์— ๋ถ™์ด๋Š” ์ฟผ๋ฆฌ๋ฌธ์—์„œ 'join ~ on ~' ๋ถ€๋ถ„ ์ด์–ด์„œ ๋ถ™์ด๊ธฐ.
--order by๋Š” ๋งจ ๋’ค์— ์ž‘์„ฑ
SELECT
  c.course_id,
  c.course_name,
  e.enrollment_id,
  p.payment_id,
  p.payment_status,
  p.amount
FROM basic.courses c
LEFT JOIN basic.enrollments e
ON c.course_id = e.course_id
LEFT JOIN basic.payments p
ON e.enrollment_id = p.enrollment_id
ORDER BY c.course_id, e.enrollment_id, p.paid_at;

(3) ๊ด€๊ณ„(์นด๋””๋„๋ฆฌํ‹ฐ) ๊ฐ๊ฐ 1:1 / 1:N

  • 1(PK)์— N๊ฐœ์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋ถ™์–ด์„œ ์ตœ์ข… ๊ฒฐ๊ณผ๋กœ ์ถœ๋ ฅ๋˜๋Š” ํ–‰์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ๋‹ค. = 'Join ๊ฒฐ๊ณผ๊ฐ€ ๋ปฅํŠ€๊ธฐ๋œ๋‹ค'
    students (1) : enrollments (N) → ํ•™์ƒ 1๋ช…์€ ์ˆ˜๊ฐ•์‹ ์ฒญ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ•  ์ˆ˜ ์žˆ์Œ
    enrollments (1) : payments (N) → 1๊ฐœ ์‹ ์ฒญ์— ๊ฒฐ์ œ๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ์ฐํž ์ˆ˜ ์žˆ์Œ(์žฌ๊ฒฐ์ œ/๋ถ€๋ถ„ํ™˜๋ถˆ/๋ถ„ํ• ๊ฒฐ์ œ ๋“ฑ)
  • ์ตœ์ข… ๊ฒฐ๊ณผ ๋ถ„์„ ์‹œ์— ํ—ท๊ฐˆ๋ฆฌ์ง€ ์•Š์œผ๋ ค๋ฉด??
    • โญ๏ธ ๊ฐ ํ…Œ์ด๋ธ”์˜ PK/FK ๊ด€๊ณ„๋ฅผ ํŒŒ์•…ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.
    • ๋จผ์ € ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์ตœ์ข… ํ–‰ ๋‹จ์œ„(grain)๋ฅผ ์„ ์–ธ
      ํ•™์ƒ 1ํ–‰์”ฉ ๋ณด๊ณ  ์‹ถ๋‹ค → ์ตœ์ข… grain = student_id
      ์‹ ์ฒญ(์ˆ˜๊ฐ•) 1ํ–‰์”ฉ ๋ณด๊ณ  ์‹ถ๋‹ค → ์ตœ์ข… grain = enrollment_id
    • N์ชฝ ํ…Œ์ด๋ธ”์€ “JOIN ์ „์—” ์ง‘๊ณ„/์„ ํƒํ•ด์„œ 1ํ–‰์œผ๋กœ ๋งŒ๋“ค์–ด ๋ถ™์—ฌ๋ผ
      ํ•™์ƒ 1ํ–‰์”ฉ + ์ˆ˜๊ฐ•์‹ ์ฒญ ์š”์•ฝ๋งŒ ๋ถ™์ด๊ธฐ
      ์‹ ์ฒญ 1ํ–‰์”ฉ + ๊ฒฐ์ œ ์ด์•ก๋งŒ ๋ถ™์ด๊ธฐ

Q.1) JOIN

๋”๋ณด๊ธฐ

์ƒํ™ฉ: ์šด์˜ํŒ€์ด “์ˆ˜๊ฐ•์‹ ์ฒญ ๋ฆฌ์ŠคํŠธ(enrollments)”๋ฅผ ๋ณด๋Š”๋ฐ, `course_id`๋งŒ ์žˆ์œผ๋ฉด ์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ๋ถˆํŽธํ•ด์„œ '๊ฐ•์ขŒ๋ช…/์นดํ…Œ๊ณ ๋ฆฌ'๋ฅผ ๋ถ™์—ฌ ๋‹ฌ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๋ชฉํ‘œ: `enrollments`์™€ `courses`๋ฅผ **INNER JOIN**ํ•ด์„œ ์•„๋ž˜๋ฅผ ์ถœ๋ ฅํ•˜์„ธ์š”.
์ถœ๋ ฅ ์ปฌ๋Ÿผ: enrollment_id, course_id, course_name, category, final_price

์กฐ๊ฑด: `enrollment_status = 'active'` ์ธ ์‹ ์ฒญ๋งŒ
์ •๋ ฌ: `enroll_date` ์˜ค๋ฆ„์ฐจ์ˆœ

--์ •๋‹ต
select e.enrollment_id,
       c.course_id,
       c.course_name,
       c.category,
       e.final_price
from basic.enrollments as e inner join basic.courses as c
on e.course_id=c.course_id
where e.enrollment_status='active'
order by enroll_date;

Q.2) JOIN

๋”๋ณด๊ธฐ

์ƒํ™ฉ: CSํŒ€์ด ์ˆ˜๊ฐ•์‹ ์ฒญ(enrollments) ๋ชฉ๋ก์„ ๊ธฐ์ค€์œผ๋กœ,

  • ๊ฒฐ์ œ๊ฐ€ ์„ฑ๊ณต(paid)ํ•œ ์‹ ์ฒญ์ด๋ฉด ๊ฒฐ์ œ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ 
  • ๊ฒฐ์ œ ์„ฑ๊ณต ๊ธฐ๋ก์ด ์—†์œผ๋ฉด “๊ฒฐ์ œ ์ •๋ณด ์นธ์ด ๋น„์–ด์žˆ๊ฒŒ(NULL)” ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์–ดํ•ฉ๋‹ˆ๋‹ค.

์ฆ‰, ์ˆ˜๊ฐ•์‹ ์ฒญ์€ ์ „๋ถ€ ๋ณด์—ฌ์•ผ ํ•˜๋ฏ€๋กœ enrollments๊ฐ€ ๊ธฐ์ค€(LEFT) ์ž…๋‹ˆ๋‹ค.

๋ชฉํ‘œ: enrollments e๋ฅผ ๊ธฐ์ค€์œผ๋กœ payments p๋ฅผ LEFT JOINํ•ด์„œ ์•„๋ž˜ ์ปฌ๋Ÿผ์„ ์ถœ๋ ฅํ•˜์„ธ์š”.

์ถœ๋ ฅ ์ปฌ๋Ÿผ

  • e.enrollment_id
  • e.student_id
  • e.course_id
  • e.enroll_date
  • e.final_price
  • p.payment_id (๊ฒฐ์ œ ์„ฑ๊ณต์ด ์—†์œผ๋ฉด NULL)
  • p.paid_at (๊ฒฐ์ œ ์„ฑ๊ณต์ด ์—†์œผ๋ฉด NULL)
  • p.amount (๊ฒฐ์ œ ์„ฑ๊ณต์ด ์—†์œผ๋ฉด NULL)

์กฐ๊ฑด: ๊ฒฐ์ œ ์ •๋ณด๋Š” `payment_status = 'paid'`์ธ ๊ฒƒ๋งŒ ๋ถ™์ด๊ธฐ
- ๋‹จ, ๊ฒฐ์ œ ์„ฑ๊ณต์ด ์—†์–ด๋„ enrollments ํ–‰์€ ์‚ฌ๋ผ์ง€๋ฉด ์•ˆ ๋จ
์ •๋ ฌ: `e.enroll_date ASC`, `e.enrollment_id ASC`, `p.paid_at ASC`

--์ •๋‹ต
SELECT e.enrollment_id,
       e.student_id,
       e.course_id,
       e.enroll_date,
       e.final_price,
       p.payment_id,
       p.paid_at,
       p.amount
from basic.enrollments as e
left join basic.payments as p
on e.enrollment_id=p.enrollment_id
and p.payment_status='paid'
order by e.enroll_date ASC, e.enrollment_id ASC, p.paid_at ASC;

Q.3) ๋‹ค์ค‘JOIN

๋”๋ณด๊ธฐ

์ƒํ™ฉ
์šด์˜ํŒ€์ด **๊ฐ•์ขŒ(courses)** ๋ณ„๋กœ
1. ์‹ ์ฒญ์ด ๋ช‡ ๊ฑด์ธ์ง€(์‹ ์ฒญ ์ˆ˜)
2. ๊ฒฐ์ œ ์„ฑ๊ณต(`paid`) ๊ธˆ์•ก์ด ์–ผ๋งˆ์ธ์ง€(๋งค์ถœ)๋ฅผ ๋ณด๊ณ  ์‹ถ์–ดํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ ํ…Œ์ด๋ธ”(3๊ฐœ)
- `courses c`
- `enrollments e`
- `payments p`

์กฐ์ธ ๊ทœ์น™
- `c.course_id = e.course_id`
- `e.enrollment_id = p.enrollment_id`
- ๊ฒฐ์ œ๋Š” `payment_status='paid'`์ธ ๊ฒƒ๋งŒ ๋งค์ถœ๋กœ ์ธ์ • (์ด ์กฐ๊ฑด์€ **ON**์— ๋‘๋Š” ๊ฑธ ์ถ”์ฒœ)

์ถœ๋ ฅ ์ปฌ๋Ÿผ
- `course_id`
- `course_name`
- `enroll_cnt` = ์‹ ์ฒญ ๊ฑด์ˆ˜ (`COUNT(DISTINCT e.enrollment_id)`)
- `paid_revenue` = ๊ฒฐ์ œ ์„ฑ๊ณต ๊ธˆ์•ก ํ•ฉ๊ณ„ (`SUM(p.amount)`)
- `paid_payment_rows` = ๊ฒฐ์ œ ์„ฑ๊ณต ๊ฒฐ์ œ ์ด๋ฒคํŠธ ์ˆ˜ (`COUNT(p.payment_id)`)

์กฐ๊ฑด/์ •๋ ฌ
- ์กฐ๊ฑด: `c.is_active = 1` ์ธ ๊ฐ•์ขŒ๋งŒ
- ์ •๋ ฌ: `paid_revenue DESC`, `course_name ASC`

--๐Ÿ“Œ์˜ค๋‹ต
select c.course_id,
       c.course_name,
       count(distinct e.enrollment_id) as enroll_cnt,
       sum(p.amount) as paid_revenue,
       COUNT(p.payment_id) as paid_payment_rows
from basic.courses as c
	left join basic.enrollments as e
	on c.course_id=e.course_id
	left join basic.payments as p
	on e.enrollment_id=p.enrollment_id
	and p.payment_status='paid'
where c.is_active=1
group by c.course_id,c.course_name --๐Ÿ“Œselect์— ์ง‘๊ณ„์ปฌ๋Ÿผ๊ณผ ์ผ๋ฐ˜์ปฌ๋Ÿผ์ด ๊ฐ™์ด ์žˆ์œผ๋ฉด group by ํ•„์ˆ˜
order by c.course_name asc, paid_revenue desc;
<์˜ค๋‹ต๋…ธํŠธ>
- select์— ์ง‘๊ณ„์ปฌ๋Ÿผ๊ณผ ์ผ๋ฐ˜์ปฌ๋Ÿผ์ด ๊ฐ™์ด ์žˆ์œผ๋ฉด group by ํ•„์ˆ˜!!
- 'join ~ on ~ and'๋Š” ํ•ด๋‹นํ•˜๋Š” join์— ๊ฐ™์ด ์ ๊ณ , where์€ from(+join) ๋ฐ–์— ์ ๊ธฐ

๐Ÿฏ์‹ค๋ฌด ํŒ

๋ฐ์ดํ„ฐ ๋ช…์„ธ์„œ

1. ๋ฐ์ดํ„ฐ ๋ถ„์„ ์ „, ํ…Œ์ด๋ธ” ์š”์•ฝํ‘œ๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ด ํ•„์ˆ˜

2. ํ’ˆ์งˆ ๊ทœ์น™: ํ…Œ์ด๋ธ” ๊ฐ’ ๊ตฌ์„ฑ์—์„œ ๋ฐ์ดํ„ฐ ํ’ˆ์งˆ์ด ๊น”๋”(?)ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ •ํ•ด์•ผ ํ•˜๋Š” ๊ทœ์น™


3. ๋ฐ์ผ๋ฆฌ ํ€˜์ŠคํŠธ
# ๊ณผ์ œ

๊ณผ์ œ1)

๋”๋ณด๊ธฐ

โœ… ์ƒํ™ฉ
์šด์˜ํŒ€์ด ๊ณต์ง€ ๋ฐœ์†ก์„ ์œ„ํ•ด “์‚ฌ๋žŒ ๋ชฉ๋ก”์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
ํ•™์ƒ(students) ๊ณผ ๊ฐ•์‚ฌ(instructors) ๋ฅผ ํ•œ ๊ฒฐ๊ณผ๋กœ ํ•ฉ์ณ์„œ(์„ธ๋กœ๋กœ ์Œ“์•„์„œ) ๋ณด๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.
- ์ด๋ฆ„์ด ๊ฐ™์€ ์‚ฌ๋žŒ์ด ์žˆ์„ ์ˆ˜๋„ ์žˆ์œผ๋‹ˆ(๋™๋ช…์ด์ธ) ์ค‘๋ณต ์ œ๊ฑฐ๋ฅผ ํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

โœ… ๋ชฉํ‘œ
ํ•™์ƒ/๊ฐ•์‚ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์•„๋ž˜ ์ปฌ๋Ÿผ์œผ๋กœ ํ†ต์ผํ•ด์„œ ํ•œ ๊ฒฐ๊ณผ๋กœ ํ•ฉ์น˜๊ธฐ

์ถœ๋ ฅ ์ปฌ๋Ÿผ
- `person_type` : ํ•™์ƒ์ด๋ฉด `'student'`, ๊ฐ•์‚ฌ๋ฉด `'instructor'`
- `person_id` : ํ•™์ƒ์€ `student_id`, ๊ฐ•์‚ฌ๋Š” `instructor_id`
- `person_name` : ํ•™์ƒ์€ `student_name`, ๊ฐ•์‚ฌ๋Š” `instructor_name`

์ •๋ ฌ
- `person_type ASC`, `person_id ASC`

select 'student' as person_type,
        student_id as person_id,
        student_name
from basic.students
union all
select 'instructor' as person_type,
		instructor_id as person_id,
		instructor_name
from instructors
order by person_type asc, person_id ASC;

๊ณผ์ œ2)

๋”๋ณด๊ธฐ

โœ… ์ƒํ™ฉ
์šด์˜ํŒ€์ด “์–ด๋–ค ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์‹ ์ฒญ์ด ์ž˜ ๋“ค์–ด์˜ค๋Š”์ง€” ๋ณด๊ณ  ์‹ถ์–ดํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ `enrollments`์—๋Š” `category`๊ฐ€ ์—†๊ณ , `courses`์—๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค.
→ **์ˆ˜๊ฐ•์‹ ์ฒญ(enrollments)** ๊ณผ **๊ฐ•์ขŒ(courses)** ๋ฅผ `course_id`๋กœ **INNER JOIN**ํ•ด์„œ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๋ถ™์ธ ๋’ค ์ง‘๊ณ„ํ•ฉ๋‹ˆ๋‹ค.

โœ… ๋ชฉํ‘œ
`enrollment_status = 'active'` ์ธ ์‹ ์ฒญ๋งŒ ๋Œ€์ƒ์œผ๋กœ, ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ง€ํ‘œ๋ฅผ ๋ฝ‘์œผ์„ธ์š”.

์ถœ๋ ฅ ์ปฌ๋Ÿผ
- `category`
- `active_enrollments` : ์ง„ํ–‰์ค‘ ์‹ ์ฒญ ๊ฑด์ˆ˜ (`COUNT(*)`)
- `unique_students` : ์ง„ํ–‰์ค‘ ์‹ ์ฒญํ•œ “๊ณ ์œ  ํ•™์ƒ ์ˆ˜” (`COUNT(DISTINCT e.student_id)`)
- `avg_final_price` : ์ง„ํ–‰์ค‘ ์‹ ์ฒญ์˜ ํ‰๊ท  ๊ฒฐ์ œ๋Œ€์ƒ๊ธˆ์•ก (`ROUND(AVG(e.final_price), 0)`)

์กฐ๊ฑด
- `enrollment_status = 'active'`

์ถ”๊ฐ€ ์กฐ๊ฑด (๊ทธ๋ฃน ํ•„ํ„ฐ)
- `active_enrollments >= 2` ์ธ ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ ๋‚จ๊ธฐ๊ธฐ (`HAVING`)

์ •๋ ฌ
- `active_enrollments DESC`, `category ASC`

select category,
       count(*) as active_enrollments,
       count(distinct e.student_id) as unique_students,
       round(avg(e.final_price), 0) as avg_final_price
from courses c
inner join enrollments e
on c.course_id=e.course_id
where e.enrollment_status='active'
group by c.category
having count(*) >= 2
order by active_enrollments DESC, category ASC;

๊ณผ์ œ3)

๋”๋ณด๊ธฐ

โœ… ์ƒํ™ฉ
CSํŒ€์ด “๊ฒฐ์ œ ์„ฑ๊ณต์ด ์•ˆ ๋œ ์‹ ์ฒญ๊ฑด”์„ ์ฐพ์•„์„œ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋ ค ํ•ฉ๋‹ˆ๋‹ค.
ํฌ์ธํŠธ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.:
- ๊ธฐ์ค€(๋ชจ์ˆ˜)์€ **์ˆ˜๊ฐ•์‹ ์ฒญ(enrollments)** ์ž…๋‹ˆ๋‹ค → ์‹ ์ฒญ์€ ์ผ๋‹จ ์กด์žฌํ•˜๋‹ˆ๊นŒ
- ๊ฒฐ์ œ ์„ฑ๊ณต(`payment_status='paid'`)์ด ์žˆ์œผ๋ฉด ๊ฒฐ์ œ ์ •๋ณด๋ฅผ ๋ถ™์ด๊ณ 
- **๊ฒฐ์ œ ์„ฑ๊ณต์ด ์—†์œผ๋ฉด** ๊ฒฐ์ œ ์ปฌ๋Ÿผ์ด `NULL`๋กœ ๋‚จ๊ฒŒ ๋งŒ๋“ค๊ณ (LEFT JOIN)
- ๊ทธ `NULL`์„ ์ด์šฉํ•ด ๊ฒฐ์ œ ์„ฑ๊ณต์ด ์—†๋Š” ์‹ ์ฒญ๋งŒ ๊ณจ๋ผ๋ƒ…๋‹ˆ๋‹ค.

โœ… ๋ชฉํ‘œ
๊ฒฐ์ œ ์„ฑ๊ณต(paid) ๊ธฐ๋ก์ด ์—†๋Š” ์‹ ์ฒญ๋งŒ ์ถœ๋ ฅํ•˜์„ธ์š”.


์ถœ๋ ฅ ์ปฌ๋Ÿผ
- `e.enrollment_id`
- `e.student_id`
- `e.course_id`
- `e.enroll_date`
- `e.enrollment_status`
- `e.final_price`

์กฐ๊ฑด
- ์‹ ์ฒญ ์ƒํƒœ๋Š” `active`, `completed`๋งŒ ํฌํ•จ (์ทจ์†Œ `cancelled` ์ œ์™ธ)
- ๊ฒฐ์ œ๋Š” `paid`๋งŒ “์„ฑ๊ณต”์œผ๋กœ ์ธ์ •
    - ์ด ์กฐ๊ฑด์€ **WHERE๊ฐ€ ์•„๋‹ˆ๋ผ ON์—** ๋„ฃ๋Š” ๊ฒŒ ํ•ต์‹ฌ!

์ •๋ ฌ
- `e.enroll_date ASC`, `e.enrollment_id ASC`

select
	e.enrollment_id,
	e.student_id,
	e.course_id,
	e.enroll_date,
	e.enrollment_status,
	e.final_price
from basic.enrollments e
	left join basic.payments p
	on e.enrollment_id=p.enrollment_id
	and p.payment_status='paid' --์šฐ์ธก ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ์กฐ๊ฑด์ด๋ฏ€๋กœ on์— ์กฐ๊ฑด๋‹ฌ๊ธฐ
where e.enrollment_status in ('active', 'complete') --๊ธฐ์ค€(๋ชจ์ˆ˜)์— ๋Œ€ํ•œ ์กฐ๊ฑด์ด๋ฏ€๋กœ where์— ์กฐ๊ฑด๋‹ฌ๊ธฐ ๊ฐ€๋Šฅ
      and p.payment_status is null
order by e.enroll_date ASC, e.enrollment_id ASC;

<์˜ค๋‹ต๋…ธํŠธ>
JOIN์—์„œ ์กฐ๊ฑด ์ถ”๊ฐ€์‹œ,
- ๊ธฐ์ค€(๋ชจ์ˆ˜) ์กฐ๊ฑด: where์— ์ถ”๊ฐ€ (์šฐ์ธก ํ…Œ์ด๋ธ”์˜ null ์œ ์ง€๋จ)
- ์šฐ์ธก ํ…Œ์ด๋ธ” ์กฐ๊ฑด: on์— ์ถ”๊ฐ€ (null ์กด์žฌ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ)

 

 

 

 

 

 

 

 

 

 

 

 

๊ณผ์ œ4)

๋”๋ณด๊ธฐ

โœ… ์ƒํ™ฉ
์šด์˜ํŒ€์ด ๊ฐ•์ขŒ๋ณ„๋กœ ์•„๋ž˜๋ฅผ ๋ณด๊ณ  ์‹ถ์–ดํ•ฉ๋‹ˆ๋‹ค.
- ์‹ ์ฒญ์ด ์–ผ๋งˆ๋‚˜ ๋“ค์–ด์™”๋Š”์ง€(์‹ ์ฒญ ์ˆ˜)
- ๊ฒฐ์ œ ์„ฑ๊ณต ๋งค์ถœ์ด ์–ผ๋งˆ์ธ์ง€(๋งค์ถœ)
- ๊ฒฐ์ œ ์ด๋ฒคํŠธ๊ฐ€ ๋ช‡ ๋ฒˆ ๋ฐœ์ƒํ–ˆ๋Š”์ง€(๊ฒฐ์ œ rows)

์‚ฌ์šฉ ํ…Œ์ด๋ธ” (3๊ฐœ)
- `courses c` (1ํ–‰=๊ฐ•์ขŒ 1๊ฐœ)
- `enrollments e` (1ํ–‰=์ˆ˜๊ฐ•์‹ ์ฒญ 1๊ฑด)
- `payments p` (1ํ–‰=๊ฒฐ์ œ ์ด๋ฒคํŠธ 1๊ฑด)

JOIN ๊ทœ์น™
- `c.course_id = e.course_id`
- `e.enrollment_id = p.enrollment_id`
- ๋งค์ถœ์€ `p.payment_status = 'paid'` ์ธ ๊ฒฐ์ œ๋งŒ ์ธ์ • (์ด ์กฐ๊ฑด์€ **ON**์— ๋‘๊ธฐ)

์ถœ๋ ฅ ์ปฌ๋Ÿผ
- `c.course_id`
- `c.course_name`
- `enroll_cnt` : ์‹ ์ฒญ ๊ฑด์ˆ˜ (`COUNT(DISTINCT e.enrollment_id)`)
- `paid_enroll_cnt` : ๊ฒฐ์ œ ์„ฑ๊ณต ์ด๋ ฅ์ด ์žˆ๋Š” ์‹ ์ฒญ ์ˆ˜ (`COUNT(DISTINCT p.enrollment_id)`)
- `paid_payment_rows` : ๊ฒฐ์ œ ์„ฑ๊ณต ๊ฒฐ์ œ ์ด๋ฒคํŠธ ์ˆ˜ (`COUNT(p.payment_id)`)
- `paid_revenue` : ๊ฒฐ์ œ ์„ฑ๊ณต ๊ธˆ์•ก ํ•ฉ (`SUM(p.amount)`)
- `paid_enroll_rate_pct` : `paid_enroll_cnt / enroll_cnt * 100` (์†Œ์ˆ˜์  1์ž๋ฆฌ)

์กฐ๊ฑด
- `c.is_active = 1` (์šด์˜์ค‘ ๊ฐ•์ขŒ๋งŒ)

์ฃผ์˜(์ค‘์š”)
- ์‹ ์ฒญ์ด 1๊ฑด๋„ ์—†์œผ๋ฉด `enroll_cnt=0`์ด์–ด์„œ ๋น„์œจ ๊ณ„์‚ฐ์ด ์• ๋งคํ•ด์ง‘๋‹ˆ๋‹ค.
    → ์ด๋ฒˆ ๊ณผ์ œ์—์„œ๋Š” **์‹ ์ฒญ์ด 1๊ฑด ์ด์ƒ์ธ ๊ฐ•์ขŒ๋งŒ** ํฌํ•จ์‹œํ‚ค์„ธ์š”. (`HAVING`)

์ •๋ ฌ
- `paid_revenue DESC`, `c.course_name ASC`

select c.course_id,
       c.course_name,
       count(distinct e.enrollment_id) as enroll_cnt,
       count(distinct p.enrollment_id) as paid_enroll_cnt,
       count(distinct p.payment_id) as paid_payment_rows,
       sum(p.amount) as paid_revenue,
       ROUND(count(distinct p.enrollment_id)/count(distinct e.enrollment_id)*100, 1) as paid_enroll_rate_pct
from basic.courses c
	left join basic.enrollments e
	on c.course_id=e.course_id
	left join basic.payments p
	on e.enrollment_id=p.enrollment_id
		and p.payment_status='paid'
where c.is_active=1
group by c.course_id, c.course_name
having count(distinct e.enrollment_id) >= 1
order by paid_revenue DESC, c.course_name ASC;

# ๋ฐ์ผ๋ฆฌ ํ€ด์ฆˆ

Q1. `UNION`๊ณผ `UNION ALL`์˜ ๊ฐ€์žฅ ํฐ ์ฐจ์ด๋Š”?
A. UNION์€ ๊ฐ€๋กœ ๊ฒฐํ•ฉ, UNION ALL์€ ์„ธ๋กœ ๊ฒฐํ•ฉ์ด๋‹ค
B. UNION์€ ์ค‘๋ณต ํ–‰ ์ œ๊ฑฐ, UNION ALL์€ ์ค‘๋ณต ํ–‰ ์œ ์ง€๋‹ค
C. UNION์€ ORDER BY๋ฅผ ๋ชป ์“ฐ๊ณ , UNION ALL์€ ORDER BY๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋‹ค
D. UNION ALL์€ PK/FK๊ฐ€ ์žˆ์–ด์•ผ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค

Q2. UNION์œผ๋กœ “์ „์ฒด ๊ฒฐ๊ณผ”๋ฅผ ์ •๋ ฌํ•˜๋ ค๋ฉด `ORDER BY`๋Š” ์–ด๋””์— ์จ์•ผ ํ• ๊นŒ์š”?
A. ์ฒซ ๋ฒˆ์งธ SELECT ์•ˆ์— ์“ด๋‹ค
B. ๊ฐ SELECT๋งˆ๋‹ค ORDER BY๋ฅผ ๊ฐ๊ฐ ์“ด๋‹ค
C. UNION์œผ๋กœ ์—ฐ๊ฒฐ๋œ ๋งˆ์ง€๋ง‰ SELECT ๋’ค์— ์“ด๋‹ค
D. UNION์—์„œ๋Š” ORDER BY๋ฅผ ์“ธ ์ˆ˜ ์—†๋‹ค

Q3. UNION์„ ์‚ฌ์šฉํ•  ๋•Œ ํ•„์š”ํ•œ ์กฐ๊ฑด์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ๊ฒƒ์€?
A. ๋‘ SELECT์˜ ์ปฌ๋Ÿผ ์ด๋ฆ„์ด ์™„์ „ํžˆ ๊ฐ™์•„์•ผ ํ•œ๋‹ค
B. ๊ฐ SELECT๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ปฌ๋Ÿผ ๊ฐœ์ˆ˜๋Š” ๋‹ฌ๋ผ๋„ ๋œ๋‹ค
C. ๊ฐ SELECT๋Š” ๊ฐ™์€ ๊ฐœ์ˆ˜์˜ ์ปฌ๋Ÿผ์„ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค
D. ๋‘ ํ…Œ์ด๋ธ” ์‚ฌ์ด์— PK/FK๊ฐ€ ๋ฐ˜๋“œ์‹œ ์„ค์ •๋ผ ์žˆ์–ด์•ผ ํ•œ๋‹ค

 

Q4. `LEFT JOIN`์—์„œ ์˜ค๋ฅธ์ชฝ ํ…Œ์ด๋ธ”์— ๋งค์นญ๋˜๋Š” ํ–‰์ด ์—†์œผ๋ฉด ์–ด๋–ค ์ผ์ด ๋ฐœ์ƒํ•˜๋‚˜์š”?
A. ์™ผ์ชฝ ํ–‰๋„ ํ•จ๊ป˜ ์‚ฌ๋ผ์ง„๋‹ค
B. ์™ผ์ชฝ ํ–‰์€ ๋‚จ๊ณ , ์˜ค๋ฅธ์ชฝ ์ปฌ๋Ÿผ๋“ค์ด NULL๋กœ ์ฑ„์›Œ์ง„๋‹ค
C. ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค
D. ์ž๋™์œผ๋กœ RIGHT JOIN์œผ๋กœ ๋ฐ”๋€๋‹ค

Q5. ์•„๋ž˜ ์ฟผ๋ฆฌ๊ฐ€ “LEFT JOIN์„ ํ–ˆ๋Š”๋ฐ๋„ ๊ฒฐ์ œ ์—†๋Š” ์‹ ์ฒญ์ด ์‚ฌ๋ผ์ง€๋Š”” ์ด์œ ๋กœ ๊ฐ€์žฅ ์ ์ ˆํ•œ ๊ฒƒ์€?

SELECT e.enrollment_id, p.payment_status
FROM basic.enrollments e
LEFT JOIN basic.payments p
ON e.enrollment_id= p.enrollment_id
WHERE p.payment_status='paid';


A. LEFT JOIN์€ ์›๋ž˜ ๊ฒฐ์ œ ์—†๋Š” ์‹ ์ฒญ์„ ๋ชป ๊ฐ€์ ธ์˜จ๋‹ค
B. WHERE ์กฐ๊ฑด์ด ์˜ค๋ฅธ์ชฝ NULL ํ–‰์„ ์ œ๊ฑฐํ•ด์„œ ๊ฒฐ๊ณผ๊ฐ€ INNER JOIN์ฒ˜๋Ÿผ ๋œ๋‹ค
C. payment_status๋Š” ์ˆซ์žํ˜•์ด ์•„๋‹ˆ๋ผ์„œ ๋น„๊ต๊ฐ€ ์•ˆ ๋œ๋‹ค
D. ON์—๋Š” ์กฐ๊ฑด์„ ์“ธ ์ˆ˜ ์—†์–ด์„œ ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค


# SQL ์‚ฌ์ „์บ ํ”„ ๋ฌธ์ œ [๋‹ฌ๋ฆฌ๊ธฐ๋ฐ˜]

๋ฌธ์ œ1)

- user ํ…Œ์ด๋ธ”์€ ์ŠคํŒŒ๋ฅดํƒ€ ์ฝ”๋”ฉํด๋Ÿฝ์— ๊ฐ€์ž…ํ•œ ์œ ์ €๋“ค์˜ ์ •๋ณด๋ฅผ ๋‚ ์งœ๋ณ„๋กœ ๊ธฐ๋กํ•œ ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค.

- name_cnt: “๊น€”์”จ ์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ต์œก์ƒ์˜ ์ˆ˜

--์ •๋‹ต
select count(distinct user_id) as name_cnt
FROM sparta.users
where name like '๊น€%';

 

๋ฌธ์ œ2)
๋‚ ์งœ๋ณ„ ํš๋“ํฌ์ธํŠธ ์กฐํšŒํ•˜๊ธฐ
- created_at: ์ต๋ช…ํ™”๋œ ์œ ์ €๋“ค์˜ ์•„์ด๋””(varchar255)
- average_points: ์œ ์ €๊ฐ€ ํš๋“ํ•œ ๋‚ ์งœ๋ณ„ ํ‰๊ท  ํฌ์ธํŠธ(int), ๋ฐ˜์˜ฌ๋ฆผ ํ•„์ˆ˜

--์ •๋‹ต
select date(created_at) as created_at,
       round(avg(point), 0) as average_points
from sparta.point_users
group by date(created_at);

 

๋ฌธ์ œ3)
์ด์šฉ์ž์˜ ํฌ์ธํŠธ ์กฐํšŒํ•˜๊ธฐ

- user_id: ์ต๋ช…ํ™”๋œ ์œ ์ €๋“ค์˜ ์•„์ด๋””
- email: ์œ ์ €๋“ค์˜ ์ด๋ฉ”์ผ
- point: ์œ ์ €๊ฐ€ ํš๋“ํ•œ ํฌ์ธํŠธ
- users ํ…Œ์ด๋ธ”์—๋Š” ์žˆ์ง€๋งŒ point_users์—๋Š” ์—†๋Š” user๋Š” ํฌ์ธํŠธ๊ฐ€ ์—†์œผ๋ฏ€๋กœ 0 ์œผ๋กœ ์ฒ˜๋ฆฌ
- ํฌ์ธํŠธ ๊ธฐ์ค€์œผ๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌ

--์ •๋‹ต
select u.user_id,
       u.email,
       ifnull(pu.point, 0) as point
from sparta.users u
left join sparta.point_users pu
on u.user_id=pu.user_id
order by point desc;

 

๋ฌธ์ œ4)

๋‹จ๊ณจ ๊ณ ๊ฐ๋‹˜ ์ฐพ๊ธฐ

--๊ณ ๊ฐ๋ณ„๋กœ ์ฃผ๋ฌธ ๊ฑด์ˆ˜์™€ ์ด ์ฃผ๋ฌธ ๊ธˆ์•ก์„ ์กฐํšŒํ•˜๋Š” SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.
--์ •๋‹ต
select c.CustomerName,
       count(OrderID) as OrderCount,
       sum(TotalAmount) as TotalSpent
from customers c
left join orders o
on c.CustomerID=o.CustomerID
group by c.CustomerName;
--๋‚˜๋ผ๋ณ„๋กœ ์ด ์ฃผ๋ฌธ ๊ธˆ์•ก์ด ๊ฐ€์žฅ ๋†’์€ ๊ณ ๊ฐ์˜ ์ด๋ฆ„๊ณผ ๊ทธ ๊ณ ๊ฐ์˜ ์ด ์ฃผ๋ฌธ ๊ธˆ์•ก์„ ์กฐํšŒํ•˜๋Š” SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.
--๐Ÿ“Œ์˜ค๋‹ต (์„œ๋ธŒ์ฟผ๋ฆฌ๋ฌธ ์ˆ™์ง€ ๋ถ€์กฑ)
--๐Ÿ’กํ•ด์„ค
SELECT c.Country,
       c.CustomerName AS Top_Customer,
       SUM(o.TotalAmount) AS Top_Spent
FROM Customers c
	JOIN Orders o
    ON c.CustomerID = o.CustomerID
GROUP BY c.Country, c.CustomerName
HAVING --having์— ์„œ๋ธŒ์ฟผ๋ฆฌ๋ฌธ ์ž‘์„ฑํ•˜์—ฌ, ๊ตญ๊ฐ€๋ณ„ 1์œ„ ์„ ๋ณ„
    SUM(o.TotalAmount) = (
        SELECT MAX(SumSpent)
        FROM (SELECT SUM(o2.TotalAmount) AS SumSpent
             FROM Customers c2
            	 JOIN Orders o2
                 ON c2.CustomerID = o2.CustomerID
             WHERE c2.Country = c.Country
             GROUP BY c2.CustomerID) AS Subquery
             );
--์‰ฌ์šด ๋‹ต (rank() ํ•จ์ˆ˜ ์‚ฌ์šฉํ•ด์„œ ์„œ๋ธŒํ€ด๋ฆฌ๋ฌธ ์ž‘์„ฑ)
select Country, CustomerName, Top_Spent
from
(
select c.Country,
       c.CustomerName,
       sum(o.TotalAmount) as Top_Spent,
       rank() over(partition by c.Country order by sum(o.TotalAmount) desc) as rnk
from Customers as c
	left join Orders as o
	on c.CustomerID=o.CustomerID
group by c.Country, c.CustomerName
) a
where rnk=1
order by a.CustomerName;
<์˜ค๋‹ต๋…ธํŠธ>
having ์•ˆ์— ์„œ๋ธŒ์ฟผ๋ฆฌ๋ฌธ ์ž‘์„ฑํ•˜๋Š” ๋ฒ„์ „ ๋‹ต ์ดํ•ด ๋ถˆ๊ฐ€. ์ถ”๊ฐ€ ๊ณต๋ถ€ ํ•„์š”
๋‹ค๋ฅธ ๋ฐฉ๋ฒ• ์ฐพ์Œ.

2025.12.29 - [์ŠคํŒŒ๋ฅดํƒ€ ๋‚ด์ผ๋ฐฐ์›€์บ ํ”„(25.12.01~)] - [๊ฐœ๋…์ •๋ฆฌ] Having + Subquery

 

[๊ฐœ๋…์ •๋ฆฌ] Having + Subquery

Pattern 1) ์ง‘๊ณ„๊ฐ’์„ ์ƒ์ˆ˜์™€ ๋น„๊ตQ. ์ด ๊ตฌ๋งค๊ธˆ์•ก์ด 100๋งŒ ์ด์ƒ์ธ ๊ณ ๊ฐSELECT customer_id, SUM(amount) AS total_spentFROM ordersGROUP BY customer_idHAVING SUM(amount) >= 1000000;Next : “1000000” ์ž๋ฆฌ์— ์„œ๋ธŒ์ฟผ๋ฆฌ๋กœ ๋งŒ๋“  ๊ฐ’์„

tjsdud7032.tistory.com