단원 홈

Ⅰ. 데이터 과학의 이해6차시

표 한 장이면
안 되나

같은 사실을 여러 곳에 적으면

따릉이 대여 기록 열두 줄이 담긴 표 한 장. 누군가 대여소 이름을 고쳤는데 다섯 줄 가운데 세 줄만 고쳐졌습니다. 이제 "망원역 1번출구 앞 대여소 이용은 몇 건?"이라는 하나의 물음에 표가 두 가지 답을 냅니다. 표를 나누고, 키로 잇고, 규칙으로 지키는 일 — 데이터베이스를 직접 만들어 봅니다.

  • [12데과01-03]
  • 50분네 정거장
  • 새 도구SQLite(sqlite3)
  • 자료가상 대여 12건 · 대여소 3곳
Ⅰ 단원 지도6 / 9차시
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9

학습 목표

  1. 개념에서

    데이터셋과 데이터베이스가 어떻게 다른지, 되풀이가 왜 모순을 부르는지 말할 수 있다.

  2. 손으로

    표 한 장을 두 데이터셋으로 나누고 기본키·외래키로 이어 SQL 로 물을 수 있다.

  3. 확인에서

    무결성 규칙이 막은 실수를 짝짓고, 새 자료를 표 몇 장으로 나눌지 스스로 설계할 수 있다.

1
여는 장면 · 5분

하나의 물음, 표 한 장이 내놓은 두 답

따릉이 운영팀은 대여 기록을 표 한 장으로 관리합니다. 대여 한 건이 한 줄이고, 그 줄에 대여소 이름·자치구·거치대 수까지 함께 적습니다. 이틀 치 열두 줄이 쌓였습니다.

이틀째 아침, 인턴이 대여소 이름의 띄어쓰기를 바로잡았습니다 — 망원역 1번출구 앞 을 망원역 1번 출구 앞 으로요. 그때 장부에 있던 줄은 빠짐없이 고쳤습니다. 그런데 그날 오후, 같은 대여소에서 두 건이 더 들어왔고 그 두 줄에는 옛 이름이 찍혔습니다.

이제 팀장이 묻습니다. "망원역 1번출구 앞 대여소는 이틀 동안 몇 번 쓰였지?" 같은 표를 보고도 2건 · 3건 · 5건 세 가지 답이 나옵니다.

같은 표세 가지 답
4광화문31번 출구21번출구32번출구
옛 이름으로 세면 2새 이름으로 세면 3

주황 막대 둘이 같은 102번 대여소다.

102번 대여소의 참값은 5건이다 — 표는 그 답을 내지 못한다. 자료: 가상 대여 12건

표 1운영팀의 표 한 장대여 12줄 × 7칸 · 인턴이 고친 뒤의 모습
대여ID대여일시대여소번호대여소명자치구거치대수이용시간
106-01 08:10102망원역 1번 출구 앞마포구1512
206-01 08:25103망원역 2번출구 앞마포구147
306-01 09:02102망원역 1번 출구 앞마포구1525
406-01 12:40301광화문역 2번출구 앞종로구209
506-01 13:05102망원역 1번 출구 앞마포구1531
606-01 18:20301광화문역 2번출구 앞종로구2014
706-02 07:55103망원역 2번출구 앞마포구1418
806-02 08:30102망원역 1번출구 앞마포구156
906-02 11:15301광화문역 2번출구 앞종로구2022
1006-02 17:45102망원역 1번출구 앞마포구1540
1106-02 19:10103망원역 2번출구 앞마포구1411
1206-02 21:30301광화문역 2번출구 앞종로구208

번호 칸(청록)은 다섯 줄 모두 102 로 같은데, 이름 칸은 세 줄만 고쳐졌습니다(주황). 번호는 기계가 붙인 값이라 아무도 손대지 않았고, 이름은 사람이 적는 값이라 손을 탔습니다. 자료: 가상 — ../data/demo_rentals_12.csv(대여 12건 · 대여소 3곳). 요점이 값이 아니라 구조라서 일부러 작게 만들었다.

먼저 예측

표 1 을 대여소명으로 묶어 세면 몇 묶음이 나올까요? 그리고 이런 일이 생기지 않게 하려면 표를 어떻게 바꿔야 할까요 — 한 문장으로 적어 두세요. 3정거장 「손으로」 미션 ①에서 여러분의 예측과 실제 장부를 맞대어 봅니다.

체크포인트 1

같은 사실을 여러 곳에 적어 두면, 한 곳만 고쳐지는 순간 표가 스스로 어긋난다는 것을 보았다.

다음 정거장 · 개념 15분 →
2
개념 · 15분

나누고, 가리키고, 규칙이 지킨다

2-1데이터셋과 데이터베이스 — 무엇이 다른가

데이터셋은 한 주제를 담은 표 하나입니다. 오늘의 표 1 이 데이터셋이고, 5차시에서 본 따릉이 이용 정보도 데이터셋이었습니다.

데이터베이스는 서로 관계가 있는 데이터셋들의 모음과, 그 관계를 대신 지켜 주는 관리 시스템(DBMS)을 함께 가리킵니다. 표를 여러 장으로 나누는 것만으로는 데이터베이스가 되지 않아요. 나누기 + 관계 + 그 관계를 지키는 규칙 셋이 있어야 합니다.

그래서 오늘은 세 걸음을 밟습니다 — ① 표 한 장이 어떻게 어긋나는지 보고, ② 두 데이터셋으로 나누어 키로 잇고, ③ 여럿이 함께 고쳐도 어긋나지 않게 규칙을 겁니다.

2-2되풀이가 모순을 부른다 — 갱신 이상

표 1 에서 대여소 정보 세 칸(대여소명·자치구·거치대수)은 대여 한 건마다 다시 적힙니다. 12줄 × 3칸 = 36칸 가운데 27칸이 되풀이예요. 되풀이는 자리를 차지하는 문제로 끝나지 않습니다 — 고쳐야 할 곳이 27군데 더 생긴다는 뜻이고, 그 가운데 한 곳이라도 빠지면 표가 갈라집니다.

그림 1같은 사실을 몇 곳에 적었나칸 하나 = 대여소 정보 한 칸 · 옅은 칸이 되풀이
① 한 장에 적으면대여 12줄마다 대여소 정보 3칸을 다시 적는다12 × 3 = 36칸이름자치구거치대110221033102430151026301710381029301101021110312301처음 적은 9칸되풀이 27칸 — 고칠 자리가 그만큼 늘었다 ② 두 장으로 나누면대여소 정보는 대여소 표에 한 번만 적고대여 줄에서는 번호로 가리킨다 — 3 × 3 = 9칸나누면이름자치구거치대102103301되풀이 0칸이름을 고칠 곳도 한 곳뿐이다대여 표에는 대여ID · 대여일시 ·대여소번호 · 이용시간 넷만 남는다 ① 한 장에 적으면대여 12줄마다 대여소 정보 3칸을 다시 적는다12 × 3 = 36칸이름자치구거치대110221033102430151026301710381029301101021110312301처음 적은 9칸되풀이 27칸 — 고칠 자리가 그만큼 늘었다 ② 두 장으로 나누면대여소 정보는 대여소 표에 한 번만 적고대여 줄에서는 번호로 가리킨다 — 3 × 3 = 9칸나누면이름자치구거치대102103301되풀이 0칸이름을 고칠 곳도 한 곳뿐이다대여 표에는 대여ID · 대여일시 ·대여소번호 · 이용시간 넷만 남는다

한 장에 적으면 고칠 곳이 최대 12군데, 나누면 한 군데입니다. 대여가 300만 건으로 늘어도 대여소 표는 그대로라, 이 차이는 데이터가 커질수록 벌어집니다. 자료: 가상 대여 12건 — 칸 수 36 · 27 · 9 는 실행 칸 ①·②가 찍는 값과 같다(원장 실측)

규칙같은 사실은 한 곳에만 적고, 나머지 자리에서는 그 한 곳을 가리킨다.

2-3키 — 이름 말고 번호로 가리킨다

표를 나누었으면 두 표를 다시 이을 손잡이가 있어야 합니다. 그 손잡이가 키입니다.

기본키(PRIMARY KEY)는 한 표 안에서 줄마다 다르고 비어 있으면 안 되는 칸입니다. 대여소 표에서는 대여소번호 가 기본키예요. 외래키(FOREIGN KEY)는 다른 표의 기본키를 가리키는 칸입니다. 대여 표의 대여소번호 가 대여소 표를 가리키는 외래키가 됩니다.

왜 이름이 아니라 번호일까요? 이름은 바뀌고(오늘 아침에 실제로 바뀌었습니다) 겹칠 수도 있습니다. 이름을 키로 쓰면 이름을 고치는 순간 연결이 끊어져요. 그래서 뜻이 없어서 바꿀 까닭도 없는 번호를 키로 씁니다.

Ⅰ-5 에서 가져옴대여소번호는 5차시에서 "숫자처럼 생겼지만 계산하면 안 되는 값"으로 만났던 칸입니다. 평균을 내면 뜻이 없었지요 — 뜻이 없다는 것이 바로 키의 자격입니다.

무엇으로가리키나
이름으로 가리키면대여소이름을 고치면 끊긴다번호로 가리키면대여소번호는 바뀌지 않는다
이름 → 끊긴다번호 → 남는다

도해: 대여 세 줄이 대여소 한 줄을 가리키는 모습

2-4여럿이 함께 쓸 때 — 사람의 조심 대신 규칙

나누고 이었어도, 창구·키오스크·인턴·담당자가 동시에 고치는 장부라면 또 어긋납니다. 그래서 데이터베이스는 표를 만들 때 규칙을 함께 적어 두고, 규칙을 어기는 입력을 거부합니다. 이 책에서는 규칙을 빗장이라 부릅니다 — 머리줄 칸 위에 가로로 밀어 채워 두는 막대로, 한 번 걸어 두면 누가 입력하든 그 칸을 지킵니다.

표 2빗장 셋 — 무엇을 막고, 어기면 무슨 말이 나오나
빗장거는 칸SQL 로 쓰면어기면 나오는 말
🔑 열쇠 빗장 대여소 표의 대여소번호PRIMARY KEY UNIQUE constraint failed: 대여소.대여소번호
✳ 빈칸 금지 빗장 대여소 표의 대여소명NOT NULL NOT NULL constraint failed: 대여소.대여소명
🔗 고리 빗장 대여 표의 대여소번호REFERENCES 대여소(대여소번호) FOREIGN KEY constraint failed

세 줄 모두 사람이 조심하는 대신 시스템이 거부하는 규칙입니다. 거부 문구는 실행 칸 ③과 시뮬레이터 3라운드에 글자 그대로 다시 나옵니다. 자료: SQLite 3.39.0 실측 문구(원장 브라우저 실행)

그런데 빗장 하나는 적어 두는 것만으로는 지켜지지 않습니다. SQLite 는 옛 파일과 어긋나지 않으려고 외래키 검사를 기본으로 꺼 둡니다. 연결할 때마다 PRAGMA foreign_keys = ON 으로 빗장 잠금 스위치를 켜야 🔗 고리 빗장이 일을 합니다. 꺼 둔 채로 돌리면 없는 대여소를 가리키는 대여가 오류 없이 들어가고, 두 표를 이을 때 그 줄이 조용히 빠집니다.

대여소 수 · 번호로 세느냐 이름으로 세느냐
3번호로 세면(곳)
vs
4이름으로 세면(묶음)
4광화문31번 출구21번출구32번출구

주황 막대 둘(3건 · 2건)이 같은 102번이다 — 되풀이한 이름 하나가 갈라져 대여소가 하나 늘었다.

대여소 정보 칸 · 한 장이냐 두 장이냐
36한 장에 적으면(칸)
→
9두 장으로 나누면(칸)
36칸9칸되풀이27 → 0

고칠 자리가 27군데 사라진다 — 되풀이 0.

103번 대여 · 같은 번호가 두 줄이면
3대여 표의 103번(건)
인데
6이으면(건)
대여 표의 103번3건이으면103이 두 줄이면6건

🔑 이 없으면 대여가 두 번씩 세어진다.

대여 행 수 · 스위치를 끄면
13대여 표(행)
vs
12이으면(행)
대여 표13행이으면이은 표12행한 행이 조용히 빠졌다

없는 대여소를 가리킨 한 줄이 조용히 빠진다(Ⅰ-7 예고).

자료: 가상 대여 12건 · 가상 장부 사건 대본 — 위 네 쌍의 숫자는 모두 실행 칸 ①~③과 시뮬레이터가 찍는 값이다(원장 브라우저 실측).

표를 나눌 때 묻는 네 가지 — 차례로

  1. 되풀이같은 사실이 몇 곳에 적혀 있나 — 한 곳만 고쳐질 수 있나
  2. 키줄마다 다르고 비지 않으며 바뀌지 않는 칸이 있나
  3. 고리다른 표를 가리키는 칸이 실제로 있는 값만 받도록 묶였나
  4. 스위치그 규칙이 적혀만 있나, 아니면 켜져 있나

넷 가운데 하나만 빠져도 표는 조용히 어긋납니다. 특히 넷째는 화면에 아무 말도 나오지 않아서 가장 늦게 발견됩니다 — 이을 때 행이 사라진 다음에요.

체크포인트 2

데이터셋과 데이터베이스의 차이, 갱신 이상, 기본키·외래키, 그리고 '켜야 지켜지는 규칙'을 말할 수 있다.

다음 정거장 · 손으로 20분 →
3
손으로 · 20분

표 한 장을 데이터베이스로 바꾼다

오늘의 장비

SQLite — 파일 하나로 도는 진짜 데이터베이스

지금까지는 표를 pandas 로 읽어 파이썬 안에서 다뤘습니다. 오늘은 표를 데이터베이스에 넣고 데이터베이스에게 SQL로 묻습니다. 파이썬에 들어 있는 sqlite3 가 그 데이터베이스예요 — 실행 칸에서는 디스크가 아니라 메모리(:memory:)에 만들고, 칸을 닫으면 사라집니다.

부르는 모양

import sqlite3

db = sqlite3.connect(":memory:")
db.execute("PRAGMA foreign_keys = ON")
db.execute("CREATE TABLE 대여소 (…)")
for 줄 in db.execute("SELECT …"):
    print(줄)

오늘 쓰는 SQL 넷

CREATE TABLE
표를 만든다 — 칸 이름·자료형과 빗장을 함께 적는다
INSERT INTO
줄을 넣는다 — 빗장을 어기면 여기서 거부된다
JOIN … ON
두 표를 키로 잇는다
GROUP BY
같은 값끼리 묶어 세거나 평균을 낸다

pandas 와 다른 셋

  1. 규칙이 표에 붙는다거부를 IntegrityError 로 돌려준다
  2. 여럿이 같이 쓴다한 사람의 실수를 들어오기 전에 막는다
  3. 물음이 글이다코드가 아니라 SELECT … FROM … WHERE 문장

대가 — 표를 만들기 전에 구조를 먼저 정해야 합니다. 나중에 바꾸려면 이미 쌓인 줄을 옮겨야 해요.

1

이틀 치 장부를 팀장으로 지키고, 같은 숫자를 코드로 다시 잰다

11분

여는 장면에 적어 둔 내 예측입니다. 이번에는 조건이 하나 다릅니다 — 인턴이 그때 있던 줄을 빠짐없이 고쳤는데도 이틀 뒤 이름으로 묶으면 몇 묶음이 될까요?

실험실 같은 이틀을 구조만 바꿔 세 번 삽니다. 1라운드는 표 한 장, 2라운드는 두 장으로 가르기, 3라운드는 머리줄에 빗장 걸기. 라운드마다 예측을 잠가야 시계가 흐르고, 전광판이 장부의 답과 실제로 일어난 일을 나란히 보여 줍니다. 도전 셋을 풀면 카드가 스스로 닫힙니다.

시뮬레이터

넷이 고치는 장부

같은 사실이 여러 곳에 적혀 있으면, 여럿이 고치는 사이 반드시 갈라진다

  1. 1라운드를 고르고 구조를 정한다(1라운드는 표 한 장 그대로)
  2. 2예측 카드를 하나 골라 잠근다 — 잠가야 시계가 흐른다
  3. 3☕ 로 이틀을 흘리고 전광판이 빨개지는 자리를 찾는다
라운드 — 같은 이틀, 다른 구조
내 예측 ① — 이름으로 묶으면 몇 묶음?
1라운드의 손
내 예측 ② — 이틀 뒤 마포구는 몇 건?
가르기 — 무엇이 같으면 한 장으로 접나
내 예측 ③ — 잘못 셋 가운데 몇 건이 샐까?
🔑 열쇠 빗장을 어느 칸에
나머지 빗장과 스위치
시계 — 6/1 08:00 → 6/3 10:00
1라운드 · 표 한 장예측을 잠그면 시계가 흐른다

장부의 답 = 지금 장부에 적힌 것만 보고 센 값 · 실제로 일어난 일 = 이틀 동안 진짜로 있었던 일 · 둘이 다른 동안이 모순 시간이다

별 — 이틀을 끝냈을 때 모순 시간 2시간 이하면 ★★, 5시간 이하면 ★. 라운드마다 따로 센다

모순 시간0.0시간
지금6/1 08:00
대여 기록0줄
이으면—행
대여소0곳
샌 잘못—
도전 1

1라운드를 모순 시간 0으로 끝내라. 두 걸음이 필요하다 — 옛 이름이 어디서 계속 나오는지 막고, 이미 갈라진 줄을 한 번 정리하라.

도전 2

2라운드에서 세 가지 접기를 모두 해 보고, 몇 장이 되는지 견주어라. 왜 번호로 접어야만 이틀을 흘릴 수 있는가?

도전 3

3라운드에서 잘못 셋을 모두 튕겨라. 빗장을 다 걸었는데도 한 건이 샌다면, 빠진 것은 빗장이 아니다.

자료: 가상 — ../data/demo_rentals_12.csv 의 대여 12건 + 사건 대본(인턴의 찾아 바꾸기 · 103번 재등록 · 이름 빈 104번 등록 · 대여소 999 오타). 라운드별 숫자와 거부 문구는 원장이 같은 대본을 sqlite3(3.39.0)로 브라우저에서 돌려 잰 값이고, 쪽이 열릴 때 판이 그 값과 스스로 대조한다.

기본 코드 ①은 채워져 있습니다. 그대로 ▶ 실행해 되풀이 칸 수와 이름으로 묶은 묶음을 확인하세요. 시뮬레이터 1라운드가 끝났을 때의 장부와 같은 네 묶음이 나옵니다.

도전 WHERE 절의 대여ID <= 5 를 대여ID <= 8 로 바꿔 보세요. 인턴이 조금 더 늦게 고쳤다면 묶음이 어떻게 달라지나요? 묶음 수가 줄어드는 것이 문제가 사라진 것인지 생각해 보세요.

탐구 이 표에서 대여소 이름을 완전히 통일하려면 몇 군데를 고쳐야 할까요? 대여가 300만 건이라면 몇 군데일까요? (확인 문제 4에서 식으로 셉니다)

"망원역 1번출구 앞 이용은 몇 건?"에 표가 내야 할 답은 이다. 표가 두 답을 낸 까닭은 때문이고, 인턴이 빠짐없이 고쳤는데도 다시 갈라진 까닭은 때문이다.

확인 문제 1에 적어 제출 →
2

두 데이터셋으로 나누고, 번호로 잇는다

3.5분

이번에는 대여소 정보를 대여소 표에 한 번만 적고, 대여 표에서는 번호로만 가리킵니다. 그리고 이름을 대여소 표에서 한 번만 고칩니다 — 이제 102번은 몇 건으로 나올까요?

기본 빈칸 ①에 두 표를 잇는 조건을 채우고 ▶ 실행합니다. CREATE TABLE 두 줄에서 PRIMARY KEY 와 REFERENCES 를 찾아 보세요 — 시뮬레이터 3라운드에서 🔑 과 🔗 이 번역된 바로 그 두 줄입니다.

빈칸을 그대로 두고 ▶ 을 누르면 sqlite3.OperationalError: near "?": syntax error 가 납니다. 파이썬이 아니라 데이터베이스가 낸 오류예요 — SQL 도 문법이 있고, ????? 자리에서 문장을 읽다 멈춘 것입니다. 막히면 🔑 정답 코드 넣기로 끝까지 돌려 본 뒤, ↺ 처음 코드로 를 눌러 다시 채워 보세요.

도전 ON 의 조건을 r.대여소명 = s.대여소명 으로 바꾸려면 무엇이 더 필요한가요? 대여 표에는 이름 칸이 아예 없습니다 — 나누는 순간 이름으로 이을 길이 사라졌다는 뜻입니다.

탐구 ORDER BY s.대여소번호 를 ORDER BY s.대여소명 으로 바꾸면 차례가 어떻게 달라지나요? 차례가 바뀌어도 건수는 그대로인 까닭은 무엇일까요?

두 표를 로 이은 까닭은, 그 칸이 이기 때문이다. 이름으로 이었다면 했을 것이다.

확인 문제 2에 적어 제출 →
3

규칙이 막는다 — 켜져 있다면

5.5분

이제 여럿이 고치는 장부에서 일어난 두 가지 잘못을 데이터베이스에 그대로 넣어 봅니다 — ① 같은 번호로 대여소를 또 등록 ② 대여소 표에 없는 번호로 빌린 기록. 들어갈까요, 거부될까요? 둘 다 짐작해 두세요.

기본 빈칸 ②에 대여소 표에 없는 번호를 하나(무엇이든), 빈칸 ③에 무엇으로 묶을지를 채우고 ▶ 실행합니다. 거부 문구를 표 2 와 맞대어 보세요.

도전 맨 위 PRAGMA foreign_keys = ON 줄 앞에 # 를 붙이고 다시 돌리세요. 둘째 실수가 '들어갔다'로 바뀌는데도 [E]의 마포구는 8건 18.8분 그대로이고, 맨 아래 줄만 대여 표 13행 · 이으면 12행이 됩니다 — 한 줄이 이을 때 조용히 빠진 것입니다(7차시 예고).

탐구 칸 끝의 주석처럼, 학교 도서관 대출 기록을 나눈다면 표 몇 장으로 나누고 키는 무엇으로 삼겠습니까? 방금 건 빗장 둘(PRIMARY KEY · REFERENCES)을 여러분의 표에 직접 세워 보세요 — 답은 확인 문제 5에 적습니다.

UNIQUE constraint failed 는 빗장이, FOREIGN KEY constraint failed 는 빗장이 막은 것이다. 그런데 둘째 빗장은 해야만 일을 하고, 꺼 두면 .

확인 문제 3에 적어 제출 →
체크포인트 3

표 한 장을 두 데이터셋으로 나누어 키로 잇고, 빗장 셋으로 여러 사람의 실수를 막고, 규칙을 켜는 것까지 직접 해 보았다.

다음 정거장 · 정리·확인 10분 →
4
정리·확인 · 10분

오늘의 탐험 일지

  1. 찾은 것

    같은 사실을 여러 곳에 적으면(36칸 중 27칸이 되풀이) 한 곳만 고쳐지는 일이 생기고, 그 순간 표는 하나의 물음에 두 답을 낸다 — 대여소 3곳이 이름으로는 4묶음이 되었다.

  2. 도구의 한계

    표를 나누는 것만으로는 데이터베이스가 아니다. 규칙 없이 나누면 13행이 이을 때 15행이 되고, 규칙을 적어 두어도 스위치를 켜지 않으면 13행이 이을 때 12행이 된다 — 오류 한 줄 없이.

  3. 보고의 규칙

    표를 설계할 때 넷을 차례로 묻는다 — 되풀이 · 키 · 고리 · 스위치. 그리고 "몇 건인가"를 보고할 때는 무엇으로 묶어 세었는지(이름인가 번호인가)를 함께 적는다.

✍️ 문제마다 답을 쓰고 제출하기를 누르세요. 제출하면 모범 답안이 열리고, 제출한 답은 선생님께 전달됩니다.

1. 코드 ①의 [B] 출력(네 묶음 — 망원역 1번 출구 앞 3건 + 망원역 1번출구 앞 2건)을 보고, "망원역 1번출구 앞 대여소 이용은 몇 건인가"에 어떤 답을 내야 하는지 쓰고, 이런 일이 생긴 구조적인 까닭을 쓰시오.
📖 모범 답안

5건이다. 102번 대여소에서 일어난 대여가 이틀 동안 다섯 건이고, 이름이 둘로 갈렸을 뿐 대여소는 한 곳이다.

구조적인 까닭은 같은 사실(대여소명)을 대여 한 건마다 되풀이해 적었기 때문이다. 대여소 정보 세 칸이 12줄에 되풀이되어 36칸 가운데 27칸이 중복이었고, 고칠 자리가 여러 군데였기 때문에 일부만 고쳐지는 일(갱신 이상)이 생길 수 있었다. 그 순간 이름으로 묶으면 같은 대여소가 두 묶음으로 갈라진다.

덧붙여, 시뮬레이터 1라운드처럼 빠짐없이 고쳐도 다시 갈라진다. 고친 뒤에도 102번 키오스크가 단말에 박힌 옛 이름으로 두 줄을 더 찍었기 때문이다 — 되풀이가 남아 있는 한 '한 번 잘 고치기'로는 끝나지 않는다.

2. 코드 ②에서 두 표를 '대여소명'이 아니라 '대여소번호'로 이은 까닭을 쓰시오. 이름을 키로 썼다면 코드 ②의 UPDATE 한 줄 뒤에 무슨 일이 일어났을지도 함께 쓰시오.
📖 모범 답안

이름은 바뀔 수 있고 서로 겹칠 수도 있다. 오늘 아침에도 띄어쓰기 때문에 실제로 바뀌었다. 번호는 줄마다 다르고 비지 않으며 바꾸지 않기로 정한 기본키라서, 이름을 고쳐도 연결이 끊기지 않는다. 실제로 대여소 표에서 이름을 한 번만 고쳤는데도 102번 5건이 그대로 한 묶음으로 이어졌다.

이름을 키로 썼다면, 대여소 표의 이름을 '망원역 1번 출구 앞'으로 고치는 순간 대여 표에 남아 있던 옛 이름들이 가리킬 곳을 잃는다. 이으면 102번 다섯 건이 0건이 되거나(빠짐), 옛 이름을 그대로 둔 줄만 남아 다시 갈라진다. 키는 뜻이 없어야 한다 — 뜻이 있으면 뜻이 바뀔 때 키도 바뀐다.

3. 코드 ③ [D]의 거부 메시지 두 줄을 각각 어떤 규칙이 막았는지 아래 표에 짝짓고, 그 규칙이 없었다면 한 달 뒤 분석에 어떤 문제가 생기는지 쓰시오. 또 이름 없는 대여소를 막으려면 어떤 규칙을 어디에 두어야 하는지도 쓰시오.
거부 메시지막은 규칙(빗장)없었다면 생길 문제
UNIQUE constraint failed: 대여소.대여소번호
FOREIGN KEY constraint failed
📖 모범 답안

표 — UNIQUE constraint failed: 대여소.대여소번호 ← 🔑 기본키(PRIMARY KEY). 없었다면 같은 번호의 대여소가 두 줄이 되고, 이을 때 그 번호의 대여가 두 번씩 세어진다 — 시뮬레이터 2라운드에서 대여 13행이 이으면 15행이 되고 103번 3건이 6건으로, 마포구가 8건 18.8분에서 11건 16.9분으로 틀어졌다.

FOREIGN KEY constraint failed ← 🔗 외래키(REFERENCES 대여소(대여소번호)). 없었다면 대여소 표에 없는 번호의 대여가 그대로 들어가고, 이을 때 그 줄이 오류 없이 조용히 빠진다 — 도전에서 본 대여 13행 · 이으면 12행이 그것이다. 한 달 뒤 분석에서는 건수가 모자란데 왜 모자란지 알 수 없다.

이름 없는 대여소 — 대여소 표의 대여소명 칸에 ✳ NOT NULL 을 둔다. 그러면 NOT NULL constraint failed: 대여소.대여소명 으로 거부된다. 🔑 이 ✳ 을 대신하지 못한다는 것도 기억할 것 — 시뮬레이터에서 대여소명에 🔑 만 걸면 이름이 빈 104번이 그대로 들어간다.

그리고 SQLite 는 외래키 검사가 기본으로 꺼져 있다. 연결할 때마다 PRAGMA foreign_keys = ON 을 해야 🔗 이 일을 한다. 규칙은 적는 것이 아니라 켜는 것이다.

4. 손셈: 대여 300만 건 · 대여소 2,700곳을 표 한 장에 담으면, 대여소 정보 세 칸(대여소명·자치구·거치대수) 가운데 되풀이 칸은 몇 개이고, 두 표로 나누면 대여소 칸은 몇 개인가? 코드 ① [A]가 쓴 식을 그대로 써서 계산하고, 데이터가 커질수록 데이터베이스가 필요해지는 까닭을 '저장'과 '고치기' 두 면에서 쓰시오.
📖 모범 답안

식은 [A]와 같다 — 되풀이 칸 = 대여 수 × 3 − 대여소 수 × 3. 3,000,000 × 3 − 2,700 × 3 = 9,000,000 − 8,100 = 8,991,900개. 두 표로 나누면 대여소 표의 칸은 2,700 × 3 = 8,100개뿐이다(되풀이 0).

저장 — 같은 값을 수백만 번 적는 낭비다. 대여소 하나의 정보를 300만 번 적을 까닭이 없다.

고치기 — 이름 하나를 바꾸려면 수백만 곳을 빠짐없이 고쳐야 한다. 오늘 12줄짜리 표에서도 다섯 줄 가운데 세 줄만 고쳐졌는데, 여러 사람이 동시에 고치는 수백만 줄에서 빠짐없이 고쳐질 가능성은 사실상 없다. 나누면 한 곳만 고치면 된다.

덧붙임 — 300만 건 · 2,700곳은 자릿수만 보이려고 잡은 가상 규모다. 실제 서울 공공자전거 대여소는 2,789곳(대여소 정보 2026년 6월)이다.

5. 적용: 학교 도서관이 대출 기록을 표 한 장(학번 · 이름 · 반 · 책 제목 · 저자 · 대출일)으로 관리한다. 이 표를 두세 장으로 나누고 각 표의 기본키와 표를 잇는 외래키를 아래에 쓰시오. 그리고 책 제목을 키로 쓰지 않는 까닭을 한 줄 덧붙이시오.
표 이름담는 칸기본키 🔑외래키 🔗
표 1
표 2
표 3
📖 모범 답안

학생(학번 🔑, 이름, 반) · 책(책번호 🔑, 제목, 저자) · 대출(대출번호 🔑, 학번 🔗 → 학생, 책번호 🔗 → 책, 대출일).

되풀이가 사라진다 — 한 학생이 열 번 빌려도 이름·반은 한 곳에만 적힌다. 반이 바뀌어도 한 줄만 고치면 된다. 대출 표는 "누가 · 무엇을 · 언제"만 남는다.

책 제목을 키로 쓰지 않는 까닭 — 같은 제목의 책이 여러 권 있을 수 있고(줄마다 달라야 한다는 기본키의 조건을 깬다), 판이 바뀌면 제목도 바뀐다. 오늘 대여소명에서 본 것과 같은 문제다 — 뜻이 있는 값은 키가 되기 어렵다. 같은 책 여러 권을 따로 세어야 한다면 '책번호'가 아니라 권마다 다른 '자료등록번호'가 키가 된다.

+
더 알아보기

장부 밖의 이야기 셋

마지막 한 대 — 같은 순간의 두 요청세로는 시간(위 → 아래) · 두 세로줄은 두 사람의 요청잠금이 없으면둘 다 '1대 남음'을 읽는다가나남은 수 읽기 1남은 수 읽기 1빌린다빌린다남은 수 0남은 수 −1자전거는 한 대인데두 사람이 빌렸다읽은 값이 낡았다잠금이 있으면한 번에 한 사람만 손을 댄다가나잠그고 읽기 1빌린다 · 0잠금 풀기기다림남은 수 0대여 불가전부 되거나 전부 안 되거나가·나는 같은 순간에 마지막 한 대를 빌리려는 두 사람이다.
방법 더 깊이

마지막 한 대를 두 사람이 동시에 빌리면

오늘 우리는 한 사람씩 차례로 고치는 장부를 지켰습니다. 그런데 진짜 따릉이 시스템에서는 요청이 같은 순간에 들어옵니다. 대여소에 자전거가 한 대 남았을 때 두 사람이 동시에 앱을 누르면, 둘 다 "남은 수 1"을 읽고 둘 다 빌려 버려 남은 수가 −1 이 됩니다. 어느 쪽 코드에도 틀린 줄은 없는데 결과가 틀립니다.

데이터베이스는 이런 일을 트랜잭션으로 막습니다. 여러 줄의 작업을 한 덩어리로 묶어 전부 되거나 전부 안 되게(원자성) 하고, 한 사람이 손을 대는 동안 그 줄에 잠금을 걸어 다른 요청을 기다리게 하지요. 기다린 쪽은 잠금이 풀린 뒤 새로 읽어 "남은 수 0"을 보고 '대여 불가'를 받습니다. 오늘 건 빗장이 한 사람의 잘못된 값을 막았다면, 트랜잭션은 여러 사람의 겹친 순간을 막습니다.

도해: 잠금이 없을 때와 있을 때의 두 요청을 시간 축(위 → 아래) 위에 나란히 그렸다. 가·나는 같은 순간에 마지막 한 대를 빌리려는 두 사람이다.

모든 표를 하나의 열쇠로 이으면가운데 열쇠 하나가 다섯 표를 잇는다 — 한 곳이 새면 다섯이 함께 열린다병원 진료 기록유출은행 계좌이어져 있다학교 학적이어져 있다통신사 가입이어져 있다쇼핑몰 주문이어져 있다주민등록번호뜻 없는 키는 기관마다 다르게 붙일 수 있어, 한 곳이 새도 다른 표까지 열리지는 않는다.도해는 관계를 보인 그림이고, 다섯 기관은 예시다.
현장

모든 표를 잇던 열쇠 — 주민등록번호

오늘 우리는 대여소를 뜻 없는 번호로 가리켰습니다. 사람을 가리킬 때도 같은 고민이 있습니다. 한국에서는 오랫동안 주민등록번호가 병원·은행·학교·통신사·쇼핑몰의 표를 잇는 열쇠였습니다. 어디서든 통하니 편리했지요. 문제는 어디서든 통한다는 바로 그 점입니다 — 한 곳에서 새면 그 번호로 이어진 모든 표가 함께 열립니다.

2014년 1월, 카드사 세 곳에서 개인정보가 대규모로 빠져나간 일이 드러났습니다. 그해 8월부터는 법령에 근거가 없으면 주민등록번호를 수집·이용할 수 없게 되었고(개인정보 보호법 제24조의2), 많은 서비스가 자기 안에서만 통하는 회원번호로 갈아탔습니다. 키를 고르는 일은 기술 문제로 보이지만, 사실은 이 번호가 새면 무엇까지 함께 열리는가를 정하는 설계 결정입니다.

도해: 가운데 열쇠 하나가 다섯 표를 잇는 그물 — 기관 다섯은 예시다. · 조항·시행 시기는 국가법령정보센터의 개인정보 보호법 제24조의2 기준

한 장이 세 장이 되기까지도서관 대출 기록 — 되풀이를 걷어 내는 단계한 장학번이름반책 제목저자대출일학생 정보와 책 정보가 대출 한 건마다 되풀이된다두 장학번★이름반대출번호★학번🔗책 제목저자대출일학생을 한 번만 적고 학번으로 가리킨다세 장책번호★제목저자대출번호★학번🔗책번호🔗대출일책도 한 번만 — 학생 표는 그대로 두고 책 표를 하나 더★ 기본키 · 🔗 외래키분석할 때는 반대로 세 장을 다시 한 장으로 펴기도 한다(비정규화).Ⅱ단원의 '정규화'는 값의 크기를 맞추는 다른 일이다 — 낱말만 같다.
이름 붙이기

되풀이를 걷어 내는 일에 붙은 이름 — 정규화

오늘 한 장을 두 장으로 나눈 일에는 이름이 있습니다. 정규화입니다. 한 칸에 값 하나만 넣고(제1정규형), 키의 일부에만 딸린 칸을 떼어 내고(제2정규형), 키가 아닌 칸에 딸린 칸까지 떼어 내면(제3정규형) 되풀이가 단계별로 사라집니다. 확인 문제 5의 도서관 표가 한 장 → 두 장 → 세 장이 되는 것이 바로 그 단계예요.

다만 정규화가 언제나 옳은 것은 아닙니다. 나눌수록 물을 때마다 다시 이어야(JOIN) 하니까요. 그래서 분석용 표는 일부러 한 장으로 펴 두기도 합니다 — 비정규화입니다. 나누기는 저장·고치기를 위한 것이고, 펴기는 묻기를 위한 것이라고 기억하면 됩니다.

⚠️ 낱말 주의 — Ⅱ단원에서 만날 '정규화'는 전혀 다른 일입니다. 거기서는 값의 크기를 0~1 로 맞추는 것(min-max·z 점수)을 그렇게 부릅니다. 같은 낱말이지만 표를 나누는 오늘의 정규화와는 아무 상관이 없어요.

도해: 도서관 대출 기록 한 장이 학생 표·대출 표, 다시 책 표까지 세 장으로 나뉘는 단계(★ 기본키 · 🔗 외래키)