- SQL 안티패턴은 쿼리와 데이터 파이프라인의 유지보수를 어렵게 하고, 예상보다 느린 성능을 유발함
- CASE WHEN 남용, 인덱스 컬럼에 함수 적용, SELECT *, DISTINCT 남용, 중첩된 뷰와 서브쿼리, 깊은 의존 구조 등이 대표적인 사례
- 대부분의 문제는 속도·마감 압박으로 인한 임시방편적 해결책에서 비롯되며, 장기적으로는 데이터 신뢰성과 개발 속도를 모두 해침
- 해결책으로 명확한 조인 정의, 차원 테이블 활용, 불필요한 중첩 제거, 주기적 뷰 정리 등이 필요
- SQL을 단순한 스크립트가 아닌 팀 단위로 관리되는 생산 코드로 다뤄야 하고, 가독성을 고려한 초기 설계가 재작업을 줄여줌
소개
- 오늘은 SQL 안티패턴 중에서 자주 발생하며 영향력이 큰 몇 가지 사례를 중심으로 설명함
- 이러한 문제들은 데이터 신뢰 저하, 쿼리 개발 속도 저하 등 악순환을 유발함
- 아래 목록은 모든 사례를 포괄하지 않으며, 더 깊은 이해를 원한다면 Bill Karwin의 저서 추천함
과도하게 복잡한 CASE WHEN 구문
- 대규모 시스템에서는 상태 코드(예: 1=재고 없음)를 사람이 읽을 수 있도록 변환하기 위해 CASE WHEN 구문을 자주 사용함
- 대시보드나 리포트의 빠른 개발을 위해 하나의 View에만 해당 CASE WHEN 로직을 추가하는 것은 장기적으로 안티패턴에 해당함
- 이는 중복 로직 복사/붙여넣기, 해석 불일치 문제를 유발하며 전체 쿼리 환경을 난잡하게 만듦
- 해결책은 상태 코드를 변환하는 차원 테이블(dimension table) 또는 공용 뷰를 별도로 만들어 재사용성을 확보하는 것임
인덱스 컬럼에서 함수 사용
- “WHERE UPPER(name) = 'ABC'”처럼 인덱스가 걸린 컬럼에 함수를 적용하면 인덱스 효율이 사라짐
- SQL Server 등에서는 이로 인해 불필요한 전체 테이블 스캔(full table scan) 이 발생함
- 해결책은 함수 적용 컬럼을 별도로 인덱싱하거나 입력값을 변환해 쿼리 조건을 단순화하는 것임
뷰에서 SELECT * 사용
- 뷰 개발 시 SELECT * 를 사용하면 편해 보이지만, 구조(스키마)가 변경되면 뷰가 쉽게 깨질 수 있음
- 불필요한 컬럼까지 포함돼 의도치 않은 의존성 및 성능 문제가 발생하므로, 명시적 컬럼 선택을 해야함
DISTINCT 남용으로 중복 “해결”
- 잘못된 조인으로 중복된 결과가 발생할 때, SELECT DISTINCT로 임시 해결하는 것은 데이터 무결성 문제를 감춤
- 근본 원인은 조인 조건의 불완전함이나 관계 정의(1:1, 1:N 등) 오류임
- 올바른 해결은 조인 로직 보강을 통해 관계 정의를 명확화하고, 집계나 리포트 이전에 관계 정합성을 확보하는 것임
뷰 중첩(Excessive View Layer Stacking)
- 여러 팀이 기존 뷰를 재활용하며 새로운 뷰를 계속 쌓을 경우, 종속성 체인이 복잡해지고 성능이 급격히 저하됨
- 디버깅이 어려워지고, 쿼리 확장이 “고고학적 발굴” 수준으로 힘들어짐
- 주기적으로 변환 로직을 평탄화(flatten) 하고, 복잡한 연산은 명확한 베이스 뷰나 테이블로 머티리얼라이즈(materialize) 하는 전략이 필요함
과도한 깊이의 서브쿼리
- 3~4단계 이상으로 깊게 중첩된 서브쿼리는 가독성을 떨어뜨리고 디버깅을 어렵게 함
- 5000줄 이상의 서브쿼리를 다루는 사례도 존재함
- CTE(Common Table Expression) 를 활용하면 논리적 단계 구분이 쉬워지고 쿼리의 명확성(readability) 이 높아짐
결론
- SQL은 표면적으로 단순해 보이지만, 시스템이 커질수록 복잡성이 증대되는 특성이 존재함
- 대부분의 안티패턴은 나쁜 의도가 아니라 ‘빠른 결과’를 내기 위한 타협의 결과(속도, 마감, 임시방편) 로 생김
- SQL을 코드처럼 관리(버전 관리, 코드 리뷰, 명확한 설계) 하면 장기적으로 생산성과 신뢰성을 동시에 확보할 수 있음
- 초기 설계에 몇 분 투자해, 명확성과 일관성을 고민하면 향후 재작업과 혼란이 크게 줄어듦