sql format beautify database developer-tools

SQL 포맷터: SQL 쿼리를 즉시 보기 좋게 정리하세요

온라인 SQL 포맷터로 SQL 쿼리의 가독성을 높이세요. 복잡한 데이터베이스 스크립트를 쉽게 포맷하고, 보기 좋게 만들고, 정리하세요.

소개

데이터베이스를 다루는 개발자라면 누구나 포맷이 지정되지 않은 SQL의 벽을 경험해 본 적이 있을 것입니다. 키워드는 모두 소문자, 줄 바꿈 없음, 테이블 이름과 JOIN 조건이 붙어 있고, 오른쪽으로 끝없이 이어지는 한 줄의 코드. 디버깅은 거의 불가능하고, Pull Request에서 리뷰하기도 어려우며, 동료에게 인계하기도 힘듭니다. SQL 포맷팅은 단순한 미관상의 문제가 아닙니다. 가독성을 높이고, 버그를 줄이며, 팀 표준을 적용하고, 유지보수를 크게 쉽게 만드는 전문적인 규율입니다.

간단한 5줄짜리 SELECT문을 작성하든, CTE와 윈도우 함수를 포함한 100줄짜리 분석 파이프라인을 작성하든, 일관된 포맷팅은 코드가 의도를 전달하느냐 아니면 숨기느냐를 결정하는 차이입니다.

SQL의 간략한 역사

SQL은 1970년대 초 IBM의 산호세 연구소에서 탄생했습니다. Edgar F. Codd의 관계형 모델에서 영감을 받은 Donald D. Chamberlin과 Raymond F. Boyce가 SEQUEL(Structured English Query Language)이라는 언어를 개발했고, 나중에 SQL로 이름이 바뀌었습니다. IBM은 System R에서 이를 구현했고, Oracle은 1979년에 최초로 상용 SQL 데이터베이스를 판매한 회사가 되었습니다.

미국 국가 표준 협회(ANSI)는 1986년에 첫 번째 SQL 표준을 발표했고, ISO가 비준했습니다. 이후 SQL-89, SQL-92, SQL:1999, SQL:2003, SQL:2008, SQL:2011, SQL:2016 등의 개정판이 나오면서 트리거, 저장 프로시저, 재귀 쿼리(CTE), 윈도우 함수, JSON 지원 등의 기능이 추가되었습니다.

표준이 있음에도 불구하고, 주요 데이터베이스 벤더들은 자체 확장 기능을 도입하여 각자의 방언을 만들어냈습니다. MySQL, PostgreSQL, Microsoft SQL Server(T-SQL), SQLite, Oracle PL/SQL 등이 그 예입니다. 이러한 방언들은 공통 핵심을 공유하지만, 문자열 연산, 날짜 함수, 자동 증가 열 등의 구문에서 차이를 보입니다.

SQL 구문 기초

포맷팅을 이해하기 전에 기본 구성 요소를 파악하면 도움이 됩니다. 표준 SELECT 문은 다음 논리적 순서를 따릅니다:

SELECT   -- 반환할 열
FROM     -- 소스 테이블
JOIN     -- 관련 테이블
WHERE    -- 행 수준 필터
GROUP BY -- 집계 그룹화
HAVING   -- 집계 수준 필터
ORDER BY -- 정렬 순서
LIMIT    -- 행 수 제한

각 절에는 명확한 목적이 있습니다. WHERE는 집계 전에 필터링하고, HAVING은 집계 후에 필터링합니다. GROUP BY는 여러 행을 그룹당 하나의 행으로 축소합니다. ORDER BY는 마지막으로 적용됩니다(LIMIT 이전). 이 순서를 이해하면 각 절이 새 줄에서 시작되도록 쿼리를 포맷팅할 수 있어 논리적 흐름을 즉시 파악할 수 있습니다.

포맷팅 규칙

단일한 보편적 표준은 없지만, 다음과 같은 규칙이 널리 채택되고 있습니다:

키워드 대문자화 대부분의 스타일 가이드는 SQL 키워드(SELECT, FROM, WHERE, JOIN, GROUP BY, HAVING, ORDER BY, LIMIT)를 대문자로 작성할 것을 권장합니다. 이를 통해 구조적 키워드와 사용자 정의 식별자 사이에 시각적 대비가 생깁니다. 일부 현대적인 팀은 미관상의 이유로 소문자를 선호하며, dbt 같은 도구들이 이 스타일을 대중화했습니다. 핵심 규칙은 일관성을 유지하는 것입니다.

들여쓰기 열 목록, ON 절, WHERE 조건에는 4칸 공백(또는 컴팩트 스타일에서는 2칸 공백)을 사용합니다. 탭은 에디터마다 렌더링이 다르기 때문에 공유 코드베이스에서는 피해야 합니다.

줄 바꿈 각 주요 절(SELECT, FROM, WHERE, GROUP BY 등)은 새 줄에서 시작해야 합니다. SELECT의 열 목록은 키워드 아래에 들여쓰기하여 한 열씩 배치합니다. 이렇게 하면 개별 열을 추가, 제거 또는 주석 처리하기 쉬워집니다.

쉼표 위치 두 가지 흐름이 있습니다: 후행 쉼표(행 끝)와 선행 쉼표(다음 행 시작). 후행 쉼표는 대부분의 프로그래머에게 더 자연스럽고, 선행 쉼표는 누락된 쉼표를 한눈에 발견하기 쉽습니다. 하나를 선택하여 일관되게 사용하세요.

별칭 지정 별칭을 지정할 때는 항상 AS 키워드를 사용합니다(u user 대신 u AS user). 이렇게 하면 의도가 명확해지고 복잡한 쿼리에서 모호함을 방지할 수 있습니다.

포맷팅 전후 비교 예시

-- 포맷팅 전 (미포맷)
select u.id,u.name,o.total from users u inner join orders o on u.id=o.user_id where o.total>100 and u.active=1 order by o.total desc limit 10

-- 포맷팅 후
SELECT
    u.id,
    u.name,
    o.total
FROM
    users u
    INNER JOIN orders o ON u.id = o.user_id
WHERE
    o.total > 100
    AND u.active = 1
ORDER BY
    o.total DESC
LIMIT 10

포맷팅된 버전은 테이블 관계, 필터 조건, 정렬 순서를 즉시 파악할 수 있게 해줍니다.

SQL 방언 비교

SQL은 표준화되어 있지만, 방언 간에 중요한 차이가 있습니다:

기능 MySQL PostgreSQL SQL Server SQLite
문자열 연결 CONCAT() || 또는 CONCAT() + 또는 CONCAT() ||
자동 증가 AUTO_INCREMENT SERIAL / IDENTITY IDENTITY AUTOINCREMENT
행 수 제한 LIMIT n LIMIT n TOP n LIMIT n
날짜 함수 DATE(), NOW() CURRENT_DATE, NOW() GETDATE() DATE()
대소문자 구분 기본: 구분 없음 기본: 구분 있음 기본: 구분 없음 설정 가능

데이터베이스 간에 쿼리를 이식할 때 이러한 차이를 이해하는 것이 중요합니다. 대상 방언을 인식하는 포맷터는 방언별 구문에 대해서도 구문적으로 유효한 출력을 생성할 수 있습니다.

SQL 스타일 가이드

팀이 합의를 이루는 데 도움이 되는 두 가지 널리 참조되는 스타일 가이드가 있습니다:

Simon Holywell의 SQL 스타일 가이드 (sqlstyle.guide) 이 가이드는 루트 단어 정렬, 공백의 "강"을 사용한 절의 시각적 분리, 선행 쉼표, 식별자에 snake_case 사용을 강조합니다. 의견이 강하지만 철저하며 데이터 엔지니어링 팀에서 널리 사용됩니다.

Google SQL 스타일 가이드 Google의 내부 스타일 가이드(BigQuery 문서를 통해 부분적으로 공개)는 후행 쉼표, 4칸 들여쓰기, 대문자 키워드, 긴 IN 목록 줄 바꿈을 선호합니다. 많은 Google 엔지니어에게 익숙한 Java 코딩 표준과 잘 맞습니다.

대부분의 팀은 이 중 하나를 기반으로 가벼운 내부 스타일 가이드를 만들고 CI 파이프라인에서 자동 포맷팅을 통해 이를 시행합니다.

SQL 포맷터의 작동 원리

SQL 포맷터는 본질적으로 특수화된 컴파일러 프런트엔드입니다. 프로세스는 일반적으로 세 단계를 포함합니다:

1. 어휘 분석 (Tokenization) 원시 SQL 문자열이 플랫한 토큰 스트림으로 분해됩니다: 키워드(SELECT, FROM), 식별자(users, u), 연산자(=, >), 리터럴('completed', 100), 구두점(,, ;), 공백/주석. 어휘 분석기는 구조를 이해하지 못합니다 — 문자를 분류할 뿐입니다.

2. 구문 분석 (Parsing) 토큰 스트림이 추상 구문 트리(AST)를 구축하는 파서에 입력됩니다. AST의 각 노드는 논리적 구조를 나타냅니다: SelectStatement 노드에는 ColumnList 노드, FromClause 노드, WhereClause 노드 등이 포함됩니다. 파서는 문법 규칙을 적용하고 구문 오류를 감지합니다.

3. 재출력 (Pretty Printing) 포맷터는 AST를 순회하고 포맷팅 규칙에 따라 SQL 텍스트를 출력합니다: 절 키워드 뒤의 줄 바꿈, 자식 노드 들여쓰기, 연산자 주변의 공백, 키워드 대소문자 변환. 출력이 AST에서 파생되기 때문에 쿼리의 의미론적 의미가 완벽하게 보존됩니다.

일부 간단한 포맷터는 완전한 AST 구축을 건너뛰고 토큰 스트림에 직접 규칙 기반 변환을 적용합니다. 이 방식은 더 빠르지만 복잡한 중첩 구조에 대해서는 정확도가 낮습니다.

일반적인 SQL 패턴과 예시

다중 테이블 JOIN

SELECT
    c.name AS customer_name,
    p.name AS product_name,
    oi.quantity,
    oi.unit_price
FROM
    customers c
    INNER JOIN orders o ON c.id = o.customer_id
    INNER JOIN order_items oi ON o.id = oi.order_id
    INNER JOIN products p ON oi.product_id = p.id
WHERE
    o.status = 'shipped'
    AND o.created_at >= '2024-01-01'
ORDER BY
    c.name,
    o.created_at DESC;

서브쿼리

SELECT
    department,
    AVG(salary) AS avg_salary
FROM employees
WHERE salary > (
    SELECT AVG(salary)
    FROM employees
)
GROUP BY department
ORDER BY avg_salary DESC;

CTE (공통 테이블 식)

WITH monthly_revenue AS (
    SELECT
        DATE_TRUNC('month', created_at) AS month,
        SUM(amount) AS revenue
    FROM orders
    WHERE status = 'completed'
    GROUP BY 1
),
ranked_months AS (
    SELECT
        month,
        revenue,
        RANK() OVER (ORDER BY revenue DESC) AS rank
    FROM monthly_revenue
)
SELECT *
FROM ranked_months
WHERE rank <= 3;

CTE는 복잡한 쿼리를 명명된 재사용 가능한 빌딩 블록으로 분해하여 가독성을 향상시킵니다. 중간 집계가 추가 계산에 사용되는 분석 SQL에서 특히 유용합니다.

윈도우 함수

SELECT
    employee_id,
    department,
    salary,
    AVG(salary) OVER (PARTITION BY department) AS dept_avg,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;

윈도우 함수(OVER를 사용하는 ROW_NUMBER, RANK, LAG, LEAD, SUM, AVG)는 SQL의 가장 강력한 기능 중 하나입니다. 적절한 포맷팅 — 각 OVER 절을 해당 함수와 같은 줄에, PARTITION BYORDER BY를 명확하게 표시 — 은 가독성에 필수적입니다.

HAVING을 사용한 집계

SELECT
    category,
    COUNT(*) AS total_products,
    AVG(price) AS avg_price,
    MAX(price) AS max_price
FROM products
WHERE active = 1
GROUP BY category
HAVING COUNT(*) > 10
ORDER BY avg_price DESC;

IDE 및 도구와의 통합

VS Code sql-formatter npm 패키지는 여러 VS Code 확장 기능을 지원합니다. settings.json에서 저장 시 포맷팅을 설정할 수 있습니다:

"[sql]": { "editor.defaultFormatter": "mtxr.sqltools" }

JetBrains DataGrip DataGrip에는 코드 → 코드 재포맷(Ctrl+Alt+L) 아래에 내장 SQL 포맷팅 기능이 있습니다. 스타일 옵션은 설정 → 에디터 → 코드 스타일 → SQL에서 데이터 소스 방언별로 구성할 수 있습니다.

DBeaver DBeaver는 SQL 에디터 → SQL 포맷을 통해 SQL 포맷팅을 지원하며, 환경 설정 → 에디터 → SQL 에디터 → 포맷팅에서 사용자 정의 포맷터 설정이 가능합니다.

명령줄 sql-formatter npm 패키지는 CLI 도구로 사용할 수 있습니다:

npx sql-formatter --language postgresql --indent 4 query.sql

모범 사례

  1. 일찍, 자주 포맷팅하세요. 코드 리뷰 전에만 아니라, 매 커밋 전에 포맷터를 실행하세요.
  2. 먼저 스타일 가이드에 동의하세요. 어떤 포맷터도 규칙에 대한 팀의 합의를 대체할 수 없습니다.
  3. 별칭을 일관되게 사용하세요. 짧은 별칭(1-2자)은 간단한 쿼리에 적합하고, 복잡한 쿼리에서는 설명적인 별칭이 가독성을 향상시킵니다.
  4. CTE에 설명적인 이름을 붙이세요. cte1이나 tmp보다 monthly_revenue가 훨씬 낫습니다.
  5. 복잡한 로직에 주석을 달아요. 자명하지 않은 비즈니스 규칙을 설명하는 인라인 주석이 있는 잘 포맷된 쿼리는 영리하지만 침묵하는 SQL보다 훨씬 유지보수하기 쉽습니다.
  6. CI에서 SQL을 린팅하세요. sqlfluff 같은 도구는 Pull Request 파이프라인에서 자동으로 포맷팅 규칙을 적용할 수 있습니다.
  7. CASE WHEN을 읽기 쉽게 유지하세요.CASE WHEN 체인은 여러 줄로 나누고, 각 분기를 한 줄에 배치합니다.
  8. 모든 열 참조를 한정하세요. 다중 테이블 쿼리에서는 항상 테이블 별칭으로 열 이름을 접두사로 붙이세요(id만 대신 u.id).

자주 묻는 질문

포맷팅이 쿼리의 동작을 변경하나요? 아니요. 포맷터는 공백, 줄 바꿈, 키워드 대소문자만 변경합니다. 논리 구조와 실행 계획은 동일하게 유지됩니다.

포맷터가 구문 오류를 수정할 수 있나요? 아니요. 포맷터가 올바르게 파싱하려면 구문적으로 유효한(또는 거의 유효한) SQL이 필요합니다. 오류를 감지하고 설명하려면 sqlfluff 같은 린터를 사용하세요.

어떤 키워드 대소문자 스타일을 사용해야 하나요? 대문자 키워드는 SQL 커뮤니티와 스타일 가이드에서 가장 전통적이고 널리 권장되는 관례입니다. 그러나 dbt와 현대 데이터 도구에서는 소문자가 점점 더 많이 사용되고 있습니다. 팀을 위해 하나를 선택하고 자동으로 적용하세요.

저장 프로시저와 PL/SQL 블록을 지원하나요? 지원 수준은 다양합니다. 대부분의 포맷터는 DML(SELECT, INSERT, UPDATE, DELETE)을 잘 처리합니다. 절차적 확장(PL/SQL, T-SQL 배치, BEGIN...END 블록)은 방언 인식 파서가 필요하며 지원 품질이 다양합니다.

온라인 포맷터를 사용할 때 SQL 데이터가 안전한가요? 좋은 온라인 포맷터는 JavaScript를 사용하여 브라우저에서 클라이언트 측으로 모든 것을 처리합니다. 쿼리가 귀하의 기기를 떠나는 일은 없습니다. 민감한 스키마 정보를 붙여넣기 전에 항상 도구의 개인 정보 보호 정책에서 이를 확인하세요.

포맷터와 린터의 차이점은 무엇인가요? 포맷터는 SQL의 의미를 변경하지 않고 표현을 변환합니다. 린터(sqlfluff, SQLCheck)는 스타일 위반, 안티패턴, 잠재적 버그에 대해 SQL을 분석하고 자동 수정이 아닌 문제를 보고합니다.