이번 주 학습 내용
Python 기초를 마무리하고 SQL로 넘어간 주차다. 모듈과 패키지로 코드를 구조화하는 법, 파일을 읽고 쓰는 법을 익힌 뒤, MySQL을 통해 데이터베이스 기초까지 다뤘다. 후반부에는 Enum·zip·sorted 부가 문법과 Streamlit으로 간단한 웹 앱을 띄우는 것까지 다뤘다. 예외 처리는 취진센 상담으로 대체되어 다음으로 넘어갔다.
01. 모듈
모듈은 .py 파일 하나다. 함수나 변수를 다른 파일에서 가져다 쓸 수 있게 해준다.
import 모듈명— 모듈 전체를 가져온다. 사용 시모듈명.함수()로 접근from 모듈명 import 함수명— 특정 항목만 가져온다. 모듈명 없이 바로 사용 가능import 모듈명 as 별칭— 긴 이름을 줄이거나 충돌을 피할 때 사용math,datetime같은 표준 라이브러리도 같은 방식으로 불러온다
if __name__ == "__main__": 패턴이 인상적이었다. 직접 실행할 때는 "__main__"이 되고, import할 때는 파일명이 되어 블록이 실행되지 않는다. 테스트 코드를 같은 파일에 두면서도 import 시 자동 실행을 막는 Python다운 방식이다.
02. 패키지와 가상 환경
패키지는 모듈을 디렉토리 단위로 묶은 것이다. import 패키지명.모듈명으로 가져온다.
pip install 패키지명— 외부 패키지 설치. 이번 주에numpy를 직접 설치해봤다- 가상 환경(venv) — 프로젝트마다 독립적인 패키지 환경.
python -m venv 이름으로 생성,Scripts\activate로 활성화 pip freeze > requirements.txt로 패키지 목록 저장,pip install -r requirements.txt로 동일 환경 재현
가상 환경의 필요성을 실감했다. 프로젝트마다 요구하는 패키지 버전이 달라 전역 환경에 다 설치하면 충돌이 생긴다. venv는 이걸 프로젝트 단위로 격리한다.
03. 파일 I/O
with open(경로, 모드, encoding='utf-8') as f: 구문으로 파일을 열고 블록이 끝나면 자동으로 닫힌다.
'r'읽기 /'w'덮어쓰기 /'a'이어쓰기- txt —
f.write(),f.readlines()로 줄 단위 리스트 반환 - csv —
csv.writer/csv.reader. 쓰기 시newline=''필수 - json —
json.dump(data, f)로 저장,json.load(f)로 읽기. dict가 그대로 오간다 - html, log — 텍스트로 열어
readlines()+ 조건 필터 또는replace()로 가공
04. SQL — DDL과 제약조건
MySQL에서 테이블을 만들고 구조를 바꾸는 DDL(Data Definition Language)을 다뤘다.
CREATE TABLE IF NOT EXISTS— 테이블 생성. 이미 있으면 오류 없이 넘어간다ALTER TABLE— 컬럼 추가(ADD), 이름/타입 변경(CHANGE COLUMN), 삭제(DROP)TRUNCATE— 테이블 구조는 유지하고 데이터만 전체 삭제.DELETE FROM보다 빠르다AUTO_INCREMENT— INSERT 시 PK를 자동으로 증가시켜준다.null을 넘기면 된다
제약조건도 함께 다뤘다.
- NOT NULL — 해당 컬럼에 null 값 금지
- UNIQUE — 중복 값 금지. null은 여러 개 허용
- PRIMARY KEY — NOT NULL + UNIQUE의 조합. 테이블당 하나
- FOREIGN KEY — 다른 테이블의 PK를 참조.
ON UPDATE/DELETE SET NULL로 연쇄 동작 설정 가능 - CHECK — 값의 범위나 목록을 제한.
CHECK(gender IN ('남', '여')) - DEFAULT — 값을 안 넣으면 자동으로 채워줄 기본값
05. SQL — SELECT와 데이터 필터링
데이터를 조회하는 SELECT와 조건을 다루는 WHERE를 집중적으로 익혔다.
SELECT 컬럼 FROM 테이블 WHERE 조건— 기본 조회 구조CONCAT()— 문자열 결합.AS로 별칭 부여 가능- AND / OR — AND가 OR보다 우선순위가 높다. 괄호로 명시하는 게 안전
- LIKE — 패턴 매칭.
'%마늘%'은 "마늘"이 포함된 것,'마늘%'은 "마늘"로 시작하는 것 - IN — 여러 값 중 하나.
category_code IN (4, 5, 6) - IS NULL / IS NOT NULL — null 비교는
=가 아닌IS를 써야 한다 - BETWEEN —
menu_price BETWEEN 20000 AND 30000 - ORDER BY —
ASC(오름차순, 기본값),DESC(내림차순).FIELD()로 임의 순서 지정, null을 뒤로 보내려면IS NULL ASC - DISTINCT — 중복 행 제거. 여러 컬럼에 쓰면 조합 기준으로 중복 제거
- LIMIT — 출력 행 수 제한.
LIMIT 5또는LIMIT 1, 4(offset, count)
06. SQL — GROUP BY, JOIN
여러 행을 묶어 집계하고, 테이블을 연결해서 조회하는 방법을 다뤘다.
- GROUP BY — 같은 값끼리 묶어서 집계.
COUNT(*),SUM(),AVG()와 함께 쓴다 - HAVING — GROUP BY 결과에 조건을 거는 절. WHERE는 집계 전, HAVING은 집계 후 필터링
- WITH ROLLUP — 소계·합계 행을 자동으로 추가
- INNER JOIN — 양쪽에 매칭되는 행만 반환.
USING(컬럼명)으로 동명 컬럼 기준 조인 간소화 - LEFT JOIN — 왼쪽 테이블 전체 + 오른쪽 매칭 행. 오른쪽에 없으면 null
- RIGHT JOIN — 오른쪽 테이블 전체 + 왼쪽 매칭 행
- CROSS JOIN — 두 테이블의 모든 행 조합 (카르테시안 곱)
- Self JOIN — 같은 테이블을 별칭으로 두 번 조인. 계층 구조 탐색에 활용
- 옵티마이저 힌트 —
/*+ HASH_JOIN(a) *///*+ NO_HASH_JOIN(a) */로 MySQL이 선택할 조인 알고리즘을 직접 지정할 수 있다. 실행 계획 튜닝 시 사용
07. SQL — INDEX와 VIEW
- INDEX — 특정 컬럼에 인덱스를 만들면 검색 속도가 빨라진다.
EXPLAIN으로 실행 계획을 확인하면type=ALL(전체 탐색)이type=ref(인덱스 활용)로 바뀐 걸 볼 수 있다 CREATE INDEX 이름 ON 테이블(컬럼)/DROP INDEX 이름 ON 테이블SHOW INDEX FROM 테이블— 테이블에 걸린 인덱스 목록과 상세 정보를 확인- VIEW — SELECT 결과를 마치 테이블처럼 저장해두는 가상 테이블. 복잡한 쿼리를 단순하게 재사용할 수 있다
- VIEW를 통한 INSERT·UPDATE·DELETE도 원본 테이블에 반영된다. 단, 집계 함수가 포함된 VIEW는 DML 불가
CREATE OR REPLACE VIEW— 이미 있으면 대체, 없으면 생성
08. SQL — 서브쿼리 & DB 권한 관리
서브쿼리(Subquery) — SELECT 문 안에 중첩된 SELECT다. 외부 쿼리의 WHERE·FROM·SELECT 절에 올 수 있다.
WHERE col IN (SELECT col FROM ...)— 서브쿼리 결과 집합과 비교. 같은 결과를 Self JOIN으로도 풀 수 있지만, 중복 제거 목적이라면 서브쿼리가 더 간결하다- 상관 서브쿼리(Correlated Subquery) — 외부 쿼리의 값을 참조하는 서브쿼리. 행마다 실행되므로 성능에 주의
-- 관리자인 사원 수 조회 — 서브쿼리 방식
SELECT COUNT(*)
FROM employee e
WHERE e.MANAGER_ID IN (SELECT e2.EMP_ID FROM employee e2);
-- 동일 결과 — Self JOIN 방식
SELECT COUNT(*)
FROM employee e
JOIN employee e2 ON e.MANAGER_ID = e2.EMP_ID;DB 생성 및 권한 관리 — MySQL에서 새 데이터베이스를 만들고 사용자에게 권한을 부여하는 관리 명령어다.
CREATE DATABASE 이름— 새 데이터베이스 생성.SHOW DATABASES로 목록 확인GRANT ALL PRIVILEGES ON db명.* TO 사용자@'%'— 특정 DB의 모든 권한을 사용자에게 부여.'%'는 모든 호스트에서 접속 허용SHOW GRANTS FOR 사용자@'%'— 해당 사용자에게 부여된 권한 목록 확인
CREATE DATABASE employeedb;
SHOW DATABASES;
GRANT ALL PRIVILEGES ON menudb.* TO ohgiraffers@'%';
GRANT ALL PRIVILEGES ON employeedb.* TO ohgiraffers@'%';
SHOW GRANTS FOR ohgiraffers@'%';09. 부가 문법 — Enum, zip, sorted
Enum — 열거형 상수를 클래스로 정의하는 방법이다. 매직 넘버를 이름 있는 상수로 바꿔 가독성과 안전성을 높인다.
from enum import Enum으로 임포트 후 클래스 형태로 선언.멤버.name은 이름 문자열,멤버.value는 할당된 값- 비교 시
==와is모두 동작. 같은 값을 가진 멤버는 별칭(alias)으로 처리되어 순회 시 건너뜀 auto()— 값을 1부터 자동 증가.@unique데코레이터를 붙이면 값 중복 시 오류 발생Enum('이름', ['A', 'B', 'C'])— 클래스 없이 함수형으로 선언 가능
from enum import Enum, auto, unique
@unique
class Lang(Enum):
C = auto() # 1
Java = auto() # 2
Python = auto() # 3
print(Lang.Python.name) # Python
print(Lang.Python.value) # 3
for lang in Lang:
print(lang) # Lang.C / Lang.Java / Lang.Pythonzip — 두 시퀀스를 같은 인덱스끼리 묶어 tuple의 이터레이터를 만든다. 그 자체는 lazy object라 list()로 실체화하거나 for문으로 소비해야 값을 볼 수 있다.
for a, b in zip(list1, list2):— 병렬 순회dict(zip(keys, values))— 두 리스트를 dict로 변환하는 관용구zip(*pairs)— 언패킹으로 묶인 쌍을 다시 분리. 전치(transpose) 효과
letters = ['A', 'B', 'C']
numbers = [1, 2, 3]
print(dict(zip(letters, numbers))) # {'A': 1, 'B': 2, 'C': 3}
pairs = [('A', 1), ('B', 2), ('C', 3)]
l, n = zip(*pairs)
print(l) # ('A', 'B', 'C')sorted — 원본을 건드리지 않고 정렬된 새 리스트를 반환한다. list.sort()가 in-place인 것과 다르다.
sorted(iterable)— 기본 오름차순.reverse=True로 내림차순key=lambda x: x[1]— dict items를 value 기준으로 정렬할 때 자주 사용
d = {'b': 400, 'a': 200, 'c': 100}
# value 기준 정렬
print(sorted(d.items(), key=lambda x: x[1]))
# [('c', 100), ('a', 200), ('b', 400)]
# key 기준 정렬
print(sorted(d.items(), key=lambda x: x[0]))
# [('a', 200), ('b', 400), ('c', 100)]10. Streamlit — Python으로 만드는 웹 앱
HTML/CSS/JS 없이 Python만으로 인터랙티브 웹 앱을 만드는 프레임워크다. pip install streamlit으로 설치하고 streamlit run app.py로 실행하면 브라우저에 자동으로 열린다. 위젯 조작마다 스크립트 전체를 재실행하는 방식이 특징이다.
기본 출력
st.title() / st.header() / st.subheader()— 제목 계층st.write()— 텍스트, 마크다운, DataFrame, 차트 등 대부분을 출력 가능한 범용 함수st.divider()— 구분선,st.image(path, caption, width)— 이미지 출력st.success() / st.info() / st.warning() / st.error()— 컬러 알림 메시지
데이터 표시
st.dataframe(df)— 인터랙티브 DataFrame 뷰어.df.style.highlight_max()로 셀 강조 가능st.data_editor(df)— 편집 가능한 테이블.num_rows="dynamic"으로 행 추가·삭제st.metric(label, value, delta)— KPI 지표를 카드 형태로 표시st.json(data)— dict를 접을 수 있는 JSON 트리로 출력
column_config — dataframe·data_editor의 컬럼 표시 방식을 세밀하게 지정한다.
- 타입별 클래스:
NumberColumn,TextColumn,CheckboxColumn,SelectboxColumn,DateColumn,TimeColumn - 시각화 클래스:
LineChartColumn,BarChartColumn,ProgressColumn
st.dataframe(df, column_config={
"price": st.column_config.NumberColumn("가격", format="₩%.0f"),
"category": st.column_config.SelectboxColumn(
"카테고리", options=["전자제품", "의류", "가전제품"]
)
})차트
st.line_chart() / st.bar_chart() / st.area_chart() / st.scatter_chart()— DataFrame을 넘기면 바로 차트 생성st.pyplot(fig)— Matplotlib figure 출력st.altair_chart(chart)— Altair로 인터랙티브 차트. 툴팁·인코딩 등 세밀한 설정 가능
입력 위젯 — 모두 반환값이 있어 조건문으로 바로 연결할 수 있다.
st.text_input() / st.number_input(min_value, max_value)— 텍스트·숫자 입력st.selectbox() / st.multiselect() / st.radio()— 선택형 입력st.checkbox() / st.toggle()— True/False 반환st.slider() / st.select_slider(options)— 슬라이더st.date_input() / st.time_input()— 날짜·시간 선택기st.file_uploader(type=[...])— 파일 업로드. 업로드 객체의.name으로 파일명 확인st.button()— 클릭 시True반환.st.download_button()으로 데이터 다운로드with st.form("key"):+st.form_submit_button()— 여러 위젯을 한 번에 제출
레이아웃
col1, col2 = st.columns(2)— 화면을 N열로 분할.with col1:블록 안에 위젯 배치
session_state — 스크립트 재실행 후에도 값을 유지하는 전역 상태 저장소다. Streamlit은 위젯을 조작할 때마다 스크립트 전체를 재실행하기 때문에 일반 변수는 매번 초기화된다. 상태를 보존해야 할 때는 session_state를 써야 한다.
if "count" not in st.session_state:
st.session_state.count = 0
if st.button("손님 추가"):
st.session_state.count += 1
if st.button("초기화"):
st.session_state.count = 0
st.write(f"손님 수: {st.session_state.count}")KPT 회고
Keep
SQL 실습에서 EXPLAIN으로 실행 계획을 직접 확인한 게 좋았다. 인덱스가 실제로 어떻게 동작하는지 눈으로 보니 단순 암기보다 훨씬 납득이 됐다.
파일 I/O는 실습 문제를 풀면서 실제 파일이 생기는 걸 확인하며 진행해서 이해가 빨랐다.
Problem
SQL WHERE 조건에서 AND와 OR의 우선순위를 놓쳐 의도하지 않은 결과가 나온 적이 있었다. 괄호를 명시하지 않으면 AND가 먼저 묶이므로 복합 조건은 항상 괄호로 감싸야 한다.
파일 I/O 실습에서 로그 필터 조건을 잘못 써서 ERROR가 빠지고 INFO가 출력됐다. 출력만 대강 맞아 보이면 그냥 넘기는 습관을 고쳐야 한다.
Try
SQL 쿼리를 작성하기 전에 원하는 결과를 먼저 말로 정리한다. "어떤 테이블에서, 어떤 조건으로, 어떤 컬럼을 뽑을 것인가"를 순서대로 쓰고 나서 쿼리로 옮기면 논리 오류가 줄어든다.
새롭게 발견한 것
FOREIGN KEY ON DELETE SET NULL. 부모 테이블의 행이 삭제됐을 때 자식 테이블의 참조 컬럼을 null로 바꿔주는 옵션이다. 참조 무결성을 유지하면서도 자식 데이터를 보존할 수 있다. 삭제 룰을 어떻게 설정하느냐에 따라 데이터 흐름이 완전히 달라진다는 게 흥미로웠다.
FOREIGN KEY (grade_code)
REFERENCES user_grade (grade_code)
ON UPDATE SET NULL
ON DELETE SET NULL
-- 부모 행 삭제 시 자식의 grade_code가 자동으로 NULL로 변경됨VIEW를 통한 DML. VIEW는 조회용이라고만 생각했는데 INSERT·UPDATE·DELETE도 된다. 단, 조인이나 집계가 있는 VIEW는 불가능하다. 내부적으로 어느 테이블에 어떻게 쓸지 특정할 수 없기 때문이다.
with open의 자동 정리. with open() as f:는 예외가 발생해도 파일을 자동으로 닫는다. try/finally로 수동으로 닫는 것과 동일하지만 훨씬 간결하고 실수가 없다.
트러블슈팅
문제 : 로그 필터에서 의도하지 않은 줄이 출력됨
로그 파일에서 WARN 또는 ERROR가 포함된 줄만 출력해야 했는데, 조건을 잘못 써서 INFO 줄까지 함께 출력됐다.
# 수정 전 — INFO까지 포함됨
for l in logs:
if 'INFO' in l or 'WARN' in l: # INFO는 필터 대상이 아님
print(l.strip())문제 조건을 다시 읽고 나서야 INFO가 아니라 ERROR임을 확인했다. 비슷한 키워드들이 나열되면 눈으로 훑다가 잘못 옮기기 쉽다.
# 수정 후
for l in logs:
if 'ERROR' in l or 'WARN' in l:
print(l.strip())문제 : SQL WHERE에서 AND/OR 우선순위 혼동
카테고리가 4이거나, 가격이 9000이면서 메뉴 코드가 10보다 큰 메뉴를 조회하려 했는데 결과가 의도와 달랐다.
-- 수정 전 — AND가 먼저 묶여 조건이 달라짐
WHERE category_code = 4
OR menu_price = 9000 AND menu_code > 10;
-- 실제 동작: category_code=4 OR (menu_price=9000 AND menu_code>10)AND의 우선순위가 OR보다 높다. 이 경우 결과가 우연히 맞을 수도 있지만 명시적 괄호 없이는 의도가 불분명하다. 복합 조건은 항상 괄호로 감싸는 습관이 필요하다.
-- 수정 후 — 괄호로 의도를 명확히
WHERE category_code = 4
OR (menu_price = 9000 AND menu_code > 10);다음 주 목표
- 넘어온 예외 처리(
try / except / finally / raise) 정리 - SQL 쿼리 작성 전 로직을 먼저 말로 정리하는 습관 들이기
- Streamlit으로 실습 데이터를 시각화하는 간단한 앱 직접 완성해보기
SQL로는 데이터를 읽는 언어를 배웠다.
둘 다 결국 "무엇을 물어볼 것인가"를 명확히 하는 일이다.