SQL SELECT 세부 명령어 실습 2
코드 블록의 Try it Yourself 버튼으로 직접 실행할 수 있다.
구문
1. 인트로
WHERE 절의 심화 기능인 SubQuery(서브쿼리), LIMIT & BETWEEN(리밋 앤 비트윈)을 익힌다. 이 기능들은 복잡한 조건의 데이터를 정밀하게 추출할 때 사용된다.
2. 실습 DB 준비 (9차시 webtoon.db 이어서 사용)
import sqlite3
import pandas as pd
conn = sqlite3.connect("webtoon.db")
cur = conn.cursor()
# 데이터 확인
df = pd.read_sql("SELECT * FROM webtoons", conn)
print(df[["id","title","genre","score","views"]])
3. WHERE 절에서의 SubQuery(서브쿼리) 활용 실습
3.1. SubQuery(서브쿼리)란?
쿼리(Query(쿼리)) 안에 또 다른 쿼리를 넣는 방식이다.
괄호 () 안의 쿼리가 먼저 실행되고, 그 결과를 바깥 쿼리에서 사용한다.
# 평균 점수보다 높은 웹툰 목록
df = pd.read_sql("""
SELECT title, score
FROM webtoons
WHERE score > (SELECT AVG(score) FROM webtoons)
ORDER BY score DESC
""", conn)
print("=== 평균 점수 초과 웹툰 ===")
print(df)
코드 설명:
- 5행:
(SELECT AVG(score) FROM webtoons)— 먼저 전체 평균 점수를 계산한다. - 5행: 바깥 WHERE 절에서 그 평균값보다 높은 행만 가져온다.
AVG()는 평균을 계산하는 집계 함수이다.
3.2. 최고 조회수 웹툰 조회
# 조회수가 가장 높은 웹툰의 정보 조회
cur.execute("""
SELECT title, author, views
FROM webtoons
WHERE views = (SELECT MAX(views) FROM webtoons)
""")
top = cur.fetchone()
print(f"최고 조회수 웹툰: {top[0]} ({top[1]}) - {top[2]:,}회")
코드 설명:
- 5행:
MAX(views)— views 중 가장 큰 값을 구하는 서브쿼리이다. - 바깥 WHERE에서 그 최대값과 일치하는 행을 가져온다.
- 8행:
{top[2]:,}— 숫자에 천 단위 콤마를 추가하여 출력한다.
3.3. 카카오페이지 웹툰 중 평균보다 높은 점수
df = pd.read_sql("""
SELECT title, platform, score
FROM webtoons
WHERE platform = '카카오페이지'
AND score > (
SELECT AVG(score)
FROM webtoons
WHERE platform = '카카오페이지'
)
ORDER BY score DESC
""", conn)
print("=== 카카오페이지 평균 초과 웹툰 ===")
print(df)
코드 설명:
- 4행: 먼저 platform이 '카카오페이지'인 것만 필터링한다.
- 5~9행: 서브쿼리에서 카카오페이지 웹툰들의 평균 점수를 구한다.
- 같은 플랫폼 내 평균보다 점수가 높은 웹툰만 결과로 나온다.
4. LIMIT & BETWEEN(리밋 앤 비트윈) 명령어의 이해 및 실습
4.1. LIMIT(리밋) & OFFSET(오프셋)
# 상위 3개만 가져오기
df = pd.read_sql(
"SELECT title, score FROM webtoons ORDER BY score DESC LIMIT 3",
conn
)
print("=== TOP 3 ===")
print(df)
# 4위~6위 (OFFSET으로 앞 3개를 건너뛴다)
df = pd.read_sql(
"SELECT title, score FROM webtoons ORDER BY score DESC LIMIT 3 OFFSET 3",
conn
)
print("=== 4위~6위 ===")
print(df)
코드 설명:
LIMIT 3— 결과의 앞에서 3개만 가져온다.OFFSET 3— 앞의 3개를 건너뛰고 그 다음부터 가져온다.- 리액트 앱의 페이지네이션(Pagination(페이지네이션)) 기능에 핵심적으로 사용된다.
# 페이지네이션 함수 예시
def get_page(page=1, per_page=3):
offset = (page - 1) * per_page
df = pd.read_sql(
f"SELECT title, score FROM webtoons ORDER BY score DESC LIMIT {per_page} OFFSET {offset}",
conn
)
return df
print("=== 1페이지 ===")
print(get_page(1))
print("=== 2페이지 ===")
print(get_page(2))
코드 설명:
- 2행:
offset = (page - 1) * per_page— 1페이지는 0번째부터, 2페이지는 3번째부터 시작한다. - 이 함수가 리액트 앱의 페이지네이션 API 핵심 로직이다.
4.2. BETWEEN(비트윈) — 범위 검색
# 점수 9.0 이상 9.6 이하 웹툰
df = pd.read_sql("""
SELECT title, score
FROM webtoons
WHERE score BETWEEN 9.0 AND 9.6
ORDER BY score DESC
""", conn)
print("=== 점수 9.0~9.6 ===")
print(df)
코드 설명:
- 5행:
BETWEEN 9.0 AND 9.6— 9.0 이상 9.6 이하(양쪽 포함)인 값을 가져온다. WHERE score >= 9.0 AND score <= 9.6과 동일하지만 더 간결하다.
# NOT BETWEEN — 범위 밖의 값
df = pd.read_sql("""
SELECT title, views
FROM webtoons
WHERE views NOT BETWEEN 20000000 AND 40000000
ORDER BY views DESC
""", conn)
print("=== 2000만~4000만 조회수 제외 ===")
print(df)
코드 설명:
NOT BETWEEN— 범위 안에 포함되지 않는 값을 가져온다.
5. 복합 실전 실습
# 조회수 TOP 5 중 점수 9.0 이상인 웹툰
df = pd.read_sql("""
SELECT title, score, views
FROM webtoons
WHERE id IN (
SELECT id FROM webtoons ORDER BY views DESC LIMIT 5
)
AND score >= 9.0
ORDER BY score DESC
""", conn)
print("=== 조회수 TOP5 & 점수 9.0+ ===")
print(df)
코드 설명:
- 5~7행: 서브쿼리로 조회수 상위 5개의 id 목록을 먼저 구한다.
- 4행: 그 id 목록 안에 포함된 웹툰만 필터링한다.
- 8행: 그중 점수 9.0 이상인 것만 최종 결과로 가져온다.
6. 활용 Plus — 리액트 무한 스크롤 구현
리액트 앱의 **무한 스크롤(Infinite Scroll(인피니트 스크롤))**은 LIMIT + OFFSET 조합으로 구현된다.
# 스크롤할 때마다 다음 5개씩 불러오기
def load_more(offset=0, limit=5):
cur.execute("""
SELECT title, score, views
FROM webtoons
ORDER BY views DESC
LIMIT ? OFFSET ?
""", (limit, offset))
return cur.fetchall()
# 첫 번째 로드 (0번부터 5개)
batch1 = load_more(offset=0)
print("첫 번째:", batch1)
# 두 번째 로드 (5번부터 5개)
batch2 = load_more(offset=5)
print("두 번째:", batch2)
코드 설명:
- 2행:
offset과limit을 인자로 받는다. - 7행:
?플레이스홀더로 LIMIT과 OFFSET 값을 안전하게 전달한다. - 리액트에서 스크롤 이벤트가 발생할 때마다 offset을 증가시켜 다음 데이터를 로드한다.
7. 문제풀기
Q1. SubQuery(서브쿼리)란 무엇인가?
SQL 문장 안에 포함된 또 다른 SQL 문장이다. 괄호 안의 쿼리가 먼저 실행되고, 그 결과를 바깥 쿼리에서 조건 값으로 사용한다.
Q2. LIMIT 5 OFFSET 10의 의미는?
결과를 정렬한 뒤 앞의 10개를 건너뛰고, 11번째부터 5개를 가져온다. 3페이지(페이지당 5개)의 데이터를 가져올 때 사용하는 패턴이다.
Q3. BETWEEN 10 AND 20을 AND 조건식으로 변환하시오.
WHERE 컬럼 >= 10 AND 컬럼 <= 20
BETWEEN은 양쪽 경계값을 포함한다.
Q4. 페이지네이션에서 3페이지(페이지당 4개)의 OFFSET 값은?
OFFSET = (3 - 1) * 4 = 8
LIMIT 4 OFFSET 8