Type something to search...

10 SQL SELECT 세부 명령어 실습 2

1. 인트로

WHERE 절의 심화 기능인 SubQuery(서브쿼리), LIMIT & BETWEEN(리밋 앤 비트윈)을 익힌다. 이 기능들은 복잡한 조건의 데이터를 정밀하게 추출할 때 사용된다.


2. 실습 DB 준비 (9차시 webtoon.db 이어서 사용)

1
import sqlite3
2
import pandas as pd
3
4
conn = sqlite3.connect("webtoon.db")
5
cur = conn.cursor()
6
7
# 데이터 확인
8
df = pd.read_sql("SELECT * FROM webtoons", conn)
9
print(df[["id","title","genre","score","views"]])

3. WHERE 절에서의 SubQuery(서브쿼리) 활용 실습

3.1. SubQuery(서브쿼리)란?

쿼리(Query(쿼리)) 안에 또 다른 쿼리를 넣는 방식이다. 괄호 () 안의 쿼리가 먼저 실행되고, 그 결과를 바깥 쿼리에서 사용한다.

1
# 평균 점수보다 높은 웹툰 목록
2
df = pd.read_sql("""
3
SELECT title, score
4
FROM webtoons
5
WHERE score > (SELECT AVG(score) FROM webtoons)
6
ORDER BY score DESC
7
""", conn)
8
print("=== 평균 점수 초과 웹툰 ===")
9
print(df)

코드 설명:

  • 5행: (SELECT AVG(score) FROM webtoons) — 먼저 전체 평균 점수를 계산한다.
  • 5행: 바깥 WHERE 절에서 그 평균값보다 높은 행만 가져온다.
  • AVG()는 평균을 계산하는 집계 함수이다.

3.2. 최고 조회수 웹툰 조회

1
# 조회수가 가장 높은 웹툰의 정보 조회
2
cur.execute("""
3
SELECT title, author, views
4
FROM webtoons
5
WHERE views = (SELECT MAX(views) FROM webtoons)
6
""")
7
top = cur.fetchone()
8
print(f"최고 조회수 웹툰: {top[0]} ({top[1]}) - {top[2]:,}회")

코드 설명:

  • 5행: MAX(views) — views 중 가장 큰 값을 구하는 서브쿼리이다.
  • 바깥 WHERE에서 그 최대값과 일치하는 행을 가져온다.
  • 8행: {top[2]:,} — 숫자에 천 단위 콤마를 추가하여 출력한다.

3.3. 카카오페이지 웹툰 중 평균보다 높은 점수

1
df = pd.read_sql("""
2
SELECT title, platform, score
3
FROM webtoons
4
WHERE platform = '카카오페이지'
5
AND score > (
6
SELECT AVG(score)
7
FROM webtoons
8
WHERE platform = '카카오페이지'
9
)
10
ORDER BY score DESC
11
""", conn)
12
print("=== 카카오페이지 평균 초과 웹툰 ===")
13
print(df)

코드 설명:

  • 4행: 먼저 platform이 ‘카카오페이지’인 것만 필터링한다.
  • 5~9행: 서브쿼리에서 카카오페이지 웹툰들의 평균 점수를 구한다.
  • 같은 플랫폼 내 평균보다 점수가 높은 웹툰만 결과로 나온다.

4. LIMIT & BETWEEN(리밋 앤 비트윈) 명령어의 이해 및 실습

4.1. LIMIT(리밋) & OFFSET(오프셋)

1
# 상위 3개만 가져오기
2
df = pd.read_sql(
3
"SELECT title, score FROM webtoons ORDER BY score DESC LIMIT 3",
4
conn
5
)
6
print("=== TOP 3 ===")
7
print(df)
1
# 4위~6위 (OFFSET으로 앞 3개를 건너뛴다)
2
df = pd.read_sql(
3
"SELECT title, score FROM webtoons ORDER BY score DESC LIMIT 3 OFFSET 3",
4
conn
5
)
6
print("=== 4위~6위 ===")
7
print(df)

코드 설명:

  • LIMIT 3 — 결과의 앞에서 3개만 가져온다.
  • OFFSET 3 — 앞의 3개를 건너뛰고 그 다음부터 가져온다.
  • 리액트 앱의 페이지네이션(Pagination(페이지네이션)) 기능에 핵심적으로 사용된다.
1
# 페이지네이션 함수 예시
2
def get_page(page=1, per_page=3):
3
offset = (page - 1) * per_page
4
df = pd.read_sql(
5
f"SELECT title, score FROM webtoons ORDER BY score DESC LIMIT {per_page} OFFSET {offset}",
6
conn
7
)
8
return df
9
10
print("=== 1페이지 ===")
11
print(get_page(1))
12
print("=== 2페이지 ===")
13
print(get_page(2))

코드 설명:

  • 2행: offset = (page - 1) * per_page — 1페이지는 0번째부터, 2페이지는 3번째부터 시작한다.
  • 이 함수가 리액트 앱의 페이지네이션 API 핵심 로직이다.

4.2. BETWEEN(비트윈) — 범위 검색

1
# 점수 9.0 이상 9.6 이하 웹툰
2
df = pd.read_sql("""
3
SELECT title, score
4
FROM webtoons
5
WHERE score BETWEEN 9.0 AND 9.6
6
ORDER BY score DESC
7
""", conn)
8
print("=== 점수 9.0~9.6 ===")
9
print(df)

코드 설명:

  • 5행: BETWEEN 9.0 AND 9.6 — 9.0 이상 9.6 이하(양쪽 포함)인 값을 가져온다.
  • WHERE score >= 9.0 AND score <= 9.6과 동일하지만 더 간결하다.
1
# NOT BETWEEN — 범위 밖의 값
2
df = pd.read_sql("""
3
SELECT title, views
4
FROM webtoons
5
WHERE views NOT BETWEEN 20000000 AND 40000000
6
ORDER BY views DESC
7
""", conn)
8
print("=== 2000만~4000만 조회수 제외 ===")
9
print(df)

코드 설명:

  • NOT BETWEEN — 범위 안에 포함되지 않는 값을 가져온다.

5. 복합 실전 실습

1
# 조회수 TOP 5 중 점수 9.0 이상인 웹툰
2
df = pd.read_sql("""
3
SELECT title, score, views
4
FROM webtoons
5
WHERE id IN (
6
SELECT id FROM webtoons ORDER BY views DESC LIMIT 5
7
)
8
AND score >= 9.0
9
ORDER BY score DESC
10
""", conn)
11
print("=== 조회수 TOP5 & 점수 9.0+ ===")
12
print(df)

코드 설명:

  • 5~7행: 서브쿼리로 조회수 상위 5개의 id 목록을 먼저 구한다.
  • 4행: 그 id 목록 안에 포함된 웹툰만 필터링한다.
  • 8행: 그중 점수 9.0 이상인 것만 최종 결과로 가져온다.

6. 활용 Plus — 리액트 무한 스크롤 구현

리액트 앱의 **무한 스크롤(Infinite Scroll(인피니트 스크롤))**은 LIMIT + OFFSET 조합으로 구현된다.

1
# 스크롤할 때마다 다음 5개씩 불러오기
2
def load_more(offset=0, limit=5):
3
cur.execute("""
4
SELECT title, score, views
5
FROM webtoons
6
ORDER BY views DESC
7
LIMIT ? OFFSET ?
8
""", (limit, offset))
9
return cur.fetchall()
10
11
# 첫 번째 로드 (0번부터 5개)
12
batch1 = load_more(offset=0)
13
print("첫 번째:", batch1)
14
15
# 두 번째 로드 (5번부터 5개)
16
batch2 = load_more(offset=5)
17
print("두 번째:", batch2)

코드 설명:

  • 2행: offsetlimit을 인자로 받는다.
  • 7행: ? 플레이스홀더로 LIMIT과 OFFSET 값을 안전하게 전달한다.
  • 리액트에서 스크롤 이벤트가 발생할 때마다 offset을 증가시켜 다음 데이터를 로드한다.

7. 문제풀기

SQL 문장 안에 포함된 또 다른 SQL 문장이다. 괄호 안의 쿼리가 먼저 실행되고, 그 결과를 바깥 쿼리에서 조건 값으로 사용한다.

결과를 정렬한 뒤 앞의 10개를 건너뛰고, 11번째부터 5개를 가져온다. 3페이지(페이지당 5개)의 데이터를 가져올 때 사용하는 패턴이다.

WHERE 컬럼 >= 10 AND 컬럼 <= 20 BETWEEN은 양쪽 경계값을 포함한다.

OFFSET = (3 - 1) * 4 = 8 LIMIT 4 OFFSET 8