컴퓨터공학

실무에 강한 SQL 작성법: 비즈니스 로직을 쿼리로 설계하는 기술

nyambu 2025. 5. 16. 08:00

실무에 강한 SQL 작성법
실무에 강한 SQL 작성법

1. 실무 SQL의 본질: 단순한 SELECT가 아니다

1-1. “조회”만 하는 쿼리는 실무에서 드물다

 SQL을 학습할 땐 SELECT name FROM users 같은 단순 조회 쿼리로 시작한다. 하지만 실무에서 실제로 자주 마주치는 쿼리는 대부분 다음과 같다:

  • 조건이 복잡하게 얽힌 검색 쿼리
  • 통계를 위한 다단계 집계 쿼리
  • 기간별 변화량, 사용자별 구간 비교
  • 누적값이나 순위 기반 정렬
  • 동적 정렬, 페이징, 검색 필터가 동시에 적용되는 목록 조회

 이처럼 실무에서 SQL은 단순 조회 도구가 아니라, 복잡한 비즈니스 로직을 명확하게 표현해내는 언어로서 사용된다.


2. 쿼리 분할 vs 결합: 언제 하나로 묶고, 언제 나눠야 할까?

2-1. 단일 쿼리로 구성할 수 있는 기준

 모든 로직을 하나의 SQL로 처리할 수 있다면 가장 좋겠지만, 실무에서는 쿼리를 나눠서 처리하는 경우도 많다. 다음 조건이 충족되면 단일 쿼리로 구성하는 것이 좋다.

  1. 하나의 결과만 필요하다
  2. 동일한 기준(WHERE, JOIN, 기간 등)으로 필터링할 수 있다
  3. 동일한 인덱스를 사용할 수 있는 범위다
  4. 중간 계산값을 따로 저장하지 않아도 된다

 예를 들어, "사용자별 이번 달 주문 건수"는 다음과 같이 단일 쿼리로 처리할 수 있다.

SELECT user_id, COUNT(*) AS order_count
FROM orders
WHERE order_date BETWEEN '2024-05-01' AND '2024-05-31'
GROUP BY user_id;

2-2. 쿼리를 나눠야 할 때

 다음 조건에 해당하면 쿼리를 여러 번 나누어 처리하는 것이 더 효율적이다:

  • 중간 집계값을 여러 번 재활용해야 한다
  • 서브쿼리가 중첩되어 가독성이 떨어진다
  • 성능 최적화를 위해 중간 결과를 캐싱해야 한다
  • 하나의 쿼리로는 모든 조건을 표현하기 어렵다

 실무에서는 아래처럼 조회 쿼리 → 가공 쿼리 → 출력 쿼리를 나누는 방식으로 쿼리를 구성하는 경우가 많다.


3. 데이터 흐름을 따라 쿼리를 설계하는 습관

3-1. 데이터 흐름 먼저 생각하자

 SQL은 문법보다도 데이터가 어떻게 흘러가는지를 먼저 설계하는 것이 더 중요하다. 즉, 원하는 결과를 얻기 위해 어떤 데이터를 어떤 순서로 필터링하고, 가공하고, 출력할지를 먼저 그려야 한다. 

 

예: “지난달 기준으로 상태별 결제 건수를 구하고, 그 중 취소 비율이 높은 사용자만 찾고 싶다.”

→ 흐름을 나눠보면 다음과 같다:

  1. 조건에 맞는 결제 데이터 필터
  2. 사용자별 결제 통계 (성공, 실패, 취소 건수)
  3. 취소 비율이 일정 이상인 사용자 필터링
  4. 사용자 정보 JOIN

 이 과정을 먼저 머릿속에 정리한 뒤, 이를 SQL로 옮겨가는 방식이 실무에서 유지보수하기 좋은 쿼리 작성법이다.

3-2. 쿼리를 설계할 때의 질문들

  • 이 데이터는 어떤 기준으로 그룹화해야 하는가?
  • 지금 필요한 건 행(Row) 기준인가, 컬럼(Column) 기준인가?
  • 이 쿼리는 반복해서 재사용될 수 있는가?
  • 지금 이 연산은 DB가 하는 게 좋을까, 앱 로직에서 처리하는 게 좋을까?

 이런 질문을 먼저 던지고 설계한 SQL은 문제 발생 시에도 쉽게 수정할 수 있다.


4. 실무 쿼리 설계 시 자주 쓰이는 구조

4-1. WITH 절을 활용한 가독성 향상

 복잡한 쿼리는 공통 부분을 WITH절로 추출하여 쿼리 가독성과 재사용성을 높일 수 있다. 실제로 MySQL 8.0 이상에서는 WITH (CTE, Common Table Expression)를 적극적으로 활용한다.

WITH recent_orders AS (
  SELECT user_id, MAX(order_date) AS last_order
  FROM orders
  GROUP BY user_id
)
SELECT u.name, r.last_order
FROM users u
JOIN recent_orders r ON u.id = r.user_id;

 이 구조는 실무에서 가장 많이 사용되는 패턴 중 하나이며, 모듈화된 쿼리 작성 습관을 키우는 데 효과적이다.

4-2. 윈도우 함수의 기본 적용

 RANK(), ROW_NUMBER(), SUM() OVER(...) 등 윈도우 함수는 “모든 결과를 테이블 단위로 보고 싶은데, 그 안에서 구간별 통계를 내고 싶을 때” 매우 유용하다. 실무에서는 예를 들어 다음처럼 쓰인다.

  • 사용자별 최근 활동일
  • 카테고리별 판매 누적
  • 날짜별 로그인 수 누적

 윈도우 함수는 데이터 흐름을 자르지 않고 연속적인 분석을 가능하게 해주는 도구로, 실무에서는 필수다.


5. 복잡한 쿼리는 나누고 단순하게 구성하자

5-1. 쿼리가 길어진다고 무조건 좋은 건 아니다

 실무에서 흔히 저지르는 실수 중 하나는 “모든 것을 한 줄의 SQL로 끝내려는 습관”이다. JOIN, WHERE, GROUP BY, HAVING, 윈도우 함수, 서브쿼리, ORDER BY까지 한 문장에 담기 시작하면 결과는 나오더라도 가독성은 무너지고 유지보수도 어렵다. 복잡한 쿼리는 가급적 단계별로 의미 있는 파트를 나누어 작성하는 것이 좋다.

5-2. 쿼리 나누기의 대표 구조

  1. 기초 필터링 → 필요한 범위만 추출
  2. 그룹화/집계 → 통계 수치 정리
  3. 가공/정렬 → UI에 맞게 최종 처리

 이런 구조로 쿼리를 분리하면, 각 단계별 성능도 점검하기 쉬워지고, 오류 발생 시 디버깅도 훨씬 쉬워진다.


6. 중첩된 SELECT 구조를 제거하자

6-1. 중첩 SELECT가 왜 문제인가?

 SELECT ... WHERE IN (SELECT ...)처럼 중첩 구조가 너무 많아지면 실행 순서가 명확하지 않고, 실행 계획도 비효율적으로 구성될 수 있다. MySQL이나 PostgreSQL의 Optimizer가 어느 정도 해결해주긴 하지만, 성능 튜닝 측면에서는 명확하게 JOIN 기반으로 구성하는 게 유리하다.

6-2. 서브쿼리 대신 JOIN 또는 CTE 사용

 예전에는 서브쿼리를 많이 썼지만, 요즘은 다음과 같은 방식으로 리팩토링하는 것이 실무에서는 기본이다.

  • 서브쿼리 → WITH절 (공통 테이블 식)
  • 조건 필터링 → JOIN + WHERE로 통합
  • 집계 서브쿼리 → 인라인 뷰 또는 TEMP VIEW로 분리

 이렇게 구성하면 쿼리는 더 길어지지만, 명확하게 단계별로 역할이 구분된 SQL을 만들 수 있다.


7. 실시간 처리 쿼리 vs 비실시간 쿼리 구분

7-1. 즉시 처리와 배치 처리를 나눠야 하는 이유

 모든 데이터를 실시간으로 처리하면 좋을 것 같지만, 실제로는 비효율적인 경우가 많다. 예를 들어 사용자의 접속 로그 전체를 매번 조인해서 조회하거나, 매일의 누적 주문 수를 실시간으로 계산하면 시스템에 과부하가 생길 수 있다. 이럴 때는 다음 기준으로 분리해야 한다.

  • 사용자가 직접 보는 화면 → 실시간 쿼리
  • 관리자, 보고서, 대시보드 → 비실시간 집계 테이블 또는 뷰

7-2. 실시간 쿼리는 가능한 가볍게

 실시간으로 호출되는 쿼리는 속도가 가장 중요하다. JOIN 횟수 최소화, 정렬 제거, LIMIT 사용 등으로 성능을 유지해야 한다. 반면에 대시보드나 백오피스용 쿼리는 복잡해도 상관없지만 속도가 느리면 안 되는 유형이다. 이런 쿼리는 전처리 또는 배치 집계 테이블을 따로 만들어두는 것이 좋다.


8. 데이터 양에 따라 쿼리 구조는 달라진다

8-1. 소량일 때는 자유롭게, 대량일 때는 구조적으로

 테스트 DB나 소규모 서비스에서는 어떤 쿼리를 써도 금방 결과가 나온다. 하지만 실무에서는 테이블 하나에 수백만~수억 건의 데이터가 쌓인다. 이때 단순한 ORDER BY, LIKE, JOIN이 심각한 속도 저하를 불러오게 된다.

8-2. 쿼리 구조를 데이터 양에 따라 바꾸는 기준

데이터 규모 권장 쿼리 방식
1~1000건 서브쿼리, LIKE 등 자유롭게
1만~10만건 인덱스 기반 WHERE, JOIN 최소화
100만건 이상 서브쿼리 제거, 커서 기반 처리, 정렬 제거, 캐싱 고려
 

 실무에서는 항상 데이터 양을 고려하고, 같은 쿼리 구조를 모든 테이블에 똑같이 적용하지 않는 유연한 사고가 필요하다.


9. 유지보수 가능한 SQL을 위한 원칙

 마지막으로, 실무에 강한 SQL을 쓴다는 건 나만 이해하는 쿼리를 쓰지 말고, 모두가 이해할 수 있도록 쓰는 것이다.
다음 원칙을 참고하자:

  • 주석을 적극적으로 활용해 쿼리의 목적을 명시
  • SELECT * 대신 필요한 컬럼만 명시
  • 서브쿼리는 가독성을 기준으로 정리
  • 임시 테이블이나 CTE 활용을 적극 고려
  • 가능하면 프론트에서 복잡한 가공을 미루지 말고, SQL에서 해결