가끔은 단순히 데이터를 그룹화하고 결과를 필터링하는 것만으론 부족할 때가 있어. 예를 들어, 학생 그룹의 평균 점수를 외부 기준이랑 비교하고 싶을 때 말이지. 이럴 때 HAVING이랑 서브쿼리가 등장해 — SQL 쿼리 안에서 더 똑똑한 결정을 내릴 수 있게 해주는 강력한 도구야.
HAVING 다시 보기
이번엔 HAVING이랑 같이 쓰는 서브쿼리에 집중해보자. 이걸로 집계된 값 레벨에서 데이터를 필터링할 수 있어. 왜냐고? WHERE는 개별 행을 필터링하지만, HAVING은 이미 그룹화된 데이터에 적용돼 — 분석의 레벨이 달라서 너의 가능성을 확장시켜주지.
서브쿼리랑 HAVING 조합에 들어가기 전에, HAVING이 뭔지, WHERE랑 뭐가 다른지 잠깐 복습하자.
WHERE는 그룹화(GROUP BY) 전에 행을 필터링해.HAVING은 집계 후에, 이미 그룹화된 데이터에 필터를 거는 거야.
예를 들어, 학생들이랑 그들의 점수를 분석한다고 해보자. WHERE로는 특정 최소 점수 이하인 학생을 제외할 수 있고, HAVING으로는 평균이나 최대 점수를 기준으로 학생 그룹 전체를 제외할 수 있어.
데이터 예시
학생 예시 테이블이야:
students 테이블:
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
HAVING 사용 예시 (서브쿼리 없이)
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > 75;
결과:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
| Math | 75.0 |
"History" 학과는 평균 점수가 75보다 낮아서 결과에 안 나와. 간단하지? 이제 서브쿼리로 살짝 마법을 더해보자. 다음 예시에서는 모든 학과의 전체 평균이랑 비교해서 필터링할 수 있어.
HAVING에서 서브쿼리
HAVING에서 서브쿼리를 쓰면 집계 데이터 필터링에 유연성이 확 올라가. 평균 점수나 최대값 같은 집계값을 DB의 다른 부분에서 계산한 값이랑 비교할 수 있지. 쉽게 말해, "우리 결과가 전체 평균보다 나은가?" 이런 것도 체크할 수 있어.
예시: 학과별 평균 점수로 필터링
예를 들어, 학생들이 다른 학과보다 더 잘하는 학과 — 즉, 학과 평균 점수가 전체 평균보다 높은 학과를 찾고 싶다고 해보자.
데이터는 이래:
students 테이블:
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
먼저 전체 학생 평균 점수를 구해보자:
SELECT AVG(grade) AS university_avg
FROM students;
이제 HAVING에 서브쿼리를 넣어보자:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
결과:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
여기서 무슨 일이 일어나는지 보자:
- 서브쿼리 (
SELECT AVG(grade) FROM students)가 전체 평균 점수를 계산해 — 여기선 77이야. - 메인 쿼리는 학과별로 학생을 그룹화하고 각 학과의 평균 점수를 구해.
HAVING이 학과 평균이 전체 평균보다 높은 학과만 남겨줘.
WHERE와 HAVING 비교
차이를 이해하려면, 평균보다 점수가 높은 학생만 뽑고 싶다고 해보자. 이건 WHERE만으로 가능해:
SELECT name, grade
FROM students
WHERE grade > (SELECT AVG(grade) FROM students);
결과 (앞의 테이블 기준):
| name | grade |
|---|---|
| Alex | 80 |
| Maria | 85 |
| Dan | 90 |
근데 학과별로 평균 점수가 전체 평균보다 높은 학과를 보고 싶으면 HAVING이 필수야 — 왜냐면 행이 아니라 그룹을 필터링하니까:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
결과:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
요약:
WHERE는 그룹화 전에 개별 행에 적용돼.HAVING은 집계 후 그룹에 적용돼.
예시: 여러 집계값으로 작업하기
또 다른 케이스를 보자. students 테이블에 학생 점수와 학과 정보가 있다고 해보자:
students 테이블:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
이제 이런 학과를 찾고 싶어:
- 학생 평균 점수가 전체 평균보다 높고,
- 학과 내 최고 점수가 90보다 높아야 해.
이렇게 쿼리를 짜면 돼:
SELECT department, AVG(grade) AS avg_grade, MAX(grade) AS max_grade
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND MAX(grade) > 90;
이 쿼리에서 일어나는 일:
AVG(grade)> (SELECT AVG(grade) FROM students) — 학과가 평균적으로 더 잘하는지 체크.MAX(grade)> 90 — 학과에 90점 넘는 학생이 있는지 체크.
결과:
| department | avg_grade | max_grade |
|---|---|---|
| Math | 92.5 | 95 |
"Math" 학과만이 평균도 높고, 90점 넘는 학생도 있는 유일한 학과야.
예시: 최소 편차 그룹 찾기
이번엔, 학생들 중에서 최고점과 최저점의 차이가 전체 대학보다 작은 그룹을 찾고 싶다고 해보자.
이 students 테이블을 쓸 거야:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
단계를 나눠서 해보자:
- 먼저 전체 대학에서 최고-최저 점수 차이를 구해:
SELECT MAX(grade) - MIN(grade) AS range_university FROM students; - 이제 메인 쿼리랑 이 서브쿼리를 합쳐보자:
SELECT department, MAX(grade) - MIN(grade) AS range_department
FROM students
GROUP BY department
HAVING (MAX(grade) - MIN(grade)) < ( SELECT MAX(grade) - MIN(grade) FROM students );
쿼리 결과:
| department | range_department |
|---|---|
| Physics | 5 |
| Math | 5 |
"Physics"랑 "Math" 그룹은 점수 편차가 전체 대학보다 더 작아서 더 안정적인 점수를 보여줬어.
HAVING과 서브쿼리 쿼리 최적화
서브쿼리를 중첩해서 쓰면, 특히 큰 데이터베이스에서는 성능에 꽤 영향이 갈 수 있어. 몇 가지 팁을 줄게:
인덱스 사용하기. 서브쿼리가 WHERE나 JOIN에 쓰이는 컬럼에서 실행된다면, 그 컬럼에 인덱스가 있는지 꼭 확인해.
데이터 과다 방지. 서브쿼리가 너무 많은 중간 결과를 반환하면, 쿼리를 단계별로 나누거나 임시 테이블을 써봐.
EXPLAIN으로 쿼리 프로파일링. PostgreSQL이 쿼리를 어떻게 실행하는지 항상 체크해. 서브쿼리가 여러 번 실행된다면, 최적화를 고민해봐.
CTE랑 비교해보기. 어떤 경우엔 WITH (Common Table Expressions)를 쓰는 게 더 빠르고 읽기 쉬울 수도 있어. 이건 다음 강의에서 다룰게 :P
서브쿼리, HAVING, GROUP BY 조합하기
HAVING에서 서브쿼리를 쓰면, 집계값, 평균, 다른 메트릭을 동시에 고려하는 복잡한 필터도 만들 수 있어. 이걸로 실제 데이터에서 흥미로운 인사이트를 찾을 수 있지.
예시: 학과별 평균 점수와 학생 수 비교
예를 들어, 이런 학과를 뽑고 싶다고 해보자:
- 평균 점수가 전체 평균보다 높고,
- 학생 수가 평균이 가장 낮은 학과보다 많아야 해.
이게 students 테이블이야:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
| Oleg | 60 | History |
쿼리:
SELECT department, AVG(grade) AS avg_grade, COUNT(*) AS student_count
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND COUNT(*) > (
SELECT COUNT(*)
FROM students
GROUP BY department
ORDER BY AVG(grade)
LIMIT 1
);
이 쿼리는 HAVING과 GROUP BY에서 서브쿼리를 조합해서 여러 기준으로 분석할 수 있다는 걸 보여줘. 결과:
| department | avg_grade | student_count |
|---|---|---|
| Physics | 82.5 | 2 |
| Math | 92.5 | 2 |
History 학과는 평균 점수도 제일 낮고 학생 수도 제일 적어서 결과에 안 나와. Physics랑 Math는 평균 점수도 높고 학생 수도 많아.
흔한 실수와 예방법
NULL 관련 실수. 데이터에 NULL이 있으면, HAVING의 서브쿼리가 예상치 못한 결과를 줄 수 있어. 이런 경우엔 COALESCE로 처리해:
SELECT AVG(grade)
FROM students
WHERE grade IS NOT NULL;
서브쿼리에서 불필요한 데이터. 서브쿼리가 너무 많은 결과를 반환하면 성능이 떨어져. 항상 서브쿼리 조건을 명확히 해.
실행 순서 오해. HAVING은 그룹화 후에 실행되고, 서브쿼리는 메인 쿼리보다 먼저 실행될 수도 있다는 걸 기억해.
인덱스 없음. 서브쿼리에 쓰이는 컬럼에 인덱스가 없으면 쿼리가 엄청 느려질 수 있어.
HAVING에서 서브쿼리를 쓰면 집계 레벨에서 데이터 분석이 훨씬 다양해져. 복잡한 조건으로 그룹을 필터링하고, 그룹 간 결과를 비교하고, 복잡한 분석 쿼리도 만들 수 있지. 축하해, 이제 이걸 실제 프로젝트에 쓸 준비가 된 거야!
GO TO FULL VERSION