행·열과 필요한 열 조회
65분 안팎
학습 목표
기록 ID와 표의 한 행을 연결합니다.
개념
파일의 기록을 표의 행으로 바꾸는 이유
독서 기록을 JSON으로 저장하면 프로그램을 다시 실행해도 목록을 복원할 수 있습니다. 이제 운영자가 제목과 쪽수만 확인하고 싶다고 요청합니다. Python에서 반복문을 작성할 수도 있지만, 조회할 열과 조건을 SQL 문장으로 분리하면 보고서의 목적을 검토하기 쉬워집니다. SQL은 표에서 필요한 값을 선택하고 조건을 적용하는 언어입니다. 이번 레슨에서는 저장 방식을 갈아엎지 않고 기록 한 건과 표 한 행의 대응부터 확인합니다.
행은 하나의 독서 기록이고 열은 그 기록이 가진 속성입니다. id는 기록 식별자, title은 제목, pages는 읽은 쪽수입니다. 같은 책을 두 번 읽으면 제목은 같을 수 있으므로 제목만으로 기록을 구분하지 않습니다. 앞 모듈에서 딕셔너리의 키로 쓰던 ID를 표에서는 PRIMARY KEY로 선언합니다. ID가 같은 두 행은 서로 다른 기록으로 함께 저장할 수 없다는 약속입니다. 목록의 순번을 ID로 사용하면 정렬 후 의미가 달라질 수 있습니다.
이 모듈의 브라우저 SQL 실습은 SQLite를 사용합니다. tests의 입력은 표를 만드는 준비 SQL이며 학습자는 SELECT 조회문을 제출합니다. 결과는 머리글 없이 행마다 한 줄, 열 값은 공백으로 구분하고 NULL은 NULL로 표시합니다. 빈 결과는 출력이 없는 상태입니다. 로컬 미션은 Python 3 표준 라이브러리 sqlite3와 unittest만 사용하며 압축을 푼 폴더에서 안내 명령을 실행합니다. 모든 이름과 이메일은 가상 자료입니다.
표 정의와 입력의 책임
CREATE TABLE reading_records(...)는 열 이름과 자료형을 정합니다. TEXT는 제목과 ID 같은 문자열, INTEGER는 쪽수와 완료 표시 같은 정수에 사용합니다. completed는 이 프로젝트에서 0 또는 1이라는 입력 계약입니다. 열의 자료형만 적는다고 모든 잘못된 값이 자동으로 거부되는 것은 아닙니다. 특히 일반 SQLite 표의 형식 규칙은 Python의 엄격한 검증과 다르므로 이전 모듈의 입력 검증을 계속 유지합니다.
INSERT INTO는 새 행을 추가합니다. 열 목록을 쓰면 값이 어느 속성에 들어가는지 분명해집니다. 문자열 리터럴에는 작은따옴표를 쓰고 문자열 안의 작은따옴표는 두 번 씁니다. 예를 들어 책 제목의 따옴표도 데이터의 일부입니다. Python에서 실제 값을 넣을 때는 문자열을 이어 붙이지 않고 물음표 자리표시자와 값 묶음을 전달합니다. SQL 구조와 사용자 입력을 분리하는 구체적 방법은 미션 코드에서 확인합니다.
준비된 실습 표에는 owner_name과 owner_email도 있습니다. 내부 입력에 있는 열과 보고서에 내보낼 열은 같지 않습니다. 이번 과제에서 선택하는 것은 id, title, pages입니다. cost와 completed를 지금 출력하지 않는 이유는 요청받은 결과의 계약에 없기 때문입니다. 모든 열을 조회한 뒤 화면에서 일부를 숨기는 방법은 이미 반환된 데이터의 범위를 줄이지 못합니다. 조회문 자체에서 필요한 열을 적는 습관을 시작합니다.
SELECT를 문장으로 읽기
SELECT id, title, pages FROM reading_records는 reading_records에서 세 열을 해당 순서로 반환하라는 뜻입니다. 열 목록은 출력의 구조를 정하고 FROM은 자료의 출처를 정합니다. SELECT는 원본 기록을 수정하지 않습니다. 페이지 값을 다시 조회할 때 바뀌었다면 앞서 실행한 쓰기 작업이나 입력 자료를 조사합니다. 조회와 변경을 분리해서 이해하면 보고서 실습 중 원본을 지웠다고 오해하는 일을 줄일 수 있습니다.
SELECT *는 현재 표의 모든 열을 반환합니다. 표에 새 이메일 열이 추가되면 같은 문장이 갑자기 더 많은 정보를 반환할 수 있습니다. 따라서 보고서처럼 출력 형태가 약속된 곳에서는 별표보다 열 목록을 적습니다. id, title, pages와 title, id, pages는 같은 행을 읽어도 출력 순서가 다릅니다. 자동 채점에서 값은 맞는데 실패한다면 열 개수와 순서도 함께 확인합니다.
ORDER BY id는 결과를 ID 순서로 정렬합니다. INSERT 순서대로 나올 것이라고 기대하는 것은 조회 계약이 아닙니다. 정렬 없는 결과가 오늘 원하는 순서로 나와도 다른 입력이나 실행에서 같은 순서를 보장하지 않습니다. ID는 TEXT이므로 b10과 b2는 숫자 10과 2로 비교되지 않습니다. 샘플은 b01처럼 자리수를 맞추지만 실제 프로젝트에서는 문자열 ID의 정렬 규칙을 문서에 적습니다.
오류를 표 구조와 대조하기
no such table: reading_records는 조회하려는 이름의 표를 현재 연결에서 찾지 못했다는 뜻입니다. 표 생성이 실행되었는지, 다른 DB 연결을 열었는지, 이름을 잘못 적었는지 확인합니다. no such column: page는 열 이름을 찾지 못했다는 뜻입니다. pages를 page로 적는 오타부터 살펴봅니다. near ...: syntax error는 문법을 해석하지 못했다는 뜻이므로 SELECT와 FROM 사이 쉼표, 괄호, 세미콜론을 확인합니다.
UNIQUE constraint failed: reading_records.id는 ID 중복과 관련된 제약 위반입니다. 같은 제목이 있다고 발생하는 메시지는 아닙니다. 오류를 해결한다며 PRIMARY KEY를 지우면 중복 기록을 막는 계약도 사라집니다. 입력의 ID를 확인하고 기존 기록을 수정할지 신규 기록을 만들지 결정해야 합니다. 따라하기에서는 독립된 메모리 DB를 매번 새로 만들므로 이전 실행의 표와 충돌하지 않습니다.
실습은 빈 표, 한 행, 여러 행, 제목 중복, pages가 NULL인 행을 제공합니다. NULL은 아직 알려지지 않은 값을 나타내며 문자열 NULL이나 정수 0과 구분합니다. 이 레슨은 원본 값을 그대로 선택하므로 NULL을 임의로 0으로 바꾸지 않습니다. 제출 전에 선택 열이 정확히 세 개인지, 제목을 고유값으로 바꾸지 않았는지, ORDER BY id를 적었는지 확인합니다. 더 복잡한 별칭과 처리 순서는 더 읽기의 서재 장으로 이어집니다.
따라하기
행과 열 연결
새 표를 만들고 필요한 열 세 개를 ID 순서로 조회합니다.
CREATE TABLE reading_records(id TEXT PRIMARY KEY,title TEXT,pages INTEGER); INSERT INTO reading_records VALUES('b02','별빛',20),('b01','달빛',10); SELECT id,title,pages FROM reading_records ORDER BY id;실행 결과
b01 달빛 10 b02 별빛 20
동일 제목 유지
제목이 같아도 ID가 다른 두 기록을 각각 조회합니다.
CREATE TABLE reading_records(id TEXT PRIMARY KEY,title TEXT,pages INTEGER); INSERT INTO reading_records VALUES('a','재독',0),('b','재독',NULL); SELECT id,title,pages FROM reading_records ORDER BY id;실행 결과
a 재독 0 b 재독 NULL
공개 열 선택
이메일이 있는 표에서도 선택하지 않은 열은 결과에 나오지 않습니다.
CREATE TABLE reading_records(id TEXT,title TEXT,pages INTEGER,owner_email TEXT); INSERT INTO reading_records VALUES('a','책',7,'virtual@example.invalid'); SELECT id,title,pages FROM reading_records;실행 결과
a 책 7
확인 문제
실습
reading_records에서 id·title·pages를 ID 오름차순으로 반환합니다. 중복 제목과 NULL 쪽수도 그대로 유지합니다.
모범 답안
SELECT id, title, pages FROM reading_records ORDER BY id;
더 읽기
면접 질문
- 독서 기록의 ID를 표의 기본키로 선택한 이유를 설명합니다.