글

라벨이 데이터 처리인 게시물 표시

파이썬 SQLite 두 테이블 저장: 두 번째 INSERT 실패 시 함께 롤백하기

함께 성공해야 하는 INSERT를 같은 연결의 트랜잭션으로 묶고, 예외를 with 블록 밖에서 처리하세요. 확인일: 2026-10-25. Python 3.14 표준 라이브러리 예제이며, Windows Python 3.14.4에서 합성 입력으로 실행하고 assert 검사를 통과했습니다. 두 번의 저장이 하나의 작업이어야 하는 이유 자료 본문을 items에 넣고 처리 이력을 job_events에 따로 넣는 자동화를 생각해 보세요. 첫 저장 뒤 commit하고 이력 저장이 실패하면 본문만 남을 수 있습니다. 이 글은 중복 항목을 갱신하는 UPSERT가 아니라, 서로 연결된 두 저장의 성공·실패 경계를 맞추는 방법을 다룹니다. 예제는 메모리 DB와 가상 문서만 사용합니다. 예제의 입력과 실패 조건 items의 id와 job_events의 job_id는 각각 기본 키입니다. 첫 호출은 A01과 job-1을 저장합니다. 다음 호출은 새 문서 B02를 넣은 뒤 이미 있는 job-1을 또 저장해 두 번째 INSERT에서 IntegrityError가 나도록 구성했습니다. 예상 결과는 A01과 첫 이력만 남고 B02는 없는 것입니다. SQL 값은 문자열 보간 대신 자리표시자로 전달합니다. 직접 실행하는 예제 import sqlite3 def save_item(conn, item_id, name, job_id): with conn: conn.execute("INSERT INTO items VALUES (?, ?)", (item_id, name)) conn.execute("INSERT INTO job_events VALUES (?, ?)", (job_id, item_id)) conn = sqlite3.connect(":memory:", autocommit=False) try: conn.execute("CREATE TABLE items (id TEXT PRIMARY KEY, ...

파이썬 CSV 두 파일 비교: ID 기준으로 추가, 삭제, 수정 행 구분하기

고유 ID로 각 CSV를 색인하고, ID 집합의 차이와 공통 ID의 비교 필드를 나눠 검사하세요. 적용 환경: Python 3.14 표준 라이브러리. 2026-10-23 확인. Windows Python 3.14.4에서 아래 합성 예제를 임시 작업 폴더로 실행해 assert와 예상 예외 검사를 통과했습니다. 실제 회사 파일·API·GUI는 실행하지 않았습니다. 파일 전체 비교와 업무 항목 비교는 다릅니다 매일 내려받는 자산 목록의 줄 순서만 바뀌었을 때, 파일 해시나 줄 단위 차이는 달라질 수 있습니다. 그러나 같은 자산의 소유 부서와 상태가 같다면 업무 변경은 아닐 수 있습니다. 이 글에서는 파일의 바이트 일치가 아니라 고유 자산 ID를 기준으로 의미 있는 필드가 변했는지 비교합니다. 원본 목록에 없는 ID가 보인다는 사실과 실제 자산을 폐기해야 한다는 판단도 분리합니다. 비교할 필드와 키 규칙을 먼저 고정합니다 합성 CSV의 헤더는 asset_id,owner_team,state이며 순서까지 고정합니다. asset_id는 문자열이라 001의 앞자리 0을 보존합니다. owner_team과 state 두 값의 튜플이 같으면 변경 없음입니다. ID 앞뒤 공백과 중복 ID, 빈 값·누락 열·추가 열은 실패시키며, 부서나 상태의 공백을 임의로 정제하지 않습니다. 시간 같은 변동 필드가 추가되면 비교 대상에 넣을지 먼저 결정하세요. 직접 실행하는 최소 예제 import csv from io import StringIO FIELDS = ["asset_id", "owner_team", "state"] def load_snapshot(text): reader = csv.DictReader(StringIO(text, newline=""), strict=True) if reader.fieldnames != FIELDS: raise ValueError("헤더 불일치...

파이썬 Decimal 합계가 다른 이유: 행별 반올림과 합산 후 반올림 비교

금액은 문자열에서 Decimal로 만들고, 반올림 단위·방식·시점을 입력 계약으로 고정해야 합계를 재현할 수 있습니다. 적용 환경: Python 3.14 표준 라이브러리. 2026-10-22 확인. Windows Python 3.14.4에서 아래 예제를 임시 작업 폴더에서 실행하고 assert 및 예상 예외 검사를 통과했습니다. 실제 회사 자료·외부 서비스는 사용하지 않았습니다. Decimal을 쓰면 왜 합계가 자동으로 맞지 않을까요? 이 글은 금전 거래나 세금 계산 규정이 아니라 보고서 숫자를 재현하는 프로그래밍 예제입니다. 입력이 정확해도 각 행을 먼저 반올림한 뒤 더하는 계산과, 원래 값을 모두 더한 뒤 한 번 반올림하는 계산은 다를 수 있습니다. 담당 시스템과 합의한 기준 없이 어느 쪽을 정답으로 바꾸면 안 됩니다. 작은 합성 입력으로 차이를 고정합니다 예제 단위는 소수 둘째 자리까지 표시하는 가상의 금액입니다. 0.005 두 행을 ROUND_HALF_UP으로 각각 처리하면 0.01 두 개라 합계는 0.02입니다. 원래 값 두 개의 합 0.010을 마지막에 처리하면 0.01입니다. 소수 둘째 자리라는 선택과 HALF_UP 방식은 이 예제의 계약일 뿐 모든 통화·정산 규칙의 기본값이 아닙니다. 직접 실행하는 최소 예제 from decimal import Decimal, localcontext, ROUND_HALF_UP import re PATTERN = re.compile(r"[0-9]{1,12}(?:\.[0-9]{1,4})?") CENT = Decimal("0.01") def calculate_total(values, mode): if not isinstance(values, list) or not 1 <= len(values) <= 10000: raise ValueError("1~10000개 문자열의 목록이 필요합니다") if mode not in {...

파이썬 CSV 한글 깨짐 진단: UTF-8 BOM과 CP949를 손실 없이 읽기

파일 제공처의 인코딩을 확인한 뒤 strict 모드로 디코딩하고, 예상 헤더와 열 개수를 별도로 검사해야 합니다. 적용 환경: Python 3.14 표준 라이브러리. 2026-10-21 확인. Windows의 Python 3.14.4에서 아래 합성 예제를 임시 작업 폴더로 실행하고 assert 검사를 통과했습니다. 외부 서비스 호출이나 실제 회사 파일 처리는 실행하지 않았습니다. 디코딩 실패와 잘못된 헤더는 다른 오류입니다 거래처에서 받은 실적표의 첫 열이 부서인데 KeyError가 난다면, 먼저 실제 헤더를 확인하세요. UTF-8 BOM이 있는 파일을 utf-8로 읽으면 첫 헤더 앞에 U+FEFF가 남을 수 있습니다. utf-8-sig는 시작 부분의 UTF-8 BOM을 처리합니다. 반면 CP949로 만든 한글 바이트를 UTF-8로 읽는 문제는 UnicodeDecodeError 단계에서 발견할 수 있습니다. CSV 파싱보다 먼저 발생하는 오류입니다. 예제의 입력 계약 아래 자료는 개인정보가 없는 합성 실적표입니다. 헤더는 순서까지 부서,건수로 고정하고, 건수는 0 이상의 ASCII 정수 문자열만 허용합니다. 음수·빈 부서·누락 열·추가 열을 거부합니다. 다른 업무에서는 이 규칙을 먼저 합의해 바꾸세요. 모든 열이 문자열로 반환되므로 금액이나 날짜의 업무 검증은 별도 단계입니다. 직접 실행할 최소 예제 import csv from io import StringIO HEADERS = ["부서", "건수"] def read_report(raw, encoding): text = raw.decode(encoding, errors="strict") reader = csv.DictReader(StringIO(text, newline=""), strict=True) if reader.fieldnames != HEADERS: raise ValueError("...

Pandas merge 후 행이 늘어날 때: 기준표 중복 키와 미매칭 행 검사

주문 여러 건에 기준표 한 건을 붙이는 병합에는 validate="many_to_one"을 지정하고, indicator로 찾지 못한 키를 별도 확인하세요. 주문 목록에 상품명을 붙이는 작업에서 기준표의 상품 코드가 중복되면, 주문 한 행이 여러 이름과 연결되어 수량 합계까지 커질 수 있습니다. 이 글은 CSV를 아래로 이어 붙이는 concat과 달리, 상품 코드로 옆 열을 조회해 붙이는 좌측 병합 을 다룹니다. 주문 여러 건과 상품 한 건은 어떤 관계인가요? 예제에서는 같은 상품을 주문한 행이 여러 개인 것은 정상입니다. 반면 상품 기준표에서는 상품 코드 한 개에 이름 한 개만 있어야 합니다. 이 계약에 맞춰 many_to_one을 사용합니다. 주문과 기준표 모두 키가 유일해야 하는 업무라면 one_to_one처럼 다른 관계를 검토해야 합니다. 테스트 자료는 주문 4행이며, P01 주문 2행·P02 주문 1행·기준표에 없는 X99 주문 1행입니다. 수량은 각각 1·2·1·4로 합계 8입니다. 기준표에 없는 행도 원본에 남겨 조사하려고 how="left"를 선택했습니다. 이 수치는 아래 합성 자료에서 계산한 예제 결과이지 실제 매출 자료가 아닙니다. 중복 기준표와 미매칭을 검사하는 코드 Windows Python 3.14.4, Pandas 3.0.2에서 실행했습니다. Pandas가 없으면 작업용 가상환경에서 python -m pip install pandas 로 설치하세요. 코드의 표는 메모리에서 만들며 기존 CSV나 업무 DB를 읽거나 변경하지 않습니다. import pandas as pd def attach_names(orders, master): for name, frame in (("orders", orders), ("master", master)): keys = frame["product_id"] if keys.is...

파이썬 중첩 JSON 파싱: 누락 키와 null을 안전하게 처리하기

중첩 JSON은 각 단계의 자료형을 확인한 뒤 필요한 키를 추출해야 합니다. .get() 의 기본값은 키가 없을 때만 적용되므로, 값이 null 인 응답까지 자동으로 안전해지는 것은 아닙니다. 누락 키와 null을 구분하세요 JSON 객체는 파이썬의 dict, 배열은 list, null은 None으로 변환됩니다. data.get("items", []) 를 호출해도 실제 응답에 "items": null 이 있으면 결과는 None입니다. 응답이 항상 같은 구조라는 가정을 코드로 확인해야 합니다. 실행 가능한 최소 예제 Python 3의 표준 라이브러리만 사용하는 예제입니다. 정상 객체, 누락 필드, null, 잘못된 행 형식을 한 데이터에 넣어 처리 경계를 확인합니다. import json payload = json.loads('''{"result": {"items": [ {"id": "A01", "name": "보고서"}, {"id": "A02", "name": null}, null, {"name": "식별자 없음"} ]}}''') def extract_items(data): if not isinstance(data, dict): raise ValueError("최상위 값은 객체여야 합니다") result = data.get("result") if not isinstance(result, dict): raise ValueError("result 객체가 없습니다") items = result.get("items") if not isi...

Pandas 결측치 처리: isna, fillna, dropna 선택 기준

Pandas 결측치는 isna() 로 위치와 개수를 확인한 뒤, 열의 의미에 따라 삭제·유지·대체를 결정합니다. 결측치 비율만으로 행이나 열을 지우는 보편적인 정답은 없습니다. 삭제하기 전에 확인할 질문 누락이 시스템 장애인지, 해당되지 않는 값인지, 아직 입력되지 않은 값인지 구분하세요. 특히 매출 0과 매출 미확인은 서로 다른 정보입니다. 식별번호가 없을 때와 선택 항목이 비어 있을 때도 같은 규칙으로 처리하면 안 됩니다. 작은 표로 처리 결과 확인하기 Python 3와 Pandas가 필요합니다. python -m pip install pandas 로 설치한 뒤 실행하세요. 아래는 필수 식별자가 없는 행은 제외하고, 지역 미입력은 별도 범주로 표시하며, 금액 누락은 그대로 보존하는 예제입니다. import pandas as pd df = pd.DataFrame({ "order_id": ["001", "002", None, "004"], "region": ["서울", None, "부산", "서울"], "amount": [10000, None, 5000, 0], }) print(df.isna().sum().to_dict()) clean = df.dropna(subset=["order_id"]).copy() clean["region"] = clean["region"].fillna("미입력") assert len(clean) == 3 assert clean["amount"].isna().sum() == 1 assert (clean["amount"] == 0).sum() == 1 print(clean.to_string(index=False)) 처음 집계는 각 열의 결...

파이썬 중복 저장 방지: SQLite UNIQUE와 UPSERT 사용법

중복 저장을 막으려면 업무상 같은 항목을 식별하는 키를 정하고, 데이터베이스의 UNIQUE 제약과 저장 정책을 함께 사용해야 합니다. 파이썬에서 존재 여부를 미리 조회하는 것만으로 동시 저장의 중복까지 막을 수는 없습니다. 무엇을 중복으로 볼지 먼저 정하세요 제목이 같다고 같은 문서는 아닐 수 있습니다. 원본 서비스의 고유 ID가 있다면 우선 사용하고, 없다면 URL 정규화나 여러 필드의 조합을 검토하세요. 제목과 날짜의 해시값도 원본 필드가 같거나 충돌하면 같은 키가 될 수 있으므로 업무상 유일성을 보장하는 별도 근거가 필요합니다. SQLite로 재현하는 중복 방지 예제 Python 3 표준 sqlite3 모듈만 사용합니다. 메모리 DB를 사용하므로 실제 파일을 바꾸지 않습니다. 같은 원본 ID가 들어오면 해당 행의 내용을 갱신하는 정책입니다. import sqlite3 con = sqlite3.connect(":memory:") try: con.execute("""CREATE TABLE articles ( source_id TEXT PRIMARY KEY NOT NULL, title TEXT NOT NULL )""") sql = """INSERT INTO articles(source_id, title) VALUES (?, ?) ON CONFLICT(source_id) DO UPDATE SET title=excluded.title""" with con: con.execute(sql, ("A01", "첫 제목")) con.execute(sql, ("A01", "수정된 제목")) rows = con.execute("SELECT sourc...

파이썬 정규표현식으로 공백 정리하기: 데이터 손실을 막는 예제

정규표현식으로 텍스트를 정리할 때는 제거할 문자를 정하기 전에 보존해야 할 정보를 먼저 정해야 합니다. 숫자, 한글, 영문만 남기는 규칙은 소수점·음수 부호·날짜 구분자까지 지울 수 있습니다. 모든 특수문자를 지우면 생기는 문제 -12.5% 에서 기호를 전부 지우면 125 가 됩니다. 원래 의미가 달라지는 것입니다. 가격, 전화번호, URL, 식별번호는 서로 다른 처리 규칙이 필요합니다. HTML 태그 제거도 정규표현식 하나에 맡기기보다 HTML 파서를 사용하는 편이 적합합니다. 공백과 설명 문구만 정리하는 예제 다음 코드는 Python 3 표준 라이브러리만 사용합니다. 범용 정제가 아닌, 상품 설명 문구의 공백을 합치고 제어 문자를 제거하는 예제입니다. import re raw = " 월간\t보고서\n매출: -12.5%\x00 " without_controls = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f]", "", raw) clean = re.sub(r"\s+", " ", without_controls).strip() assert clean == "월간 보고서 매출: -12.5%" print(clean) 줄바꿈과 탭은 한 칸 공백으로 바뀌고 소수점·마이너스·퍼센트 기호는 보존됩니다. 여러 줄을 유지해야 하는 문서라면 \s+ 로 합치는 규칙을 그대로 쓰면 안 됩니다. 수평 공백만 정리하는 등 문서 목적에 맞게 범위를 좁히세요. 숫자를 추출할 때의 경계 text = "증감률 -12.5%, 목표 3.0%" numbers = re.findall(r"[-+]?\d+(?:\.\d+)?", text) assert numbers == ["-12.5", "3.0"] print(numbers) 위 패턴은 예제의 소수 표기만 다룹니다. 천 단위 쉼표, 지...

Pandas CSV 파일 병합: 청크 처리와 데이터 검증 예제

CSV를 병합할 때는 열 구조와 자료형을 먼저 맞추고, 파일 전체를 메모리에 올리기 어려우면 청크 단위로 읽어 처리합니다. 청크 크기만 지정해도 모든 메모리 문제가 해결되는 것은 아닙니다. 행을 이어 붙이는 병합과 키 결합 구분 같은 구조의 월별 파일은 행을 이어 붙일 수 있습니다. 고객번호를 기준으로 고객 표와 주문 표를 결합하는 작업은 별도의 merge이며, 키가 중복되면 결과 행 수가 늘어날 수 있습니다. 이 글은 같은 열 구조의 파일을 순서대로 이어 붙이는 예제입니다. 파일을 덮어쓰지 않고 청크로 병합하기 Python 3와 Pandas가 필요합니다. 임시 폴더에서 샘플 입력 파일과 출력 파일을 만들기 때문에 실제 업무 파일을 바꾸지 않습니다. from pathlib import Path from tempfile import TemporaryDirectory import pandas as pd with TemporaryDirectory() as folder: root = Path(folder) pd.DataFrame({"id": ["001"], "amount": [10]}).to_csv(root / "a.csv", index=False) pd.DataFrame({"id": ["002"], "amount": [20]}).to_csv(root / "b.csv", index=False) inputs = sorted(root.glob("*.csv")) output = root / "merged.csv" expected = ["id", "amount"] first = True total_rows = 0 for path in inputs: for chunk in pd...

SQLAlchemy 2 ORM 입문: 객체 저장과 트랜잭션 확인

SQLAlchemy ORM은 클래스를 테이블에 매핑하지만, 저장 확정과 오류 처리는 트랜잭션 단위로 명시해야 합니다. 어떤 상황에서 적용하나요? 예제는 SQLAlchemy 2 계열과 SQLite 메모리 DB를 사용합니다. python -m pip install "sqlalchemy>=2,<3"으로 설치하세요. 원격 DB나 기존 파일을 변경하지 않고 모델 선언부터 조회까지 확인할 수 있습니다. 검증 범위: 아래 코드는 외부 서비스에 요청하지 않는 로컬 예제입니다. 실제 서비스 연결은 별도로 검증해야 합니다. 최소 예제로 동작 확인하기 from sqlalchemy import create_engine, select from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session class Base(DeclarativeBase): pass class Task(Base): __tablename__ = "tasks" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] engine = create_engine("sqlite://") Base.metadata.create_all(engine) with Session(engine) as session: with session.begin(): session.add(Task(name="월간 보고서")) with Session(engine) as session: names = session.scalars(select(Task.name)).all() assert names == ["월간 보고서"] print(names) engine.dispose() 결과는 어떻게 확인하나요? 조회 결과는 월간 보고서 한 건입니다....

파이썬 SQLite 기초: 테이블 생성, 매개변수 쿼리와 연결 종료

파이썬의 sqlite3 모듈로 별도 DB 서버 없이 테이블 생성·저장·조회를 시작할 수 있습니다. 값은 문자열로 SQL에 붙이지 않고 매개변수로 전달하고, 커밋과 연결 종료를 구분해서 관리합니다. 설치와 적용 범위 sqlite3는 일반적인 Python 배포판에 포함된 표준 라이브러리 모듈입니다. 먼저 import sqlite3 가 되는지 확인하세요. 여러 서버가 동시에 많은 쓰기 요청을 처리하는 시스템이라면 다른 DB 구성이 적합할 수 있습니다. SQLite가 파일 기반이라는 특성과 잠금 동작을 고려해야 합니다. 메모리 데이터베이스에서 먼저 실행하기 import sqlite3 con = sqlite3.connect(":memory:") try: con.execute("""CREATE TABLE tasks ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, done INTEGER NOT NULL DEFAULT 0 )""") with con: con.execute("INSERT INTO tasks(name) VALUES (?)", ("월간 보고서",)) con.execute("INSERT INTO tasks(name) VALUES (?)", ("파일 정리",)) rows = con.execute( "SELECT id, name FROM tasks WHERE done = ? ORDER BY id", (0,) ).fetchall() assert len(rows) == 2 print(rows) finally: con.close() 두 작업이 순서대로 출력됩니다. :memory: 는 연결이 종료되면 사라지는 테스트 DB입니다. 파일...

Pandas 검색 지표 병합: 날짜와 사이트를 기준으로 집계

여러 사이트의 검색 지표를 합칠 때는 사이트·날짜를 결합 키로 사용하고 클릭률을 단순 평균하지 않아야 합니다. 어떤 상황에서 적용하나요? 아래는 같은 스키마의 작은 표를 합치는 예제입니다. 외부 분석 계정에 접근하지 않으며 결과의 합계를 입력 합계와 대조합니다. Pandas가 필요합니다. 검증 범위: 아래 코드는 외부 서비스에 요청하지 않는 로컬 예제입니다. 실제 서비스 연결은 별도로 검증해야 합니다. 최소 예제로 동작 확인하기 import pandas as pd a = pd.DataFrame([{"site": "a", "date": "2026-10-01", "clicks": 2, "impressions": 10}]) b = pd.DataFrame([{"site": "b", "date": "2026-10-01", "clicks": 3, "impressions": 90}]) combined = pd.concat([a, b], ignore_index=True) daily = combined.groupby("date", as_index=False)[["clicks", "impressions"]].sum() daily["ctr"] = daily["clicks"] / daily["impressions"].replace(0, float("nan")) assert daily.loc[0, "clicks"] == 5 assert daily.loc[0, "impressions"] == 100 assert daily.loc[0, "ctr"] == 0.05 print(daily.to_st...

파이썬 CSV 저장: 한글 인코딩과 수식 입력 주의

CSV 저장에서는 열 순서·인코딩·줄바꿈을 정하고, 스프레드시트에서 수식으로 해석될 수 있는 외부 입력을 구분해야 합니다. 어떤 상황에서 적용하나요? 아래는 임시 폴더에 CSV를 쓰고 다시 읽어 헤더와 한글 보존을 확인합니다. 표준 라이브러리만 사용하며 실제 Excel을 실행하지 않습니다. utf-8-sig는 BOM이 포함된 UTF-8입니다. 검증 범위: 아래 코드는 외부 서비스에 요청하지 않는 로컬 예제입니다. 실제 서비스 연결은 별도로 검증해야 합니다. 최소 예제로 동작 확인하기 import csv from pathlib import Path from tempfile import TemporaryDirectory def spreadsheet_text(value): text = str(value) return "'" + text if text.lstrip().startswith(("=", "+", "-", "@")) else text with TemporaryDirectory() as folder: path = Path(folder) / "report.csv" with path.open("w", encoding="utf-8-sig", newline="") as file: writer = csv.DictWriter(file, fieldnames=["name", "note"]) writer.writeheader() writer.writerow({"name": "보고서", "note": spreadsheet_text("=1+1")}) with path.open(encoding="utf-8-sig...