SQL 스타일 가이드: 가독성 높은 쿼리 포매팅 모범 사례
SQL 스타일 가이드는 쿼리를 읽기 쉽게 만들고 팀의 diff를 일관되게 유지하는 규칙의 모음입니다. SQL 자체는 이런 것에 신경 쓰지 않습니다. 키워드는 대소문자를 구분하지 않고 공백 문자는 무시되므로, SELECT, select, SeLeCt는 모두 똑같이 실행되고, 200자짜리 한 줄 쿼리는 같은 쿼리를 스무 줄로 들여쓰기해 펼친 것과 정확히 동일한 행을 반환합니다. 따라서 스타일은 오로지 나중에 그 쿼리를 읽을 사람을 위한 것입니다.
지금 당장 깔끔한 쿼리가 필요할 뿐이라면, SQL 포매터에 붙여넣고 방언을 고른 뒤 결과를 복사하세요. 하지만 그 출력 뒤에 숨은 규칙을 이해해야 매번 풀 리퀘스트에서 논쟁하는 대신 팀 표준을 정할 수 있습니다. 이 가이드는 중요한 선택들을 하나씩 짚어 갑니다. 키워드 대소문자, 들여쓰기와 줄바꿈, 명명, 방언별 차이, 그리고 이 모든 것을 자동화하는 방법입니다.
세부 내용에 들어가기 전에 한 가지 틀을 잡고 가겠습니다. SQL은 공백 문자와 대소문자를 무시하므로, 이 규칙들은 어느 것도 데이터베이스가 강제하지 않습니다. 모두 쿼리를 읽고 리뷰하고 유지보수하는 사람을 위해서만 존재합니다. 여기서 두 가지 결론이 따라옵니다. 첫째, “정답”이 하나뿐인 경우는 드뭅니다. 이 결정들은 대부분 합리적인 규칙을 정해 어디에나 똑같이 적용하는 문제이며, 이 가이드는 한 가지 스타일이 무조건 이긴다고 꾸미는 대신 진짜 트레이드오프가 어디에 있는지 솔직하게 밝힙니다. 둘째, 규칙은 요구사항이 아니라 관례이므로 일관되게 적용될 때에만 가치가 생깁니다. 그래서 모든 절은 결국 같은 결론을 가리킵니다. 한 번 정하고, 그다음은 도구가 강제하게 하면 됩니다.
SQL 포매팅이 중요한 이유
포매팅의 가장 분명한 근거는 코드 리뷰에서 드러납니다. ORM이나 빌드 단계는 쿼리를 끊김 없는 한 줄로 내보내는 경우가 많습니다.
select u.id,u.name,count(o.id) as orders from users u left join orders o on o.user_id=u.id where u.active=true group by u.id,u.name order by orders desc
아무도 그것을 리뷰할 수 없습니다. 다시 포매팅하면 구조가 한눈에 보이고 diff도 한 줄씩 리뷰할 수 있습니다.
SELECT
u.id,
u.name,
COUNT(o.id) AS orders
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.active = true
GROUP BY u.id, u.name
ORDER BY orders DESC;
디버깅도 같은 방식으로 이득을 봅니다. 느린 쿼리 로그에서 한 줄짜리 쿼리를 복사했는데 JOIN이 세 개에 WHERE가 뒤엉켜 있을 때, 먼저 포매팅하면 “버그가 어디지”가 30초짜리 훑어보기로 바뀝니다. 잘못된 조건식은 자기 줄에 따로 놓이고, JOIN은 차곡차곡 쌓이며, 실수로 생긴 카테시안 곱이나 빠뜨린 필터가 텍스트 더미에 묻히는 대신 갑자기 눈에 보입니다. 같은 요령은 다른 시스템이 생성한 SQL을 읽을 때도 도움이 됩니다. 쿼리 빌더와 리포팅 도구는 정확하지만 읽기 어려운 출력을 내놓기로 악명이 높습니다.
일관성은 더 조용한 이득이지만, 시간이 지날수록 가장 값진 이득입니다. 모두가 같은 방식으로 포매팅하면 diff는 실제로 바뀐 것만 보여 줍니다. 새 열, 수정된 필터 같은 것 말입니다. 누군가의 띄어쓰기 취향이 다른 사람의 취향과 다투면서 생기는 잡음은 사라집니다. 리뷰어의 집중력은 유한하며, 그것을 재배치된 공백에 쓰는 것은 낭비입니다. 일관된 포매팅은 온보딩도 쉽게 만듭니다. 새로 들어온 사람은 모두 비슷하게 생긴 쿼리를 읽고, 팀의 모양을 한 번 익혀 어디에나 적용합니다. 이 가운데 어느 것도 손으로 포매팅할 필요가 없으며, 이것이 마지막 절의 주제입니다. 규칙은 여러분이 정하고, 적용은 도구가 합니다.
이 모든 것의 바탕에는 한 가지 불변 규칙이 있고, 이 가이드 전반에서 반복되므로 분명히 짚어 둘 만합니다. 포매팅은 공백 문자, 줄바꿈, 키워드 대소문자, 주석만 바꿉니다. 쿼리의 로직이나 결과는 절대 바꾸지 않습니다. 포매팅된 쿼리는 같은 쿼리입니다. 그래서 위의 지저분한 버전을 SQL 포매터에 돌려도 무엇이 반환될지 걱정할 필요가 없습니다.
키워드 대소문자 — UPPERCASE vs lowercase
어떤 SQL 스타일 가이드에서나 가장 오래된 논쟁은 예약 키워드를 UPPERCASE로 쓸지 lowercase로 쓸지입니다. 둘 다 유효합니다. SQL은 키워드에 대해 대소문자를 구분하지 않기 때문입니다. 의견 차이는 가독성에 관한 것이며, 고르기 전에 양쪽을 이해해 둘 가치가 있습니다.
UPPERCASE 키워드를 지지하는 입장
전통적인 논거는 시각적 대비입니다. SELECT, FROM, WHERE, JOIN, GROUP BY를 대문자로 쓰면 키워드가 소문자 테이블·열 이름에서 도드라지므로, 에디터가 색을 입혀 주지 않아도 쿼리의 모양, 즉 절과 구조를 훑어볼 수 있습니다.
이것은 들리는 것보다 더 중요합니다. SQL은 구문 강조가 없는 여러 곳을 거치기 때문입니다. 로그 파일, 이메일 스레드, 풀 리퀘스트 설명, 일반 텍스트 diff, Slack 메시지, 모니터링 대시보드, 스택 트레이스 같은 곳입니다. 이 모든 곳에서 대문자 키워드는 구조를 알아볼 수 있게 해 주는 유일한 단서입니다. 강조를 걷어내면 select id from users where active는 소문자 단어의 죽처럼 보이지만, SELECT id FROM users WHERE active는 한눈에 봐도 여전히 쿼리로 읽힙니다. 이것은 대부분의 오래된 스타일 가이드, 데이터베이스 문서, 그리고 거의 모든 SQL 교재에서 볼 수 있는 관례이며, 에디터가 어차피 색을 입혀 줄 때조차 많은 개발자가 대문자 키워드를 더 익숙하게 느끼는 이유이기도 합니다.
lowercase 키워드를 지지하는 입장
현대적인 반론은 구문 강조가 대비 문제를 이미 해결했다는 것입니다. 모든 에디터와 IDE가 키워드를 구분해 색칠하므로 대문자로 쓰는 것은 중복이며, 어떤 독자에게는 전부 대문자가 고함치는 것처럼 읽힙니다. 또한 키워드마다 shift를 누르지 않아도 되니 타이핑이 살짝 빠릅니다.
소문자 스타일은 분석 엔지니어링 세계에서 실질적인 추진력을 얻고 있습니다. dbt 커뮤니티와 널리 인용되는 여러 팀 스타일 가이드는 소문자 키워드를 기본값으로 삼는데, 강조가 시각적 무게를 짊어지므로 소문자가 쿼리를 더 차분하게 읽히게 한다는 논리입니다. 그들에게 유리한 더 미묘한 지점도 있습니다. 소문자 키워드는 snake_case 테이블·열 이름과 같은 시각적 수준에 놓이므로, 쿼리 전체가 두 가지 어조, 즉 고함치는 키워드와 조용한 식별자가 주의를 다투는 모습 대신 하나의 일관된 텍스트로 읽힙니다. 그것이 장점인지 단점인지는 팀마다 의견이 갈리는 바로 그런 종류의 문제이며, 그래서 실제로 들어맞는 유일한 결론으로 이어집니다.
결론 — 선택보다 일관성이 이긴다
실제로 중요한 부분은 이것입니다. 어느 쪽을 고르느냐는 하나를 골라 강제하는 것보다 훨씬 덜 중요합니다. 쿼리의 절반은 SELECT라고 외치고 나머지 절반은 select라고 속삭이는 코드베이스가 최악의 결과입니다. 불일치 그 자체가 잡음이 되기 때문입니다. 한 쿼리 안에서 대소문자가 섞이면 더 나쁩니다.
일관성이 이기는 이유는 미적인 것이 아니라 기계적인 것입니다. 일관되지 않은 대소문자는 diff가 거짓말을 하게 만듭니다. 리뷰어는 사실 누군가 키워드만 다시 쓴 줄을 “변경”으로 보게 되고, 진짜 변경은 잡음 속에 숨습니다. 같은 키워드가 세 가지 대소문자로 나타나면 grep과 검색도 덜 믿음직해집니다. 하나로 강제된 스타일은 결정 한 번이라는 비용으로 이 모든 부담을 없앱니다. 그러니 팀으로서 정하고, 적어 두고, 규율에 의존하는 대신 도구가 강제하게 하세요. SQL 포매터에는 UPPERCASE, lowercase, Preserve 세 가지 옵션이 있는 Keywords 컨트롤이 있어서, 역사적으로 쌓인 쿼리 더미 전체를 한 번의 클릭으로 하나의 스타일로 정규화할 수 있습니다. 같은 쿼리를 양쪽 방식으로 표현하면 이렇습니다.
-- UPPERCASE
SELECT id, email FROM users WHERE active = true ORDER BY created_at DESC;
-- lowercase
select id, email from users where active = true order by created_at desc;
팀이 선호하는 쪽을 고르세요. 핵심은 모든 쿼리가 그것에 맞춰지는 것입니다.
들여쓰기와 줄바꿈
대소문자는 키워드가 어떻게 보일지를 결정합니다. 들여쓰기와 줄바꿈은 쿼리의 로직이 지면에 어떻게 매핑될지를 결정하며, 가독성의 대부분이 바로 여기에 있습니다.
”강(river)” 스타일 vs 블록 스타일
Simon Holywell의 잘 알려진 sqlstyle.guide는 “강(river)” 스타일을 널리 퍼뜨렸습니다. 키워드를 오른쪽 정렬해서 쿼리 가운데로 공백의 세로 통로가 흐르게 하는 방식입니다.
SELECT id,
email,
created_at
FROM users
WHERE active = true
ORDER BY created_at DESC;
매력은 SELECT, FROM, WHERE가 오른쪽 가장자리에서 정렬되고 열 목록이 강 오른쪽에 깔끔하게 자리한다는 점입니다. 하지만 단점은 실용적입니다. 정렬이 가장 긴 키워드의 길이에 의존하므로 LEFT JOIN을 추가하면 전체를 다시 들여쓰기해야 할 수 있고, 손으로 유지하기 고통스러우며, 한 키워드의 길이가 바뀌면 이웃한 줄의 공백이 밀리기 때문에 diff가 어수선해집니다.
블록(또는 왼쪽 정렬) 스타일은 각 주요 절을 자기 줄에서 왼쪽 여백부터 시작하고 절의 내용을 들여씁니다.
SELECT
id,
email,
created_at
FROM users
WHERE active = true
ORDER BY created_at DESC;
이것이 주류 기본값이고 대부분의 도구가 만들어 내는 형태인데, 바로 안정적이기 때문입니다. 절을 추가해도 위의 줄들이 다시 흐트러지지 않으므로 diff가 작게 유지되고 레이아웃이 자동 포매팅을 견딥니다. 강 스타일은 완성된 쿼리가 따로 떼어 놓고 봤을 때 어떻게 보이는지를 최적화합니다. 블록 스타일은 쿼리가 시간이 지나며 어떻게 바뀌는지, 버전 관리에서 어떻게 리뷰되는지를 최적화합니다. 저장소에 살면서 편집되는 것이라면 블록 스타일이 더 안전한 선택이며, 이 가이드의 나머지가 전제하는 방식입니다.
몇 칸으로 — 2칸 vs 4칸 vs 탭
들여쓰기를 하기로 했다면 얼마나 들여쓸지 정해야 합니다. 흔한 세 가지 답에는 각각 나름의 근거가 있습니다.
- 2칸 — 가장 흔한 기본값입니다. diff를 작게 유지하고 중첩된 쿼리가 화면 오른쪽 가장자리로 밀려나지 않게 합니다.
- 4칸 — 중첩 단계마다 더 많은 시각적 분리를 주므로, 깊은 서브쿼리나 여러 단계의 CTE가 있는 쿼리에서 도움이 됩니다.
- 탭 — 파일을 바꾸지 않고도 개발자마다 자신의 표시 폭을 고를 수 있게 합니다.
여기에는 보편적으로 옳은 답이 없으며, 바로 그래서 SQL 포매터는 세 가지(2 spaces, 4 spaces, Tab)를 모두 갖춘 Indent 컨트롤을 제공합니다. 하나를 골라 어디에나 적용하세요.
어디서 줄을 바꿀까
들여쓰기 폭은 쉬운 부분입니다. 영향이 더 큰 결정은 어디에 줄바꿈을 넣느냐입니다.
SELECT열 — 사소하지 않은 것이라면 한 줄에 한 열씩 두어, 열을 추가하거나 제거할 때 diff에서 정확히 한 줄만 건드리게 하세요. 아주 짧은 쿼리는 한 줄에 두어도 됩니다.FROM과JOIN— 각 JOIN을 자기 줄에서 시작하고,ON조건은 뒤에 붙이거나 그 아래에 들여씁니다. 이렇게 하면 JOIN 그래프가 읽기 쉽게 유지됩니다.WHERE— 각AND/OR를 자기 줄에 두어 불리언 로직이 위에서 아래로 읽히게 하세요.AND/OR가 섞인 조건이라면 그룹을 괄호로 묶고 들여써서, 우선순위를 독자가 따져 봐야 하는 무언가가 아니라 명시적으로 드러나게 하세요.
이것들은 지침이지 법은 아닙니다. 사소한 SELECT id FROM users WHERE id = 1에는 다섯 줄이 필요 없고, 억지로 다섯 줄로 만들면 가독성에 도움이 되기는커녕 해칩니다. 판단 기준은 대략 이렇습니다. 쿼리에 열이 한두 개를 넘거나, 테이블이 하나를 넘거나, 조건이 하나를 넘으면 줄을 바꾸세요. 그 임계점 아래에서는 한 줄이 더 명확하고, 그 위에서는 과감하게 줄을 바꾸세요. 좋은 포매터는 적절한 임계점을 대신 인코딩해 두지만, 출력이 결코 여러분을 놀라게 하지 않도록 그 원리를 이해해 둘 가치가 있습니다.
앞서 본 지저분한 한 줄짜리에 이 규칙들을 적용하면, 모든 절과 모든 JOIN이 한눈에 보이는 레이아웃이 나옵니다.
SELECT
u.id,
u.name,
COUNT(o.id) AS orders
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.active = true
GROUP BY u.id, u.name
ORDER BY orders DESC;
앞쉼표 vs 뒤쉼표
더 작지만 끈질긴 질문입니다. 여러 줄로 된 열 목록에서 쉼표는 어디에 둘까요?
-- Leading commas
SELECT
id
, email
, created_at
FROM users;
-- Trailing commas
SELECT
id,
email,
created_at
FROM users;
앞쉼표에는 진짜 장점이 있습니다. 열을 추가하거나 제거할 때 한 줄만 바뀌고, 빠뜨린 쉼표는 해당 줄이 두드러지기 때문에 쉽게 찾을 수 있습니다. 뒤쉼표는 더 자연스럽게 읽히고 실제로 훨씬 흔합니다. 둘 다 괜찮습니다. 하나를 골라 포매터가 적용하게 해서, 아무도 그것을 다시 생각할 필요가 없게 하세요.
테이블과 열의 명명 규칙
포매팅은 공백 문자를 다스리고, 명명은 식별자 그 자체를 다스리며, 명명이 없으면 스타일 가이드는 미완성입니다.
SQL 식별자의 사실상 표준은 snake_case입니다. 모두 소문자에 단어를 밑줄로 구분합니다. user_id, created_at, order_items처럼 말입니다. 이것이 그 지위를 얻은 것은 단순한 습관이 아니라 구체적인 이유 때문입니다. snake_case 식별자는 따옴표로 감쌀 필요가 전혀 없고 방언 전반에서 일관되게 동작하는 반면, camelCase(애플리케이션 코드에서 흔합니다)는 데이터베이스가 대소문자를 접는 방식과 충돌합니다. 이 점은 잠시 뒤에 다루겠습니다.
이것이 애플리케이션 코드와 다른 이유를 분명히 해 둘 가치가 있습니다. 대부분의 프로그래밍 언어에서는 주위의 코드가 식별자를 통제하며 camelCase나 PascalCase가 표준입니다. 반면 SQL 식별자는 데이터베이스 고유의 대소문자 접기 규칙으로 해석되며, 바로 그 규칙이 대소문자가 섞인 이름을 취약하게 만드는 주범입니다. snake_case는 그 문제 전체를 피해 갑니다. 접을 대소문자가 없고, 따옴표로 감쌀 이유가 없으며, 엔진마다 다르게 동작하는 부분도 없습니다.
거의 모든 SQL 스타일 가이드에 등장하는 몇 가지 관례가 더 있습니다.
- 단수 vs 복수 테이블 이름은 진짜로 의견이 갈립니다.
users(복수, “이 테이블은 사용자들을 담는다”)와user(단수, “각 행이 한 명의 사용자다”)는 둘 다 지지자가 있습니다. 대소문자와 마찬가지로, 선택 자체보다 모든 테이블에 일관되게 적용하는 것이 덜 중요한 것이 아니라 더 중요합니다. - 예약어를 식별자로 쓰지 마세요. 열 이름을
order,user,table로 지으면 어디서나 따옴표로 감싸야 하고 헷갈리는 오류를 부릅니다. 대신order_id나account를 쓰세요. - 키 명명을 일관되게 유지하세요.
id라는 기본 키와<referenced_table>_id(예:user_id) 형태의 외래 키는 JOIN을 예측 가능하고 자기 설명적으로 만듭니다.
명시적으로 짚어 둘 함정이 하나 있습니다. 데이터베이스 열을 애플리케이션 변수처럼 짓는 팀을 물기 때문입니다. PostgreSQL에서는 따옴표 없는 식별자가 소문자로 접히므로, SELECT userId FROM t는 실제로 userid라는 이름의 열을 찾습니다. 그것을 따옴표로 감싸는 순간 — "userId" — 데이터베이스는 대소문자를 보존하고 "userId"와 userid를 서로 다른 두 개의 열로 취급합니다.
-- Creates a column whose real name is lowercase "userid"
CREATE TABLE t (userId integer);
-- Both of these work — the name was folded to lowercase
SELECT userId FROM t;
SELECT userid FROM t;
-- This fails: "column \"userId\" does not exist"
-- The quotes force an exact, case-sensitive match
SELECT "userId" FROM t;
데이터베이스마다 대소문자를 접는 방향이 다르다는 점에 유의하세요. Oracle은 따옴표 없는 식별자를 대문자로 접고, 다른 여럿은 소문자로 접습니다. 그래서 대소문자가 섞인 따옴표 식별자는 이식성조차 없습니다. 깔끔한 탈출구는 따옴표로 감싼 대소문자 혼합 식별자를 아예 쓰지 않고 snake_case를 고수하는 것입니다. 그러면 문제 전체를 비켜 가고 모든 방언에서 스키마를 읽기 쉽게 유지합니다.
camelCase, snake_case, kebab-case를 더 깊이 비교하려면 — 코드와 데이터 전반에서 각각이 언제 옳은 선택인지를 포함해 — 명명 규칙 가이드를 참고하세요.
SQL 방언별 포매팅
지금까지의 내용은 대체로 방언과 무관합니다. 대소문자, 들여쓰기, 줄바꿈, 명명은 어느 데이터베이스를 대상으로 하든 적용됩니다. 하지만 쿼리가 특정 데이터베이스에만 있는 구문을 쓰는 순간 “이 SQL을 포매팅해”는 벽에 부딪힙니다. 그 구문을 인식하지 못하는 범용 파서는 그것을 망가뜨리기 때문입니다. 토큰을 엉뚱한 곳에서 쪼개거나, 연산자를 잘못 읽거나, 따옴표 문자를 문자열 구분자로 취급해 쿼리의 절반을 삼킬 수 있습니다. 바로 여기서 방언을 인식하는 포매팅이 제 몫을 하며, 포매터가 추측하는 대신 먼저 데이터베이스를 고르라고 요청하는 이유입니다. 아래의 차이들은 일상적인 쿼리에서 가장 자주 마주치는 것들입니다.
| 작업 | PostgreSQL | MySQL / MariaDB | SQL Server (T-SQL) | Oracle | Standard SQL |
|---|---|---|---|---|---|
| 문자열 연결 | || 또는 CONCAT() | CONCAT() | + 또는 CONCAT() | || 또는 CONCAT() | || |
| NULL 대체 | COALESCE() | COALESCE() / IFNULL() | COALESCE() / ISNULL() | COALESCE() / NVL() | COALESCE() |
| 행 제한 | LIMIT | LIMIT | TOP / OFFSET … FETCH | FETCH FIRST | FETCH FIRST |
| 식별자 인용 | 큰따옴표 ("…") | 백틱 | 대괄호 ([…]) | 큰따옴표 ("…") | 큰따옴표 ("…") |
이 표는 같은 논리적 작업이 방언마다 어떻게 다르게 표기되는지 보여줍니다.
문자열 연결과 NULL 처리
가장 흔한 일상 연산 두 가지가 방언마다 다르게 표기됩니다.
문자열 연결:
-- PostgreSQL, Oracle, SQLite (standard operator)
SELECT first_name || ' ' || last_name AS full_name FROM users;
-- SQL Server (T-SQL uses +)
SELECT first_name + ' ' + last_name AS full_name FROM users;
-- Portable across dialects
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;
NULL 대체:
-- Standard SQL (works everywhere)
SELECT COALESCE(nickname, name) AS display_name FROM users;
-- SQL Server only
SELECT ISNULL(nickname, name) AS display_name FROM users;
-- MySQL / MariaDB only
SELECT IFNULL(nickname, name) AS display_name FROM users;
잘못된 방언으로 설정된 포매터는 ISNULL이나 || 연산자를 이해하지 못해 주변 쿼리를 잘못 파싱할 수 있습니다.
행 제한과 식별자 인용
결과 행을 제한하는 것은 가장 방언마다 갈리는 구문 가운데 하나입니다.
-- PostgreSQL, MySQL, SQLite
SELECT id, name FROM users ORDER BY created_at DESC LIMIT 10;
-- SQL Server (T-SQL)
SELECT TOP 10 id, name FROM users ORDER BY created_at DESC;
-- Standard SQL / Oracle
SELECT id, name FROM users ORDER BY created_at DESC FETCH FIRST 10 ROWS ONLY;
식별자 인용도 세 갈래로 갈립니다. 식별자를 따옴표로 감싸야 할 때 — 보통 예약어를 쓰거나 대소문자를 보존하기 위해서입니다 — 구분자는 데이터베이스에 따라 달라집니다.
-- MySQL / MariaDB use backticks
SELECT `order`, `user` FROM `select`;
-- SQL Server (T-SQL) uses square brackets
SELECT [order], [user] FROM [select];
-- Standard SQL (PostgreSQL, Oracle, SQLite) uses double quotes
SELECT "order", "user" FROM "select";
MySQL 백틱을 문자열 구분자로 여기거나 T-SQL 대괄호를 다른 무언가로 여기는 포매터는 깨진 출력을 내놓습니다. 어느 것이 무엇인지 알려 주는 것이 바로 방언 설정입니다. 이것은 또한 데이터베이스 사이에서 쿼리를 복사·붙여넣기하는 것이 좀처럼 깔끔한 교체가 되지 못하는 이유이기도 합니다. 같은 논리적 의도 — 문자열 두 개를 잇고, NULL로 대체하고, 열 개로 제한하고, 예약어를 따옴표로 감싸는 것 — 가 방언마다 네 가지 방식으로 쓰이며, 여러분의 데이터베이스를 아는 파서만이 그것을 망가뜨리지 않고 다시 포매팅할 수 있습니다.
방언 인식 포매팅이 중요한 이유
바로 이 때문에 SQL 포매터는 단일 범용 모드가 아니라 아홉 가지 방언 — PostgreSQL, MySQL, SQL Server (T-SQL), BigQuery, Snowflake, Oracle, SQLite, MariaDB, Standard SQL — 을 갖추고 출시됩니다. 올바른 것을 고르면 파서가 PostgreSQL의 달러 인용 문자열과 :: 캐스트, T-SQL의 대괄호 식별자와 TOP, BigQuery와 Snowflake의 웨어하우스 전용 함수, 그리고 위의 인용 규칙들을 추측해서 틀리는 대신 올바르게 처리합니다. 포매팅하기 전에 드롭다운에서 실제 데이터베이스를 고르면, 출력이 올바르고 관용적인 형태로 돌아옵니다.
SQL 포매팅 자동화
규칙을 읽는 것과 적용하는 것은 별개이며, 아무도 그것을 손으로 적용해서는 안 됩니다. 스타일 가이드의 핵심은 기계가 그것을 강제한다는 데 있습니다. 마찰을 얼마나 줄이고 싶은지에 따라, 포매팅을 끼워 넣을 곳은 세 군데가 있습니다.
에디터에서 (저장 시 포매팅)
가장 손이 덜 가는 방법은 저장할 때마다 자동으로 포매팅하는 것입니다. VS Code에는 저장 시 실행되는 SQL 포매터 확장이 있고, JetBrains DataGrip과 다른 IDE의 데이터베이스 도구에는 키 입력이나 저장 후크에 바인딩할 수 있는 내장 포매터가 딸려 옵니다. 한번 설정해 두면 여러분의 쿼리는 그냥 절대로 포매팅되지 않은 채로 남지 않습니다. JavaScript의 Prettier나 Go의 gofmt와 같은 모델입니다. 함정은 에디터 설정이 개발자 각자의 컴퓨터에 산다는 점입니다. 그래서 저장 시 포매팅은 여러분의 SQL은 깔끔하게 유지해 주지만, 그것만으로 팀 나머지의 SQL까지 보장하지는 못합니다. 그러려면 다음 계층이 필요합니다.
CI에서 린터로
팀 전체에 스타일을 강제하려면 검사를 지속적 통합(CI)으로 옮기세요. sqlfluff 같은 SQL 린터는 린트와 자동 수정을 모두 합니다. 규칙 — 방언, 키워드 대소문자, 들여쓰기, 쉼표 위치 — 을 .sqlfluff 설정 파일에 인코딩하고, sqlfluff lint를 실행해 위반을 표시하고 sqlfluff fix로 고치며, 합의된 스타일에서 벗어나는 풀 리퀘스트는 CI가 실패시키게 합니다. ESLint나 Prettier가 프런트엔드 저장소를 게이팅하는 것과 같은 발상입니다. 스타일이 누군가 기억해서 남겨야 하는 리뷰 코멘트이기를 그치고, 기계가 결코 잊지 않는 통과·실패 검사가 됩니다. 그 대가로 스타일 논쟁은 설정을 작성할 때 한 번만 일어나고, 매 풀 리퀘스트마다 일어나지 않습니다.
일회성 온라인 포매팅
때로는 그냥 못생긴 쿼리 하나가 있을 뿐, 무언가를 설치할 생각은 전혀 없을 때가 있습니다. 로그에서 가져온 조각, 동료의 Slack 메시지, 문서에 붙여넣을 쿼리 같은 것 말입니다. 그럴 때는 SQL 포매터에 붙여넣고, 방언과 대소문자와 들여쓰기를 고른 뒤, 깔끔한 결과를 복사하세요.
여기서는 개인정보 측면이 중요한데, 놓치기 쉽습니다. 많은 온라인 포매터는 작업을 위해 여러분이 붙여넣은 텍스트를 서버로 보내며, 이는 여러분의 쿼리 사본 — 테이블 이름, 열 이름, 때로는 프로덕션 장애에서 나온 실제 값 — 이 여러분의 컴퓨터를 떠난다는 뜻입니다. SQL 포매터는 전적으로 브라우저 안에서 실행되므로, 여러분의 SQL은 어디에도 업로드되지 않습니다. 덕분에 프로덕션 스키마나 독점 로직을 건드리는 쿼리를 포매팅해도 안전합니다. 바로 깔끔한 포매팅이 가장 필요하면서도 쿼리를 제삼자에게 넘기고 싶지 않은 상황 말입니다. 같은 작업 흐름에서 다른 형식을 다루고 있다면, 형제 도구인 JSON 포맷터도 같은 방식으로 동작합니다. 똑같이 브라우저 안에서 처리하고, 똑같이 한 번의 클릭으로 복사합니다.
세 가지 방법은 서로 배타적이지 않으며, 가장 좋은 구성은 보통 그것들을 합칩니다. 작성하는 동안 빠른 안쪽 루프를 위한 저장 시 포매팅, 팀 표준을 강제하는 안전망으로서의 CI 린터, 그리고 저장소에 결코 닿지 않는 일회용 조각을 위한 온라인 포매터입니다. 무엇을 집어 들든, 마지막으로 한 번 더 불변 규칙을 기억하세요. 이 도구들 가운데 어느 것도 쿼리가 하는 일을 바꾸지 않습니다. 공백 문자, 줄바꿈, 대소문자, 주석을 재배치할 뿐, 그 밖의 것은 아무것도 바꾸지 않습니다.
자주 묻는 질문
SQL 키워드는 대문자로 써야 하나요, 소문자로 써야 하나요?
둘 다 유효합니다. SQL 키워드는 대소문자를 구분하지 않기 때문입니다. UPPERCASE는 로그나 diff처럼 구문 강조가 없는 환경에서 키워드를 두드러지게 하고, lowercase는 타이핑이 더 쉽고 이미 키워드에 색을 입히는 현대 에디터에 잘 맞습니다. 실제로 중요한 것은 팀 전체가 하나를 골라 포매터가 그것을 강제하는 것입니다. 대소문자를 섞는 것이 최악의 선택입니다.
SQL에 가장 좋은 들여쓰기는 무엇인가요?
2칸이 가장 흔한 기본값이며 diff를 작게 유지합니다. 4칸은 깊이 중첩된 쿼리를 더 읽기 쉽게 합니다. 탭은 개발자마다 자신의 표시 폭을 고를 수 있게 합니다. 정답은 하나가 아닙니다. 하나를 골라 팀 전체에 일관되게 적용하세요. 이 포매터를 포함한 대부분의 SQL 포매터는 세 가지 옵션을 모두 지원합니다.
SQL을 어떻게 자동으로 포매팅하나요?
SQL을 자동으로 포매팅하는 방법은 세 가지입니다. 에디터에서의 저장 시 포매팅(VS Code 또는 DataGrip), 스타일을 자동 수정하는 sqlfluff 같은 CI 린터, 일회성 붙여넣기를 위한 온라인 SQL 포매터입니다. 온라인 방식은 설치가 필요 없어 가장 빠릅니다. 그냥 붙여넣고, 방언을 고르고, 결과를 복사하면 됩니다.
SQL에서 앞쉼표를 써야 하나요, 뒤쉼표를 써야 하나요?
앞쉼표(각 줄 시작에 쉼표)는 열을 추가하거나 제거할 때 더 깔끔한 diff를 주고 빠뜨린 쉼표를 쉽게 찾게 합니다. 뒤쉼표(끝에 쉼표)는 더 자연스럽게 읽히고 더 흔합니다. 어떤 SQL 스타일 가이드에서나 둘 다 받아들여집니다. 핵심은 하나를 골라 포매터가 자동으로 적용하게 하는 것입니다.
SQL을 포매팅하면 쿼리가 실행되는 방식이 바뀌나요?
아닙니다. SQL 포매팅은 공백 문자, 줄바꿈, 키워드 대소문자, 주석만 바꿀 뿐, 쿼리의 로직은 절대 바꾸지 않습니다. 포매팅된 쿼리는 원본과 정확히 같은 결과를 반환하며, 그래서 프로덕션 쿼리조차 리뷰하거나 실행하기 전에 보기 좋게 다듬어도 전혀 안전합니다.
SQL 테이블과 열에는 어떤 명명 규칙을 써야 하나요?
snake_case — 모두 소문자에 밑줄 — 가 SQL 테이블·열 이름의 사실상 표준입니다. 따옴표를 피하고 방언 전반에서 안전하게 유지되기 때문입니다. 기본 키(id)와 외래 키(user_id)의 이름을 일관되게 유지하고, 따옴표 골칫거리를 막기 위해 order나 user 같은 예약어를 식별자로 쓰지 마세요.
PostgreSQL이나 T-SQL 같은 특정 방언에 맞춰 SQL을 어떻게 포매팅하나요?
먼저 포매터에서 맞는 방언을 고르세요. PostgreSQL 모드는 :: 캐스트와 달러 인용 문자열을 올바르게 처리하고, SQL Server (T-SQL) 모드는 대괄호 [identifiers]와 TOP을 이해합니다. 잘못된 방언을 고르면 범용 파서가 방언 전용 구문을 망가뜨리므로, 포매팅하기 전에 항상 실제 데이터베이스로 설정하세요.
표준 SQL 스타일 가이드가 있나요?
공식 표준은 없지만, 널리 참조되는 것이 몇 가지 있습니다. Simon Holywell의 sqlstyle.guide, 그리고 Mozilla나 dbt 커뮤니티 같은 팀의 공개 스타일 가이드입니다. 그들이 공유하는 합의 — 일관된 들여쓰기, snake_case 식별자, 각 주요 절 앞의 줄바꿈 — 가 바로 이 가이드가 성문화하는 내용이며, 포매터가 여러분을 대신해 그것을 강제할 수 있습니다.