SQL은 작성하기 어렵지 않아요. 잘 작성하기가 어려울 뿐이에요.
Beekeeper Studio는 무료 오픈 소스 데이터베이스 GUI예요
지금까지 써 본 SQL 쿼리 & 에디터 도구 중 최고예요. 데이터베이스 관리에 필요한 모든 게 다 있어요. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio는 빠르고 직관적이며 사용하기 쉬워요. 다양한 데이터베이스를 지원하고 Windows, Mac, Linux에서 모두 잘 작동해요.
개발 환경에서 잘 돌던 쿼리가 프로덕션에서는 느려지기 시작해요. ORDER BY가 빠지면 결과가 들쭉날쭉하고, 대충 쓴 SELECT *는 스키마가 바뀌면 깨지며, 작은 데이터셋에서는 무해해 보이던 필터가 규모가 커지면 거의 멈춰 버리기도 해요.
시간이 지나면 오래 버티는 패턴과 그렇지 못한 패턴이 보이기 시작해요.
그래서 모던한 오픈 소스 크로스 플랫폼 데이터베이스 GUI인 Beekeeper Studio를 만드는 팀이 SQL 모범 사례 7가지를 소개해 드려요.
🔖 빠른 참고: 이 모범 사례를 익히는 동안 SQL 언어 치트 시트를 곁에 두고 SELECT, WHERE, JOIN, 집계 함수 등 필수 SQL 명령어를 빠르게 찾아보세요.
1. 구체적으로 작성하세요 — SELECT * 피하기
SELECT *로 모든 컬럼을 가져오면 빠르고 유연하게 느껴져요. 어떤 컬럼을 반환하고 뺄지 고민할 필요가 없고, 데이터셋을 탐색하는 단계에서는 잘 동작하죠.
하지만 그 유연함에는 대가가 따라요. 스키마가 바뀌거나 조인이 늘어나면 SELECT *는 중복되거나 예상치 못한 컬럼을 가져오고, 쿼리를 느리게 만들며, 디버깅을 어렵게 해요.
필요한 컬럼만 지정하면 의도가 명확해지고, 코드가 소리 없이 깨지는 일을 막을 수 있어요.
-- Risky
SELECT * FROM user_profiles WHERE user_id = 42;
-- Better
SELECT user_id, display_name, avatar_url
FROM user_profiles
WHERE user_id = 42;
2. CTE로 복잡한 로직 단순화하기
중첩 서브쿼리도 동작은 하지만 금세 읽기 어려워져요. 어디서 무엇을 필터링하는지 파악하기 힘들고, 디버깅하려면 겹겹이 쌓인 괄호 사이를 오르내려야 하죠.
공통 테이블 표현식(WITH 절)을 쓰면 쿼리를 이름 붙은 단계로 나눌 수 있어요. 각 단계를 따로 실행하고 테스트할 수 있어 의도가 명확해지고 오류도 추적하기 쉬워져요. 다만 일부 데이터베이스는 CTE를 구체화(materialize)하므로, 반복이 잦은 경우에는 인라인 서브쿼리가 더 빠를 수 있다는 점을 기억하세요.
-- Hard to follow
SELECT customer_id, COUNT(*)
FROM (
SELECT customer_id, created_at
FROM orders
WHERE created_at > now() - interval '7 days'
) o
GROUP BY customer_id;
-- Clearer
WITH recent_orders AS (
SELECT customer_id, created_at
FROM orders
WHERE created_at > now() - interval '7 days'
)
SELECT customer_id, COUNT(*)
FROM recent_orders
GROUP BY customer_id;
3. 문법이 아니라 의도를 주석으로 남기세요
SQL을 보면 이미 알 수 있는 내용을 다시 적는 인라인 주석은 별 도움이 안 돼요. 나중에 잊어버리는 진짜 맥락은 쿼리가 무엇을 하는지가 아니라 왜 존재하는지예요.
목적이나 용도를 짧게 적어 두면 나중에 코드를 다시 보거나 넘겨줄 때 훨씬 수월해요. 미래의 나, 또는 이 쿼리가 왜 중요한지 이해하려는 다음 개발자를 위해 작성하세요.
-- Find users who signed up but never activated
-- Used in the weekly retention cohort report
SELECT u.id, u.email
FROM users u
LEFT JOIN events e
ON u.id = e.user_id
AND e.name = 'account_activated'
WHERE e.id IS NULL
AND u.created_at > now() - interval '7 days';
4. 인덱스를 활용할 수 있는 필터 작성하기
인덱스는 필터가 sargable(Search ARGument ABLE의 줄임말)할 때만 도움이 돼요. 실제로는 데이터베이스가 모든 행을 스캔하지 않고 인덱스를 바로 사용할 수 있다는 뜻이에요.
LOWER(email)이나 DATE(created_at)처럼 컬럼을 함수로 감싸면 옵티마이저가 인덱스를 사용할 수 없어요. 타입 캐스팅도 같은 효과를 낼 수 있고요. 쿼리는 여전히 동작하지만 데이터가 늘수록 느려져요.
-- Not sargable: disables index
WHERE LOWER(email) = 'test@example.com';
-- Sargable: uses existing index
WHERE email = 'test@example.com';
필터를 효율적으로 유지하려면 데이터가 저장된 방식에 맞게 작성하세요. 대소문자 구분 없이 비교해야 한다면 값을 일관된 형식으로 저장하거나 함수 기반 인덱스를 추가하세요. 날짜는 컬럼을 변환하지 말고 범위로 필터링하세요. 더 명확하고 인덱스도 계속 활용할 수 있어요.
-- Not sargable
WHERE DATE(created_at) = '2025-08-04';
-- Sargable
WHERE created_at >= '2025-08-04'
AND created_at < '2025-08-05';
5. 조인하기 전에 필터링하기
특히 탐색하거나 프로토타이핑할 때는 일단 전부 조인하고 나중에 필터링하고 싶어지죠. 하지만 조인할 때마다 행이 늘어나고, 불필요한 행 하나하나가 쿼리를 느리게 만들고 옵티마이저가 실행 계획을 세우기 어렵게 해요.
대신 테이블을 합치기 전에 각각 범위를 좁혀 두세요. 그러면 데이터베이스는 정말 필요한 데이터만 조인하게 되어 메모리 사용량이 줄고, 캐시 효율이 높아지며, 실행 계획도 예측 가능하게 유지돼요.
-- Joins every user, even inactive ones
SELECT e.*
FROM users u
JOIN events e ON e.user_id = u.id;
-- Filter first: only active, recently seen users
WITH active_users AS (
SELECT id
FROM users
WHERE active = true
AND last_seen > now() - interval '90 days'
)
SELECT e.*
FROM active_users u
JOIN events e ON e.user_id = u.id;
6. 관계 확인에는 IN보다 EXISTS 사용하기
관련 행이 존재하는지 확인할 때 IN도 제 역할을 해요. 문제가 생기기 전까지는요. 값 목록 전체를 만들어 하나씩 비교하고, NULL을 처리하는 방식 때문에 유효한 매치가 가려질 수 있어요.
EXISTS는 다르게 동작해요. 매치를 찾는 순간 true를 반환하고, 서브쿼리 안의 NULL에 영향을 받지 않아요. 그래서 특히 대용량 데이터셋에서 더 빠르고 신뢰할 수 있어요.
-- Works, but can be slower and NULL-prone
SELECT *
FROM users
WHERE id IN (SELECT user_id FROM orders);
-- Clear and efficient: stops at first match
SELECT *
FROM users u
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
);
7. 항상 ORDER BY 지정하기
SQL은 따로 요청하지 않으면 행 순서를 보장하지 않아요.
ORDER BY가 없으면 데이터베이스는 가장 편한 순서대로 행을 반환할 수 있고, 그 순서는 실행할 때마다, 환경마다, 심지어 사소한 업데이트만으로도 바뀔 수 있어요.
개발 환경에서는 안정적으로 보여도, 인덱스나 실행 계획, 데이터 양이 바뀌면 페이지네이션이 예측 불가능해지거나 API 결과가 일관되지 않게 돼요.
-- Looks fine until it isn't
SELECT id, name
FROM users
WHERE active = true
LIMIT 10;
-- Predictable and reproducible
SELECT id, name
FROM users
WHERE active = true
ORDER BY created_at DESC
LIMIT 10;
마무리
최고의 쿼리는 1년 뒤에 봐도 이해되는 쿼리예요. 의도가 명확하고, 동작이 예측 가능하며, 숨은 함정이 없죠.
이 습관들도 바로 그런 생각에서 나왔어요. 저희가 Beekeeper Studio에서 매일 사용하는 방법으로, 어떤 데이터베이스를 다루든 쿼리를 읽기 쉽고 일관되며 빠르게 유지해 줘요.
이 가이드의 무료 PDF 버전을 받아 곁에 두거나 팀과 공유해 보세요:
Beekeeper Studio와 함께라면 이 습관들을 일상에서 실천할 수 있어요. 작업 흐름을 끊지 않도록 만들어진 모던한 크로스 플랫폼 오픈 소스 SQL 편집기예요.
- 20개 이상의 데이터베이스 지원: Postgres, MySQL, SQLite, SQL Server, MongoDB, Redis 등.
- 빠르고 반응성 좋은 편집기: 스키마를 인식하는 자동 완성, 탭 기반 쿼리, 구문 강조.
- 시각적 데이터 도구: 흐름을 끊지 않고 테이블을 탐색하고, 행을 편집하고, JSON을 살펴보세요.
- AI 어시스턴트(프라이버시 우선): 데이터베이스를 위한 바로 그 AI 코딩 도구예요. 스키마를 인식하고, 원하는 LLM과 함께 동작하며, 쿼리를 더 빠르게 탐색하고 다듬도록 도와줘요.