<์ค๋์ ํ์ต>
- 10์ SQL ๋ผ์ด๋ธ ์ธ์ (Union[All] / Join) + ๋ฐ์ผ๋ฆฌ ํ์คํธ
<ํ์ต๋ด์ฉ ์ ๋ฆฌ>
# SQL ๋ผ์ด๋ธ ์ธ์
1. Union / Union All

: SELECT ๊ฒฐ๊ณผ๋ฅผ ์ธ๋ก๋ก ๋ถ์ฌ์ ํ๋๋ก ๋ง๋ญ๋๋ค.
ex. 1์ ์ ์ฒญ์ ๋ชฉ๋ก์ด ์๊ณ , 12์ ์ ์ฒญ์ ๋ชฉ๋ก์ด ๋ฐ๋ก ์์ ๋
์น ์ ์ ๋ก๊ทธ์ ์ฑ ์ ์ ๋ก๊ทธ๊ฐ ๋ฐ๋ก ์๊ณ “์ ์ฒด ์ ์ ”์ ๋ณด๊ณ ์ถ์ ๋
“ํ์ ๋ชฉ๋ก”๊ณผ “๊ฐ์ฌ ๋ชฉ๋ก”์ ํฉ์ณ์ “์ ์ฒด ์ฌ๋ ๋ชฉ๋ก”์ ๋ง๋ค๊ณ ์ถ์ ๋
- ๊ฐ SELECT๋ ๊ฐ์ ๊ฐ์์ ์ปฌ๋ผ์ ๋ฐํํด์ผ ํจ (์ปฌ๋ผ๋ช ์ด ๊ฐ์ง ์์๋ ๋จ.)
- ๊ฐ์ ์์น์ ์ปฌ๋ผ๋ผ๋ฆฌ ๋ฐ์ดํฐ ํ์ ์ด ํธํ๋์ด์ผ ํจ
- ๊ฒฐ๊ณผ ์ปฌ๋ผ๋ช ์ ์ฒซ ๋ฒ์งธ 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
'์คํ๋ฅดํ ๋ด์ผ๋ฐฐ์์บ ํ(25.12.01~)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ์คํ๋ฅดํ ๋ด์ผ๋ฐฐ์์บ ํ_๋ณธ์บ ํ_data11๊ธฐ ๊น์ ์_TIL_Day 6 (2) | 2025.12.30 |
|---|---|
| [๊ฐ๋ ์ ๋ฆฌ] Having + Subquery (0) | 2025.12.29 |
| SQL ๊ฑท๊ธฐ๋ฐ ๋ฌธ์ ํตํฉ (1) | 2025.12.27 |
| ์คํ๋ฅดํ ๋ด์ผ๋ฐฐ์์บ ํ_๋ณธ์บ ํ_data11๊ธฐ ๊น์ ์_TIL_Day 4 (1) | 2025.12.26 |
| ์คํ๋ฅดํ ๋ด์ผ๋ฐฐ์์บ ํ_๋ณธ์บ ํ_data11๊ธฐ ๊น์ ์_TIL_Day 3 (0) | 2025.12.24 |