SQL 포맷터
SQL 쿼리를 정렬하거나 한 줄로 압축
처리 중…
0.0
멈춘 것이 아닙니다. 계산이 끝나면 결과가 표시됩니다.
SQL 포맷터 소개
이 포맷터는 SQL을 문법적으로 해석하는 파서가 아니라, 문자열을 토큰(키워드·식별자·숫자·문자열·주석·기호)으로 쪼갠 뒤 규칙에 따라 줄을 바꾸는 경량 도구입니다. SELECT, FROM, WHERE, JOIN, GROUP BY 같은 절마다 줄을 바꾸고, 알려진 키워드를 대문자로 바꾸고, SELECT 목록의 쉼표와 WHERE의 AND/OR마다 줄을 나눕니다. 작은따옴표 문자열과 주석 안은 건드리지 않습니다.
파서가 아니기 때문에 쿼리가 맞는지 틀리는지는 판단하지 않고, 데이터베이스마다 다른 문법(방언)을 모두 알지 못합니다. 아래에서는 MySQL·PostgreSQL·SQL Server 전용 문법 중 어떤 것을 그대로 보존하는지, 그리고 들여쓰기로는 해결되지 않는 문제를 다룹니다.
방언 문법을 어떻게 다루나
표준 SQL에서 작은따옴표는 문자열, 큰따옴표는 식별자(열·테이블 이름)입니다. 문자열 안의 작은따옴표는 두 번 써서 'It''s'로 표현합니다. 이 도구는 이 규칙과 MySQL의 백틱 식별자 `id`를 인식하고, 아래의 방언 문법도 한 덩어리로 보존합니다.
- MySQL의 백슬래시 이스케이프 문자열('It\'s')은 내용을 바꾸지 않고 그대로 둡니다. 백슬래시로 해석해서 문자열이 맞게 닫히지 않으면 표준 SQL 규칙('' 이스케이프)으로 다시 읽습니다.
- MySQL의 # 한 줄 주석을 주석으로 인식합니다. 압축해도 # 주석은 지워지므로 다음 줄 코드가 주석에 먹히지 않습니다. PostgreSQL의 #>, #>>, #- 는 주석이 아니라 연산자로 둡니다.
- PostgreSQL의 ->>, ->, @>, <@, ||, ::(형 변환)은 쪼개지 않고 하나의 연산자로 둡니다. ::는 앞뒤 공백 없이 created_at::date처럼 붙입니다. $$ ... $$나 $tag$ ... $tag$로 감싼 본문은 문자열로 보고 건드리지 않습니다.
- 문자열에 붙어 있는 기호는 원래 간격을 유지합니다. 예를 들어 array['a']는 그대로 남습니다.
- SQL Server의 대괄호 식별자 [order id]는 공백을 포함한 채 그대로 둡니다.
- 압축하면 -- 와 /* */ 주석은 지우지만, 옵티마이저 힌트 /*+ ... */와 MySQL의 /*! ... */ 실행 주석은 남깁니다. 이 둘은 주석 형태여도 실행 계획이나 동작에 영향을 주기 때문입니다.
들여쓰기가 고쳐 주지 못하는 것
가장 흔한 것은 AND와 OR의 우선순위입니다. SQL에서 AND가 OR보다 먼저 묶이므로 a = 1 OR b = 2 AND c = 3은 a = 1 OR (b = 2 AND c = 3)입니다. 포맷터는 AND와 OR를 같은 깊이로 나란히 세워 주기 때문에 오히려 「세 조건이 대등하다」고 읽히기 쉽습니다. 의도가 (a = 1 OR b = 2) AND c = 3이라면 괄호를 직접 넣어야 합니다.
WHERE 절에 오른쪽 테이블 조건을 둔 LEFT JOIN도 같습니다. 예시 1처럼 LEFT JOIN orders 뒤 WHERE에서 o.created_at을 거르면, 주문이 없는 사용자 행(o.* 가 NULL)이 걸러져 결과가 INNER JOIN과 같아집니다. 주문 없는 사용자까지 남기려면 그 조건을 ON 쪽으로 옮겨야 합니다. 줄을 정리하면 이런 문제가 눈에 잘 띄지만, 포맷터가 알려 주지는 않습니다.
= NULL은 항상 참이 아니라 UNKNOWN이 되어 아무 행도 돌려주지 않습니다. IS NULL을 써야 합니다. 이런 논리 오류는 포맷 전후가 똑같이 「유효한 SQL」입니다.
도구가 바꾸는 것과 안 바꾸는 것
- SELECT, FROM, LEFT JOIN, GROUP BY, CASE, AS 같은 목록에 있는 키워드만 대문자가 됩니다. count, coalesce, date 같은 함수·형 이름은 입력한 대로 남습니다.
- 들여쓰기 폭은 공백 수로 정합니다. 서브쿼리는 괄호 깊이만큼 한 단계 더 들여씁니다.
- 압축은 공백을 하나로 줄이고 주석을 지웁니다(힌트 /*+ */와 /*! */는 남김). 문자열 안의 연속 공백('x y')은 그대로 둡니다.
예시
한 줄 쿼리 정리
- 입력
select u.id, u.name, count(o.id) as cnt from users u left join orders o on o.user_id = u.id where u.active = 1 and o.created_at >= '2026-01-01' group by u.id, u.name having count(o.id) > 3 order by cnt desc limit 10;- 결과
SELECT u.id, u.name, count(o.id) AS cnt FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE u.active = 1 AND o.created_at >= '2026-01-01' GROUP BY u.id, u.name HAVING count(o.id) > 3 ORDER BY cnt DESC LIMIT 10;
정리하고 나면 WHERE의 o.created_at 조건이 LEFT JOIN을 사실상 INNER JOIN으로 만든다는 것이 보입니다.
AND/OR가 나란히 보이는 경우
- 입력
select * from t where a = 1 or b = 2 and c = 3- 결과
SELECT * FROM t WHERE a = 1 OR b = 2 AND c = 3
실제 의미는 a = 1 OR (b = 2 AND c = 3)입니다. 줄 모양만 보고 판단하지 마세요.
PostgreSQL 연산자 보존
- 입력
select id, data->>'name' as n, created_at::date from t where tags @> array['a']- 결과
SELECT id, data ->>'name' AS n, created_at::date FROM t WHERE tags @> array['a']
->>, ::, @>는 한 덩어리로 남습니다. 문자열 'name'에 붙어 있던 ->>는 그 사이 간격을 그대로 둡니다.
압축할 때 힌트와 # 주석
- 입력
SELECT /*+ INDEX(o idx_user) */ id FROM o # note WHERE a = 1- 결과
SELECT /*+ INDEX(o idx_user) */ id FROM o WHERE a = 1
옵티마이저 힌트는 남고 # note 주석만 지워집니다. 다음 줄의 WHERE 조건은 그대로 유지됩니다.
사용 방법
- SQL을 붙여 넣습니다. 여러 문장은 ;로 구분됩니다.
- 들여쓰기 폭을 고르고 포맷 또는 압축을 누릅니다.
- 결과를 원본과 비교해 확인한 뒤 사용합니다.
자주 묻는 질문
문법 오류를 찾아 주나요?
아니요. 토큰 단위로 줄만 바꾸므로 틀린 쿼리도 그대로 포맷됩니다. 오류 확인은 데이터베이스의 EXPLAIN이나 실행으로 하세요.
함수 이름은 왜 소문자로 남나요?
대문자로 바꾸는 것은 내장 키워드 목록에 있는 단어뿐입니다. count, coalesce 같은 함수는 입력한 대로 둡니다.
압축한 SQL을 코드에 넣어도 되나요?
쓸 수는 있지만 일반 주석은 지워지고 힌트(/*+ */)와 MySQL 실행 주석(/*! */)만 남습니다. 필요한 주석이 있는지 확인하세요. 성능상 이점은 거의 없습니다.
PostgreSQL 연산자도 지원하나요?
->>, ->, @>, <@, ||, ::와 $$ 달러 인용은 쪼개지 않고 그대로 둡니다. 방언 설정은 따로 없으므로, 목록에 없는 전용 문법이 있다면 결과를 원본과 비교하세요.
출처
- MySQL 8.0 Reference Manual: Comments
- MySQL 8.0 Reference Manual: String Literals
- PostgreSQL: JSON Functions and Operators
- PostgreSQL: Operator Precedence
- Microsoft Learn: Database identifiers (SQL Server)
기준일: