CodeGym /행동 /SQL SELF /데이터 로딩 중 에러 처리하기 (`ON CONFLICT`)

데이터 로딩 중 에러 처리하기 (`ON CONFLICT`)

SQL SELF
레벨 23 , 레슨 3
사용 가능

데이터 로딩 중 에러 처리하기 (ON CONFLICT)

대량 데이터 로딩의 드라마틱한 현장에 온 걸 환영해! 오늘은 ON CONFLICT 구문으로 데이터 로딩 중에 생기는 에러를 어떻게 똑똑하게 처리할 수 있는지 배워볼 거야. 이건 마치 비행기 오토파일럿 켜는 거랑 비슷해: 뭔가 잘못돼도 당황하지 않고, 사고 없이 대처할 수 있지. 자, PostgreSQL의 꿀팁들 파헤쳐보자!

누구나 예상치 못한 상황은 싫어하지. 특히 데이터가 안 들어갈 때! 대량 데이터 로딩할 때 자주 만나는 문제들이 있어:

  • 데이터 중복. 예를 들어, 테이블에 UNIQUE 제약조건이 있는데, 데이터 파일에 중복이 많을 때.
  • 제약조건 충돌. 예를 들어, NOT NULL 제약이 걸린 컬럼에 빈 값을 넣으려고 하면? 결과는? 에러지. PostgreSQL은 이런 상황에서 항상 엄격해.
  • 중복된 기본 정보. 테이블에 이미 네 CSV 파일이랑 같은 id 가진 데이터가 있을 수도 있어.

이제 ON CONFLICT로 이런 "지뢰"들을 어떻게 피할 수 있는지 알아보자.

ON CONFLICT로 에러 처리하기

ON CONFLICT 구문은 제약조건(UNIQUEPRIMARY KEY 등) 충돌이 생겼을 때 어떻게 할지 정할 수 있어. PostgreSQL은 기존 데이터를 업데이트하거나, 충돌된 행을 그냥 무시할 수 있게 해줘.

기본 ON CONFLICT 구문은 이렇게 생겼어:

INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...)
ON CONFLICT (conflict_target)
DO UPDATE SET column1 = new_value1, column2 = new_value2;

만약 그냥 충돌을 무시하고 싶으면 DO UPDATE 대신 DO NOTHING을 쓰면 돼.

예시: 충돌 시 데이터 업데이트하기

예를 들어, students 테이블이 있다고 해보자:

CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    age INT
);

이제 새로운 데이터를 넣으려고 하는데, 일부는 이미 DB에 있어:

INSERT INTO students (id, name, age)
VALUES 
    (1, 'Peter', 22),  -- 이 학생은 이미 있어
    (2, 'Anna', 20),  -- 새로운 학생
    (3, 'Mal', 25) -- 새로운 학생
ON CONFLICT (id) DO UPDATE SET 
    name = EXCLUDED.name, 
    age = EXCLUDED.age;

이 예시에서, 이미 추가하려는 ID가 있는 학생이 있으면, 그 학생의 데이터가 업데이트돼:

ON CONFLICT (id) DO UPDATE SET
    name = EXCLUDED.name, 
    age = EXCLUDED.age;

여기서 EXCLUDED라는 마법 같은 단어를 주목해봐. 이건 "넣으려고 했지만 충돌 때문에 제외된 값"을 의미해.

결과:

  • id = 1인 학생은 이름과 나이가 업데이트돼.
  • id = 2id = 3인 학생은 새로 테이블에 추가돼.

예시: 충돌 무시하기

데이터를 업데이트하지 않고, 충돌된 행만 무시하고 싶으면 DO NOTHING을 써:

INSERT INTO students (id, name, age)
VALUES 
    (1, 'Peter', 22),  -- 이 학생은 이미 있어
    (2, 'Anna', 20),  -- 새로운 학생
    (3, 'Mal', 25) -- 새로운 학생
ON CONFLICT (id) DO NOTHING;

이제 충돌된 행은 그냥 안 들어가고, 나머지는 DB에 잘 들어가.

에러 로그 남기기

가끔은 무시하거나 업데이트만으론 부족할 때가 있어. 예를 들어, 나중에 분석하려고 충돌을 기록해야 할 때가 있지. 그럴 땐 에러 로그용 테이블을 따로 만들 수 있어:

CREATE TABLE conflict_log (
    conflict_time TIMESTAMP DEFAULT NOW(),
    id INT,
    name TEXT,
    age INT,
    conflict_reason TEXT
);

이제 에러 처리와 로그 남기기를 추가해보자:

INSERT INTO students (id, name, age)
VALUES 
    (1, 'Peter', 22), 
    (2, 'Anna', 20), 
    (3, 'Mal', 25)
ON CONFLICT (id) DO UPDATE SET 
    name = EXCLUDED.name, 
    age = EXCLUDED.age
RETURNING EXCLUDED.id, EXCLUDED.name, EXCLUDED.age
INTO conflict_log;

이 마지막 예시는 저장 프로시저 안에서만 동작해. 이게 어떻게 돌아가는지는 나중에 PL-SQL 배울 때 자세히 다룰 거야. 살짝 앞서 나간 거지만, 데이터 로딩 충돌을 해결하는 또 다른 방법(문제 있는 행을 전부 로그로 남기기)을 보여주고 싶었어.

이제 충돌 원인을 분석할 수 있어. 이 테크닉은 복잡한 시스템에서 대량 데이터 로딩할 때 "흔적"을 남기는 게 중요할 때 특히 유용해.

실전 예제

지금까지 배운 걸 한 번에 써보는 간단한 미션! 학생 업데이트가 담긴 CSV 파일이 있고, 이걸 테이블에 넣고 싶다고 해보자:

파일 이름은 students_update.csv

id name age
1 Otto 23
2 Anna 21
4 Wally 30

데이터 로딩과 충돌 처리

  1. 먼저 임시 테이블 tmp_students를 만들어:
CREATE TEMP TABLE tmp_students (
  id   INTEGER,
  name TEXT,
  age  INTEGER
);
  1. \COPY로 파일에서 데이터 불러오기:
\COPY tmp_students FROM 'students_update.csv' DELIMITER ',' CSV HEADER
  1. 임시 테이블에서 실제 테이블로 INSERT ON CONFLICT로 데이터 넣기:
INSERT INTO students (id, name, age)
SELECT id, name, age FROM tmp_students
ON CONFLICT (id) DO UPDATE
  SET name = EXCLUDED.name,
      age = EXCLUDED.age;

이제 모든 데이터(업데이트도 포함해서, id = 1인 행도!)가 잘 들어갔어.

자주 하는 실수와 예방법

고수 개발자도 실수는 해. 하지만 미리 알고 있으면 시간(그리고 멘탈!)을 엄청 아낄 수 있지.

  • UNIQUE 제약조건 충돌. ON CONFLICT에 올바른 필드를 지정했는지 꼭 확인해. 예를 들어, id 대신 email을 써야 하는데 잘못 지정하면 PostgreSQL이 그냥 "빠이"하고 쿼리 날려버려.
  • EXCLUDED 잘못 쓰기. 이 별칭은 현재 쿼리에서 전달된 값에만 쓸 수 있어. 다른 데서 쓰려고 하면 안 돼.
  • 컬럼 누락. SET에 적은 컬럼이 테이블에 실제로 있는지 꼭 확인해. 예를 들어, SET non_existing_column = 'value' 이런 식이면 에러 나.

ON CONFLICT를 쓰면 PostgreSQL에서 대량 데이터 로딩이 훨씬 유연하고 안전해져. 충돌 때문에 쿼리가 실패하는 걸 막을 수 있을 뿐 아니라, 데이터 처리 방식을 직접 컨트롤할 수 있지. 사용자(그리고 서버!)가 진짜 고마워할 거야.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION