문서 작업 가이드

코드 리뷰용 SQL을 포맷하고 쿼리 동작을 검증하는 방법

SQL 방언을 먼저 선택해 포맷하고 의미 diff를 검토한 뒤 배포 전에 실제 DB에서 동작을 증명하는 실전 절차입니다.

작성 및 검수 게시일: 검토일:

이 작업 흐름을 검증한 방법

이번 검증은 “그룹 query를 포맷한 뒤 검토 schema에서 검증”의 정확한 입력과 SQL 포매터를 짝지어 수행했습니다. “대상 DB, 버전, 실행 문맥을 확인합니다” 단계를 거쳐 “명시적이고 재현 가능한 설정으로 수동 포맷합니다” 이후 결과를 캡처해 예시와 비교했습니다. 명시적인 경계 질문은 “vendor 문법에 일반 방언 사용”, “포매터 통과를 위해 문법 제거”였습니다.

포맷 결과는 JOIN 조건, active 필터, GROUP BY와 alias 내림차순을 각각 검토 가능한 절로 드러내면서 문자열 literal과 query 구조는 바꾸지 않았습니다.

문제 상황

긴 SQL 문장은 검토하기 어려워 팀이 일반 포매터에 붙여넣고 깔끔한 출력이 나오면 쿼리가 올바르다고 간주하기 쉽습니다. 그러나 이 방법은 token 배치, vendor 문법 검증, 특정 schema와 data에서의 동작 증명이라는 서로 다른 세 작업을 섞습니다. 공백 포매터는 SELECT, JOIN, WHERE, GROUP BY, ORDER BY를 배치할 만큼 구조를 인식해도 불완전한 절을 받아들일 수 있고, 서버 버전 규칙을 모르며, table·column type·권한·lock·cardinality·index·timezone·collation·NULL 동작·transaction 경계·migration 순서를 전혀 확인하지 않습니다. 잘못된 문법을 선택하면 유효한 PostgreSQL cast, T-SQL 대괄호 식별자, MySQL backtick, BigQuery 이름을 오류로 판단할 수도 있습니다. 저장 procedure와 사용자 지정 delimiter는 일반 쿼리 포매터가 지원하지 않는 추가 언어 층입니다. 복사한 SQL에는 자격 증명, tenant 식별자, 개인정보, 비공개 URL, 운영 literal이 포함되어 clipboard 기록, 브라우저 확장, 화면 캡처, 다운로드 파일에 남을 수도 있습니다. 따라서 목표를 좁혀야 합니다. 포맷은 검토 가능한 후보를 만들 때만 사용하고, 원본과 정확한 설정을 보존하며, 미지원 경계에서 명확히 중단하고, 의미 diff를 비교한 뒤 포매터가 증명하지 못한 모든 항목은 DB와 애플리케이션 테스트로 확인합니다.

이럴 때 사용하세요

  • 한 줄 query, CTE, 연속 join, report query, 저장 routine이 아닌 여러 문장 script를 pull request나 장애 검토 전에 읽기 쉽게 만들어야 할 때
  • PostgreSQL, MySQL, SQLite, SQL Server, Oracle query statement, BigQuery가 서로 다른 포맷 또는 parse 결과를 내고 선택한 방언을 분리해야 할 때
  • 생성하거나 복사한 query를 식별자, literal, 주석, parameter, semicolon을 바꾸지 않고 문서·테스트용 안정된 fixture로 만들 때
  • 포맷 변경만 schema, 성능, 권한, migration, 애플리케이션 동작 검사와 명확히 분리해야 할 때
  • 대표 경계 입력과 rollback 조건을 수동 검증한 뒤 팀이 포매터 버전과 설정을 CI에 고정하려 할 때

단계

  1. 1단계

    대상 DB, 버전, 실행 문맥을 확인합니다

    DB 제품과 버전, client 또는 driver, parameter 형식, schema 또는 search path, transaction mode, text가 query·migration·생성 fragment·저장 routine·template 중 무엇인지 기록합니다. vendor 전용 문법이 없을 때만 Standard SQL을 사용하세요. PostgreSQL cast와 dollar parameter, T-SQL 대괄호 식별자, MySQL backtick, PL/SQL query statement, SQLite 함수, BigQuery 경로에는 맞는 문법을 명시적으로 선택합니다.

  2. 2단계

    원본을 보존하고 민감한 값을 제거합니다

    원본은 version control이나 별도 fixture에 유지하고 정확한 byte가 중요하면 checksum을 기록합니다. 브라우저, clipboard, 화면 캡처, 다운로드를 사용하기 전에 비밀번호, token, 개인정보, tenant 식별자, 비공개 hostname, 독점 literal, 운영 key를 대표 비밀 없는 값으로 바꾸세요. 비식별화 과정에서 placeholder와 인용 규칙을 바꾸지 마세요.

  3. 3단계

    명시적이고 재현 가능한 설정으로 수동 포맷합니다

    SQL 포매터에서 방언, 키워드 대소문자, 2·4·8칸 들여쓰기를 선택한 뒤 수동 모드로 실행합니다. 입력은 UTF-8 1,000,000바이트, 20,000줄, 한 줄당 UTF-8 100,000바이트 안으로 유지하세요. 다른 검토자나 CI가 같은 자산을 재현하도록 sql-formatter 15.8.2와 정확한 옵션을 기록합니다.

  4. 4단계

    parse와 미지원 언어 경계에서 중단합니다

    보고된 줄과 열을 사용해 선택한 방언, 닫히지 않은 따옴표·괄호, template 문법, 미지원 token을 확인합니다. 출력이 나올 때까지 원본을 삭제하지 마세요. 저장 procedure·function·trigger와 사용자 지정 DELIMITER script는 브라우저 포매터 계약 밖이므로 DB 제조사의 parser, IDE, migration 도구로 옮깁니다.

  5. 5단계

    시각 layout만 보지 말고 의미 diff를 검토합니다

    공백을 포함한 보기와 무시한 보기로 원본과 후보를 비교합니다. 식별자, 인용 이름, literal, placeholder, cast, operator, 주석, optimizer hint, semicolon, 문장 순서가 유지되었는지 확인하세요. build 도구가 읽는 주석, 주변 공백이 중요한 생성 fragment, 인용 text 안의 우발적인 키워드·data type 변경을 특히 확인합니다.

  6. 6단계

    실제 DB와 애플리케이션에서 동작을 증명합니다

    올바른 서버 버전과 schema에서 후보를 parse 또는 prepare한 뒤 안전한 대표 data로 실행합니다. 결과 row와 type, 오류, 영향받은 row 수, 권한, transaction, lock, rollback을 비교하세요. 성능이 중요하면 EXPLAIN이나 vendor plan 도구를 사용하고 승인 전에 application, migration, integration, timezone, collation, NULL, regression test를 실행합니다.

  7. 7단계

    검증된 style과 rollback 경로를 자동화합니다

    대표 query가 통과한 뒤 저장소에 포매터 버전과 설정을 고정하고 방언별·미지원 경계별 fixture를 추가합니다. CI가 운영 migration을 조용히 다시 쓰지 않고 포맷 drift를 탐지하게 하세요. 기존 migration history와 생성 diff를 보존하고 rollback의 정확한 조건과 명령을 정의합니다.

예시

그룹 query를 포맷한 뒤 검토 schema에서 검증

입력

select c.id, c.name, count(o.id) as order_count from customers c left join orders o on o.customer_id = c.id and o.status = 'paid' where c.active = true group by c.id, c.name having count(o.id) > 2 order by order_count desc;

출력

SELECT
  c.id,
  c.name,
  count(o.id) AS order_count
FROM
  customers c
  LEFT JOIN orders o ON o.customer_id = c.id
  AND o.status = 'paid'
WHERE
  c.active = TRUE
GROUP BY
  c.id,
  c.name
HAVING
  count(o.id) > 2
ORDER BY
  order_count DESC;

흔한 실수

vendor 문법에 일반 방언 사용

일반 문법은 유효한 vendor token을 거절하거나 잘못 배치할 수 있습니다. 먼저 DB를 선택하고 비밀 없는 최소 fixture로 오류를 재현하세요.

포맷된 SQL을 검증된 SQL이라 부르기

포맷은 schema 해석, 저장 routine compile, 권한 검사, 실행, 결과 비교, 실행 plan 증명을 하지 않습니다. 이 gate를 별도로 유지하세요.

포매터 통과를 위해 문법 제거

cast, hint, template marker, delimiter, routine 문법을 지우면 보기 좋지만 다른 SQL이 됩니다. 원본을 보존하고 미지원 입력을 적절한 vendor 도구로 옮기세요.

공백 무시 diff만 검토

공백 무시 보기는 사라진 주석, 바뀐 인용 text, 문장 순서, semicolon을 숨길 수 있습니다. 정확한 비교와 의미 비교를 모두 사용하세요.

후보를 운영에서 바로 실행

대표 schema와 안전한 data가 있는 격리 DB를 먼저 사용합니다. 운영 실행 전 plan, 권한, transaction, lock, migration, test, rollback을 검증하세요.

자주 묻는 질문

같은 SQL이 방언에 따라 다르게 포맷되는 이유는 무엇인가요?

각 문법이 인식하는 인용 식별자, parameter, operator, data type, function, 예약어가 다릅니다. 선택한 방언이 token 분류와 layout을 바꾸므로 자산 설정에 함께 기록해야 합니다.

포매터가 SQL 문법 유효성을 증명할 수 있나요?

아닙니다. 일부 parse 실패를 보고할 수 있지만 문법과 완전성 검사는 대상 DB 서버와 같지 않습니다. 올바른 서버 버전, schema, client, 실행 문맥만 전체 문장을 검증할 수 있습니다.

SQL 포맷 후 주석이 유지되나요?

이 포매터는 지원되는 줄·block 주석 token을 유지하지만 위치는 이동할 수 있습니다. optimizer hint, migration directive, license text, 다른 도구가 소비하는 주석은 정확한 diff로 확인하세요.

생성 SQL이나 template은 어떻게 처리해야 하나요?

가능하면 완전히 render하고 비식별화한 query를 포맷하세요. template marker는 SQL 문법이 아닐 수 있고 주변 문맥 없는 fragment 포맷은 공백 계약을 바꿀 수 있습니다. 생성 test를 유지하고 최종 prepared statement를 별도로 비교하세요.

SQL 포맷은 언제 CI에서 실행해야 하나요?

버전과 설정을 고정하고, 지원 방언별 대표 query를 검토하고, routine·template 제외 규칙을 정하고, CI가 migration history를 예기치 않게 다시 쓰지 않고 drift만 보고하도록 만든 뒤 실행하세요.

포맷 전용 pull request에 어떤 증거가 필요한가요?

DB와 버전, source 역할, 비식별화 기록, 포매터 버전·설정, 정확한 diff·의미 diff, parser 또는 prepare 결과, 대표 결과 비교, 필요한 EXPLAIN, application·migration test, 권한·transaction 검사, rollback 조건을 포함하세요.