Menu

SQLite RETURNING 절: INSERT·UPDATE·DELETE 결과 한 번에 받기

SQLite RETURNING 절 사용법 정리. 추가 쿼리 없이 INSERT, UPDATE, DELETE로 변경된 행을 그 자리에서 바로 돌려받는 방법을 예제로 살펴봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 — 편집하고 실행하면 결과를 바로 볼 수 있습니다.

방금 무슨 일이 벌어졌는지 확인하는 방법

INSERT, UPDATE, DELETE를 실행하면 SQLite는 몇 개의 행이 영향을 받았는지는 알려주지만, 어떤 행이 바뀌었는지나 변경 후의 값이 어떻게 되었는지는 알려주지 않습니다. 예전부터 흔히 쓰던 방법은 곧바로 SELECT를 한 번 더 날리는 것이었죠. 하지만 이러면 쿼리를 두 번 보내야 하고, 그 사이에 다른 누군가가 해당 행을 건드릴 수 있는 미세한 경합 구간이 생깁니다.

RETURNING 절은 바로 이 문제를 해결해 줍니다. 쓰기 쿼리 뒤에 RETURNING을 붙이고 받고 싶은 컬럼을 나열하면, SQLite가 영향받은 행을 마치 SELECT로 조회한 것처럼 그대로 돌려줍니다:

한 번의 쿼리로 한 번 왕복하면, 자동 생성된 id와 데이터베이스가 채워준 created_at 기본값까지 돌려받을 수 있습니다.

RETURNING 절은 SQLite 3.35.0(2021년 3월)부터 추가됐습니다. 만약 구문 오류로 거부된다면 SELECT sqlite_version();으로 버전을 먼저 확인해 보세요. 그 이전 버전에서는 이 키워드를 인식하지 못합니다.

INSERT 후 자동 생성된 ID 가져오기

RETURNING을 가장 많이 쓰는 상황은 INSERT 직후 자동 생성된 기본 키를 곧바로 받아오고 싶을 때입니다. last_insert_rowid()를 따로 호출할 필요 없이 한 번에 처리할 수 있죠:

RETURNING 절이 등장하기 전에는 INSERT를 실행한 뒤 같은 커넥션에서 last_insert_rowid()(혹은 드라이버에서 제공하는 동등한 함수)를 호출해야 했습니다. 지금도 동작하긴 하지만, 이 방식은 커넥션 상태에 의존하는 일종의 마법이라 커넥션 풀이나 스레드 환경에서 실수하기 쉽습니다. 반면 RETURNING id는 문장 자체에 명시적으로 묶여 있어, 어떤 환경에서 커넥션을 다루든 항상 같은 방식으로 동작합니다.

테이블에 INTEGER PRIMARY KEY를 명시하지 않았더라도, 암묵적으로 부여되는 row 식별자를 받아올 수 있습니다:

일반적인 SQLite 테이블에는 모두 rowid가 자동으로 부여되는데, RETURNING 절을 쓰면 이 값을 그대로 받아올 수 있습니다.

여러 컬럼과 표현식 함께 반환하기

RETURNING 뒤에는 SELECT의 컬럼 목록과 똑같은 형태가 올 수 있습니다. 컬럼을 나열해도 되고, *로 전체를 가져와도 되며, 표현식을 써서 별칭을 붙이는 것도 가능합니다:

RETURNING *은 기본값으로 채워진 컬럼까지 포함해 모든 값을 한 번에 받아오고 싶을 때 유용합니다. 컬럼 이름을 일일이 적을 필요가 없죠:

새로 만들어진 id, 직접 넘긴 name, 그리고 SQLite가 계산한 타임스탬프까지 한 번에 확인할 수 있습니다.

UPDATE에서 RETURNING 사용법

UPDATE 문에 RETURNING 절을 붙이면 변경이 적용된 뒤의 값, 즉 업데이트가 끝난 시점의 행 상태를 그대로 돌려받을 수 있습니다:

Ada의 새 잔액인 125가 그대로 반환됩니다. 이전 값 100이 아니죠. 덕분에 RETURNING 절은 원자적 카운터나 입출금 처리에 딱 맞습니다. 값을 읽고, 계산하고, 다시 쓰고, 또 읽는 번거로운 과정을 거칠 필요가 없거든요.

WHERE 조건에 여러 행이 매칭되면, 영향을 받은 행마다 하나씩 결과가 반환됩니다.

세 행을 넣었으니 세 행이 그대로 나옵니다. 다만 결과 순서는 보장되지 않으므로, 특정 순서가 필요하다면 클라이언트 쪽에서 정렬해 주세요.

DELETE에서 RETURNING 절 사용하기

DELETE 문에 RETURNING을 붙이면 _삭제 직전 시점_의 행 데이터를 받아올 수 있습니다. 아카이빙이나 감사 로그를 남길 때, 또는 어떤 행이 실제로 지워졌는지 확인하고 싶을 때 유용합니다:

만료된 세션 두 건이 테이블에서 이미 사라졌는데도 모든 필드가 그대로 반환됩니다. 이걸 다른 곳으로 옮기고 싶다면 아카이브 테이블을 만들기 딱 좋은 구조죠. 같은 트랜잭션 안에서 결과를 받아 다른 테이블에 그대로 넣어주면 됩니다.

UPSERT에서 RETURNING 활용하기

RETURNINGINSERT ... ON CONFLICT ... DO UPDATE 구문, 즉 sqlite upsert returning 패턴에서도 똑같이 동작합니다. 새로 INSERT가 됐든, 충돌이 나서 UPDATE 쪽으로 갔든, 실제로 실행된 분기의 결과 행이 그대로 반환됩니다.

이 문장을 두 번 실행해 보세요. 첫 번째는 INSERT가 일어나면서 ('visits', 1)이 반환됩니다. 두 번째는 충돌이 발생해 값이 1 증가하고, ('visits', 2)가 돌아옵니다. 어느 쪽이든 결과는 한 문장에 한 행 — "방금 삽입된 건가, 업데이트된 건가?"를 따로 확인할 필요가 없습니다.

이 방식은 "값이 없으면 만들고, 있으면 현재 값을 달라"를 라운드트립 없이 처리하는 SQLite의 가장 깔끔한 패턴입니다.

알아두면 좋은 몇 가지

처음 쓰는 분들이 자주 걸려 넘어지는 포인트들입니다.

  • RETURNINGINSERTUPDATE에서는 변경 이후 행을, DELETE에서는 변경 이전 행을 보여줍니다. 반대쪽을 요청할 수 있는 문법은 없습니다.
  • 반환되는 행의 순서는 보장되지 않습니다. 순서가 중요하다면 클라이언트 측에서 ORDER BY를 붙이세요.
  • RETURNING은 서브쿼리 안에 넣을 수 없습니다. 표현식이 아니라 쓰기 문장에 붙는 최상위 절입니다.
  • RETURNINGBEFORE 트리거가 수정한 데이터를 별도로 돌려주지 않습니다 — 실제로 기록된 값이 반환됩니다. AFTER 트리거는 쓰기와 행 반환 사이에 실행됩니다.
  • 생성 컬럼(generated column)과 DEFAULT 값도 결과에 그대로 보입니다. 그래서 데이터베이스가 알아서 채워준 값을 빠르게 확인하기에는 RETURNING *이 제격이죠.

다음 주제: CSV 데이터 가져오기

RETURNING은 한 번에 한 행, 또는 몇 행 정도를 쓰면서 결과를 바로 확인하고 싶을 때 진가를 발휘합니다. 반면 파일에서 수천 행씩 한꺼번에 적재해야 한다면 SQLite의 CSV 가져오기 도구를 쓰게 되는데, 바로 다음 페이지에서 다룹니다.

자주 묻는 질문

SQLite도 RETURNING 절을 지원하나요?

네, 3.35.0 버전(2021년 3월 릴리스)부터 지원합니다. INSERT, UPDATE, DELETE 문 뒤에 RETURNING을 붙이면 영향을 받은 행을 그대로 돌려받을 수 있어요. 그보다 낮은 버전이면 파서 단계에서 바로 거부되니, SELECT sqlite_version();로 먼저 버전을 확인해 보세요.

방금 INSERT한 행의 ID를 SQLite에서 어떻게 받아오나요?

INSERT ... RETURNING id 형태로 쓰면 됩니다. 명시적인 PRIMARY KEY가 없는 테이블이라면 RETURNING rowid를 쓰세요. 같은 문장 안에서 생성된 값을 바로 반환해 주기 때문에, last_insert_rowid()로 다시 한 번 조회할 필요가 없습니다.

RETURNING으로 여러 컬럼을 한 번에 받을 수도 있나요?

가능합니다. SELECT처럼 원하는 컬럼을 콤마로 나열하면 돼요. 예를 들어 RETURNING id, name, created_at처럼요. 모든 컬럼이 필요하면 RETURNING *을 쓰면 되고, RETURNING id, price * quantity AS total처럼 표현식이나 별칭도 그대로 사용할 수 있습니다.

UPSERT나 ON CONFLICT 구문에서도 RETURNING이 동작하나요?

네. INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING ... 형태로 쓰면 새로 INSERT됐든, 충돌 처리 과정에서 UPDATE됐든 최종 행을 돌려받을 수 있어요. UPSERT를 수행하면서 결과 상태까지 한 번의 왕복으로 확인하는 가장 깔끔한 방법입니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기