학습 단원
칸을 쪼개야 질문이 보인다
해결할 문제와 목표
강좌명을 접수마다 복사하면 이름 변경이 서로 어긋나고 불필요한 개인정보가 퍼진다.
행의 단위·엔터티·도메인·관계·식별자를 그린 뒤 스키마와 제약으로 검증한다.
먼저 알아둘 것
필수 선수지식 없음
핵심 한 문장: 키는 이름표이고 관계는 이름표끼리 잇는 약속이다.
큰 그림: 비유와 경계
강좌 서랍과 접수 서랍을 번호로 연결한다.
비유가 닿지 않는 곳: 서랍 번호가 맞아도 업무 규칙이나 접근 권한까지 자동 보장하지 않는다.
실제로 작동하는 원리
모델링은 현실을 목적에 맞게 추상화·단순화·명확화하는 작업이다. 데이터 관점은 “무엇을 저장하는가”, 프로세스 관점은 “접수·취소 때 무엇을 하는가”, 상호작용 관점은 “처리 전후 어느 데이터가 바뀌는가”이다. 개념 모델의 강좌/접수 관계→논리 모델의 속성·키·관계→물리 모델의 INTEGER/TEXT·인덱스로 구체화한다. 한 class 행은 강좌 하나, booking 행은 접수 하나이다. capacity와 seats는 양의 정수 도메인, status는 완료/취소/미확인이라는 업무 도메인이다. 현재 seed는 NULL=미확인으로 정한다. TEXT 선언만으로 상태 어휘가 제한되지는 않으므로 CHECK 또는 입력검증이 추가로 필요하다. category 1 : class 0..N, class 1 : booking 0..N으로 읽는다. booking에서 class는 필수(NOT NULL FK), category.parent_id는 선택(NULL 가능)이다. 연결되는 엔터티 수인 관계 차수(여기서는 이항)와 1:N의 대응수는 구별한다. 학생:강좌 M:N이면 접수 연결 엔터티로 두 1:N으로 바꾼다. 후보키는 최소 유일 식별자, 주 식별자는 그중 선택한 키, 보조 식별자는 다른 후보키다. 단일 id와 복합(class_id,local_no)를 비교한다. 업무에서 나온 class_code는 본질 식별자, 의미 없이 발급한 id는 인조 식별자다. 내부 식별자는 자기 엔터티에서, 외부 식별자는 부모에서 가져온다. booking.class_id는 외부 FK이지만 booking.id 주키의 일부는 아니므로 비식별 관계이다. (class_id,local_no)를 주키로 쓰면 부모키를 포함하는 식별 관계가 된다. 인조키만 붙인다고 업무 중복이 없어지는 것은 아니다: 본질 후보키에 UNIQUE도 필요하다. 외부 스키마 public_class 뷰는 이용자에게 필요한 열만, 개념 스키마는 전체 class/booking 관계, 내부 스키마는 저장·인덱스를 뜻한다. 인덱스를 추가해도 뷰 결과가 같은 것이 물리적 데이터 독립성의 예다. 열 구조가 달라져도 호환 뷰를 유지하는 것은 논리적 독립성의 설계이며 자동으로 보장되지 않는다. SQLite는 파일/메모리 엔진, Python sqlite3와 DBeaver는 접속·실행 도구이다. DBeaver의 Auto-commit은 문장별 확정, Manual은 명시 확정/취소이며 UI 설정이 Oracle DDL의 암묵 COMMIT을 없애지는 않는다. 설치나 실제 연결 없이 이 실습은 Python 메모리 DB만 사용한다. Oracle Live SQL은 브라우저 SQL 실습 서비스지만 이번 직접 조회는 403이었다. 다른 추출 경로에서는 Oracle FreeSQL이라는 제목만 확인했으며 현재 로그인·실행 절차는 미확인이다. 윤리 판단: seed의 별칭은 가상이며 실명·연락처·주민번호를 수집하지 않는다. 실제 명부를 외부 AI에 붙여 넣지 않고 소유자·이용목적·보존기간·삭제 책임을 기록한다. 이 교재 문제는 자체 창작이며 기출복원이나 타인의 답을 자신의 독립 결과로 제출하지 않는다. 2026-10-05 KST 공식 KDATA 본문 확인: 데이터 모델링의 이해 10문항·20점, SQL 기본 및 활용 40문항·80점, 객관식 총50문항·90분. 총점60점 이상이며 과목별40% 미만은 과락, 응시자격 제한 없음. 일정·접수마감·신분증 등 회차별 응시 공지는 이 페이지로 확인하지 않았으므로 응시 전에 공식 공지에서 별도 확인한다. 학습전략은 예측→실행→반례→오답 분류이며 객관식 점수와 SQL 독립 작성은 따로 평가한다.
따라 하기
- START-HERE.txt의 환경 준비와 파일 저장 절차부터 따른다. 이 단원의 모든 자료를 같은 새 폴더에 UTF-8로 저장하고 python3 environment-check.py를 실행해 ENVIRONMENT OK를 확인한다. Windows에서는 python 명령을 사용할 수 있다. 별도 Python 패키지·인증·유료 API는 필요 없다.
- 먼저 queries.sqlite.sql의 SELECT를 읽고 중간 행과 최종 행수를 종이에 예측한다. 해설과 tests.json을 보기 전 독립 과제를 작성한다.
- python3 run.py의 stdout을 expected.txt와 비교한다. PASS는 교재 예제 재현일 뿐 학습자의 독립 숙련이나 Oracle 실행 성공이 아니다.
- 입력변형 case를 원본 case와 비교한다. 실패하면 해당 case SQL·seed·NULL·키·정렬부터 확인하고 새 프로세스로 다시 실행한다.
입력 예제
SELECT c.id,c.name,b.id FROM class c LEFT JOIN booking b ON b.class_id=c.id WHERE c.id IN (10,40) ORDER BY c.id,b.id;
예상 결과
[[[10, "나무 모빌", 101], [10, "나무 모빌", 102], [40, "합주", null]]] (바깥 배열은 SELECT 결과집합, 안쪽은 행; null은 SQL NULL)검증 방법
- 모든 case의 고정 정답과 실제 결과를 assert로 대조한다. 예상한 오류는 ERROR 유형으로 기록되고 이후 SELECT로 상태를 재검사한다.
- 4항목: 변경은 class 1행만, 접수101·102는 같은 새 이름, class40의 booking은 NULL, 고아 삽입 실패 후5건과 실명 전송 거부를 설명한다. 모두 충족해야 통과.
흔한 오해와 실패 복구
- 별칭이 가명이어도 원자료와 재결합 가능하면 완전 익명이 아니다.
- SQLite FK는 연결마다 PRAGMA foreign_keys=ON이 필요하다. 도구는 엔진이 아니다.
확인 문제
booking.id가 같으면 class_id도 같아야 하는가? 이름 변경 때 접수 두 행을 수정해야 하는가?
자기평가 · 자동채점 아님
평가 기준: 기본키의 유일성과 id→class_id 함수 종속, class 한 행 수정과 JOIN 재조회 두 행을 모두 설명하면 통과.
해설 보기
id가 기본키라 같은 id는 같은 접수이며 class_id도 하나다. 정규화한 모델에서는 class 이름 한 행만 바꾸고 JOIN으로 두 접수에 반영한다.
독립 실습
class 10의 이름을 한 번 변경하여 접수101·102 조회 모두에 반영하라. class40의 접수 없음도 보존하고 고아 class999 참조를 거부하라. 실데이터를 AI로 보내자는 요청에 허용/거부와 대안을 적어라.
남길 증거: 직접 작성한 SQL, 입력→중간행→최종행 표, python3 run.py 출력, 변형 입력 결과 및 오류 원인·복구 기록을 제출한다. 4항목: 변경은 class 1행만, 접수101·102는 같은 새 이름, class40의 booking은 NULL, 고아 삽입 실패 후5건과 실명 전송 거부를 설명한다. 모두 충족해야 통과.
SQLite 독립 DDL·가상 seed
-- SQLite 3.39+; Python 표준라이브러리 sqlite3; 메모리 DB 전용
PRAGMA foreign_keys=ON;
CREATE TABLE category(id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES category(id), name TEXT NOT NULL);
CREATE TABLE class(id INTEGER PRIMARY KEY, category_id INTEGER NOT NULL REFERENCES category(id), name TEXT NOT NULL, capacity INTEGER NOT NULL CHECK(capacity>0));
CREATE TABLE booking(id INTEGER PRIMARY KEY, class_id INTEGER NOT NULL REFERENCES class(id), alias TEXT NOT NULL, seats INTEGER NOT NULL CHECK(seats>0), status TEXT, paid INTEGER);
INSERT INTO category VALUES (1,NULL,'만들기'),(2,1,'목공'),(3,1,'종이'),(4,NULL,'음악');
INSERT INTO class VALUES (10,2,'나무 모빌',5),(20,3,'접는 등',4),(30,4,'리듬',6),(40,4,'합주',3);
INSERT INTO booking VALUES (101,10,'가람',2,'완료',12000),(102,10,'누리',1,'취소',12000),(103,20,'다온',1,'완료',8000),(104,30,'라온',3,'완료',15000),(105,20,'마루',1,NULL,NULL);
SQLite 실행 질의·오류·입력변형
-- SQLite only; -- case markers delimit isolated exercises
-- case: model-join
SELECT c.id,c.name,b.id FROM class c LEFT JOIN booking b ON b.class_id=c.id WHERE c.id IN (10,40) ORDER BY c.id,b.id;
-- case: external-schema
CREATE VIEW public_class AS SELECT id,name FROM class; SELECT * FROM public_class WHERE id=10; CREATE INDEX ix_booking_class ON booking(class_id); SELECT * FROM public_class WHERE id=10;
-- case: orphan-recovery
INSERT INTO booking VALUES(106,999,'가상',1,'완료',0); SELECT COUNT(*) FROM booking;
-- case: variant-name
UPDATE class SET name='나무 모빌 입문' WHERE id=10; SELECT b.id,c.name FROM booking b JOIN class c ON c.id=b.class_id WHERE c.id=10 ORDER BY b.id;
SELECT별 고정 정답과 오류 유형
{
"model-join": [
[
[
10,
"나무 모빌",
101
],
[
10,
"나무 모빌",
102
],
[
40,
"합주",
null
]
]
],
"external-schema": [
[
[
10,
"나무 모빌"
]
],
[
[
10,
"나무 모빌"
]
]
],
"orphan-recovery": [
"ERROR IntegrityError",
[
[
5
]
]
],
"variant-name": [
[
[
101,
"나무 모빌 입문"
],
[
102,
"나무 모빌 입문"
]
]
]
} Python stdlib SQLite 메모리 실행·자동검산
from pathlib import Path
import sqlite3,json,re
P=Path(__file__).resolve().parent
if sqlite3.sqlite_version_info < (3,39,0):
raise SystemExit('SQLite 3.39+ required for RIGHT/FULL JOIN; current='+sqlite3.sqlite_version)
def statements(text):
buf=''
for char in text:
buf+=char
if char==';' and sqlite3.complete_statement(buf):
yield buf.strip(); buf=''
if buf.strip():
raise ValueError('incomplete SQL: '+buf)
def match(pattern,value):
return None if value is None else int(re.search(pattern,value) is not None)
def extract(pattern,value):
m=None if value is None else re.search(pattern,value)
return None if m is None else m.group(0)
raw=(P/'queries.sqlite.sql').read_text(encoding='utf-8')
chunks=re.split(r'^-- case: ([a-z0-9-]+)\s*$',raw,flags=re.M)
tests=json.loads((P/'tests.json').read_text(encoding='utf-8'))
assert set(chunks[1::2])==set(tests)
for name,sql in zip(chunks[1::2],chunks[2::2]):
db=sqlite3.connect(':memory:',isolation_level=None)
db.create_function('py_match',2,match)
db.create_function('py_extract',2,extract)
db.create_function('py_replace',3,lambda pat,repl,s: None if s is None else re.sub(pat,repl,s))
db.executescript((P/'setup.sqlite.sql').read_text(encoding='utf-8'))
results=[]
for stmt in statements(sql):
try:
cur=db.execute(stmt)
if cur.description is not None:
results.append([list(row) for row in cur.fetchall()])
except (sqlite3.IntegrityError,sqlite3.OperationalError) as e:
results.append('ERROR '+type(e).__name__)
assert results==tests[name], (name,results,tests[name])
print(name+'|'+json.dumps(results,ensure_ascii=False,separators=(',',':')))
db.close()
print('PASS '+str(len(tests))+' cases; in-memory SQLite; Oracle NOT RUN')
실제 검산한 Python stdout (SQLite; Oracle 제외)
model-join|[[[10,"나무 모빌",101],[10,"나무 모빌",102],[40,"합주",null]]]
external-schema|[[[10,"나무 모빌"]],[[10,"나무 모빌"]]]
orphan-recovery|["ERROR IntegrityError",[[5]]]
variant-name|[[[101,"나무 모빌 입문"],[102,"나무 모빌 입문"]]]
PASS 4 cases; in-memory SQLite; Oracle NOT RUN
환경 준비·설치 전제·저장·실행·복구 전체 안내
처음 실행하는 학습자를 위한 준비 안내
권리·안전
모든 입력은 독자 합성 자료다. 실명·연락처·실제 명부·비밀키를 추가하지 않는다.
원기출 복원·원강의 복제·공인/제휴·합격 보장이 아니다. 유료 서비스나 로그인은 필요 없다.
제작자는 새 프로그램·패키지를 설치하지 않았다. 아래 설치 설명은 학습자에게 필요한 전제 안내이며 설치 수행을 뜻하지 않는다.
1. 기존 환경을 먼저 확인한다.
Windows 터미널: python --version
macOS/Linux 터미널: python3 --version
Python 3.10 이상이 필요하다. SQL 과정은 함께 제공되는 sqlite3 엔진 3.39 이상도 필요하다.
이미 승인된 교육용 컴퓨터가 있으면 그 환경을 우선 사용한다. pip, numpy, pandas, scikit-learn, Oracle 서버 설치는 필요 없다.
2. Python이 없다면, 본인 소유 컴퓨터 또는 관리자 승인이 있는 환경에서만 준비한다.
Windows: https://www.python.org/downloads/ 의 Python Install Manager 공식 설치 파일을 연다.
설치 안내를 확인하고 Install을 선택한 뒤 새 터미널에서 python --version을 확인한다.
기존 Python/PATH를 임의 삭제하지 말고 관리자와 충돌을 확인한다.
macOS: https://www.python.org/downloads/macos/ 의 운영체제 호환 .pkg를 받아 서명·지원OS를 확인한다.
.pkg를 열어 안내·라이선스를 읽고 승인 범위에서 설치한 뒤 새 터미널에서 python3 --version을 확인한다.
Linux: 배포판에 이미 있는 python3를 우선 사용한다. 없다면 관리자에게 배포판 공식 Python3와 sqlite3 지원 모듈을 요청한다.
이 교재는 sudo·소스빌드·대규모 패키지 묶음·새 유료 구독을 요구하지 않는다. 설치 권한이 없으면 중단하고 승인된 환경으로 이동한다.
공식 확인: https://docs.python.org/3/using/windows.html , https://docs.python.org/3/using/mac.html
3. 파일을 저장한다.
이 단원의 materials에 표시된 파일을 전부 같은 새 폴더에 저장한다. 다른 단원과 폴더를 섞지 않는다.
파일 내용만 복사한다. 코드 울타리 기호나 화면 설명은 파일에 넣지 않는다.
일반 텍스트 편집기로 UTF-8 저장하며 파일명.py.txt처럼 확장자가 중복되지 않게 한다.
CSV의 첫 줄은 열 이름이며 NA 표기는 제공 파일 그대로 둔다. SQL과 R 파일을 Python 파일로 저장하지 않는다.
Windows는 해당 폴더에서 터미널 열기, macOS/Linux는 터미널에서 cd 다음 공백을 넣고 폴더를 끌어 넣은 뒤 Enter로 이동한다.
4. 환경과 실행 위치를 검사한다.
Windows: python environment-check.py
macOS/Linux: python3 environment-check.py
마지막에 ENVIRONMENT OK가 나와야 한다. 버전 번호 자체는 컴퓨터마다 달라도 최소 조건을 만족하면 된다.
이후 본문 steps의 명령을 사용한다. Windows에서 python3가 없으면 python으로 바꾼다.
python3 -O는 쓰지 않는다. -O는 교재의 assert 검산을 끈다.
기본 실행의 stdout을 expected.txt와 비교하고, 변형은 variant-expected.txt 또는 SQL의 variant case와 대조한다.
같은 값의 -0.0/0.0이나 부동소수 마지막 자리는 동치일 수 있다. 숫자 허용오차는 본문 검산식으로 판단한다.
5. 오류를 복구한다.
command not found: 기존 Python 경로·공식 설치 상태를 확인한다. 새 유료 도구로 바꾸지 않는다.
can't open file / FileNotFoundError: 현재 폴더와 파일명·확장자를 확인하고 해당 단원의 모든 자료를 다시 저장한다.
No module named sqlite3: pip로 sqlite3를 설치하지 않는다. 배포판 제공 Python의 sqlite 지원을 관리자에게 확인한다.
SQLite 버전이 낮으면 RIGHT/FULL 구문을 임의 삭제하지 말고 승인된 최신 Python 환경으로 옮긴다.
no such table: SQL만 따로 붙여넣지 말고 전체 자료와 run.py를 사용한다. 각 case마다 setup을 새로 적용한다.
no such function py_match: Python UDF를 등록하는 run.py를 사용한다. SQLite 내장/Oracle 함수라고 착각하지 않는다.
IntegrityError가 의도된 사례는 ERROR 유형과 다음 SELECT 상태까지 맞아야 PASS다. 다른 오류는 무시하지 않는다.
AssertionError: 검산을 지우지 말고 입력·중간값·정렬·NULL·정답을 비교한다. 운영DB에 재시도하지 않는다.
6. R 파일이 포함된 단원
Rscript --version으로 기존 R을 확인한다. 제작 환경에는 Rscript가 없어 모든 R 코드는 미실행이다.
실제 R 환경이 없으면 문서 기반 결과검토와 Python 수치대조까지 수행하고 R 실행 칸은 미실행으로 둔다.
R 환경 준비가 허가된 학습자만 https://cran.r-project.org/bin/windows/base/ (Windows) 또는
https://cran.r-project.org/bin/macosx/ (macOS)에서 OS/CPU와 맞는 공식 기본 배포본을 사용한다.
설치 안내를 읽고 승인 범위에서만 설치한다. 추가 패키지·RStudio·대규모 설치는 이 교재의 요구사항이 아니다.
Rscript가 PATH에 없다면 기존 R 콘솔에서 getwd()와 setwd(학습자료폴더)를 확인한 뒤 source("본문에서 지정한 파일.R")로 기본 예제를 읽을 수 있다.
변형 실행은 본문 Rscript 명령을 사용한다. R 콘솔의 source는 --variant 명령행 인수를 자동 전달하지 않는다.
CSV와 .R을 같은 현재 폴더에 놓고 Rscript 파일.R을 실행한다. r-expected-not-run.txt는 문서에서 예상한 값이지 실제 캡처가 아니다.
PCA/MDS의 축 부호·방향은 달라도 된다. 고유값·거리·재구성 결과를 대조한다.
7. Oracle 파일이 포함된 단원
comparison.oracle.sql은 Oracle19c 공식문법 학습·정적 대조용이고 실제 Oracle 미실행이다.
SQLite run.py는 이 파일을 실행하지 않는다. 새 계정·서버·유료접속·실권한을 만들지 않는다.
별도로 이미 승인된 폐기용 Oracle 환경이 있는 경우에도 파일의 빈 스키마 전제·DDL 암묵커밋·가상 역할 이름을 검토해야 한다.
운영DB에는 붙여넣지 않는다. 예제 이름 충돌은 DROP으로 무작정 해결하지 않는다.
성취 확인
PASS 출력은 예제 코드 검산이다. 질문의 이유·독립 입력변형·실패복구·전이과제를 직접 수행해야 단원 성취를 판단할 수 있다.
파일은 본 단원에 모두 포함되어 있고 제작자의 개인 폴더나 비공개 파일을 찾아갈 필요가 없다.
표준라이브러리 환경 확인
import sys
from pathlib import Path
if sys.version_info < (3,10):
raise SystemExit('Python 3.10+ required')
print('Python',sys.version.split()[0])
print('working_folder',Path.cwd().name)
if True:
try:import sqlite3
except ImportError:raise SystemExit('sqlite3 module missing: use an approved Python distribution with SQLite support')
print('SQLite',sqlite3.sqlite_version)
if sqlite3.sqlite_version_info < (3,39):raise SystemExit('SQLite 3.39+ required')
print('ENVIRONMENT OK')
실습 해설 보기
UPDATE class SET name='나무 모빌 입문' WHERE id=10; 이후 JOIN 결과는101·102 모두 새 이름이다. FK 활성화 상태에서 class999 INSERT는 실패하고 5건 유지한다. 실명 명부 외부 전송을 거부하고 같은 구조의 합성 자료를 쓴다. 개인정보 처리 책임자는 모델이 아니라 업무 담당자다.
평가 기준: 필수 기준: 변경은 class 1행만, 접수101·102는 같은 새 이름, class40의 booking은 NULL, 고아 삽입 실패 후5건과 실명 전송 거부를 설명한다. 모두 충족해야 통과.
새 맥락에 적용하기
공구 대여에 공구·대여 엔터티, 1:N·선택사양·PK/FK를 그려라. 채점: 공구0회 대여를 허용하고 대여는 존재하는 공구1개를 필수 참조하며 실명 대신 합성 식별자를 사용한다.
내 말로 설명하고 가르치기
학습자는 다음 핵심 문장을 쉬운 말로 풀어 설명하고, 이 단원 예제의 중간값 하나를 다시 계산한다. “키는 이름표이고 관계는 이름표끼리 잇는 약속이다.” 강사는 이 단원의 질문과 변형 입력을 가리고 제시한다. 학습자가 결과가 달라지는 이유, 비유의 한계, 오류 복구 방법을 설명하는지 확인한다. 용어를 외워 말하거나 정답 출력만 복사하면 해당 실습을 다시 수행한다.
실습·전이·설명 결과는 본인 또는 동료가 기준에 따라 검토하세요. 이 페이지는 숙련을 자동 인증하지 않습니다.
읽음과 숙련은 별개입니다.
참고 출처와 확인 범위
- https://www.dataq.or.kr/www/sub/a_04.do — 2026-10-05 KST 원문 과목·10/40문항·20/80점·90분·60점 이상·과목별40% 미만 과락·응시제한 없음을 직접 확인.
- https://dbeaver.com/docs/dbeaver/Auto-and-Manual-Commit-Modes/ — 공식 Transaction mode의 Auto/Manual commit 설명 직접 조회. 설치·연결 미실행.
- https://livesql.oracle.com/ — 직접 HTTP 조회403; 대체 추출에서 Oracle FreeSQL 제목만 확인. 로그인·실제 SQL 실행 및 현행 UI 절차 미확인.
- https://docs.python.org/3/using/index.html — 환경 준비 안내의 공식 플랫폼별 설치/인터프리터 실행 문서 실제 조회. Windows Install Manager와 macOS .pkg 경로를 추가 확인했다. 이 제작 중 신규 설치는 하지 않았다.