- 데이터 분석 업무에서 자주 쓰는 SQL 작성 습관과 쿼리 패턴을 모은 목록으로, 모든 RDBMS에 동일하게 적용되지 않을 수 있다는 전제가 있음
- 가독성 측면에서는 선행 쉼표,
WHERE 1=1, 들여쓰기, CTE, 주석,USING을 통해 쿼리를 읽고 수정하기 쉽게 만드는 방식을 권장함 - 데이터 처리에서는 anti-join,
QUALIFY,GROUP BY ROLLUP,EXCEPT처럼 실무에서 결과 필터링·합계 생성·테이블 차이 확인에 쓰는 구문을 예시로 다룸 - 성능과 정확성 측면에서는
NULL이 섞인NOT IN, 암시적 형변환, 계산 필드 alias 충돌이 쿼리 결과나 속도를 흔들 수 있음 - 복잡한 쿼리에서는 실행 순서, 문서 확인, 컬럼 출처 명시, 저장 쿼리 이름 같은 기본 습관이 디버깅과 재사용성을 높이는 데 중요함
SQL 작성 가독성을 높이는 습관
- 이 저장소는 여러 해 동안 익힌 SQL 팁과 요령을 정리한 목록이며, 데이터 분석가의 일상 업무에서 유용한 것과 처음 SQL을 쓸 때 알았으면 좋았을 내용을 중심으로 함
- 일부 팁은 모든 RDBMS에 맞지 않을 수 있음
-
선행 쉼표와 선행
ANDSELECT절의 필드 구분에는 후행 쉼표보다 선행 쉼표를 쓰는 방식을 권장함- 새 컬럼인지, 줄바꿈된 코드인지 더 명확하게 보임
- 줄 길이가 달라도 쉼표 누락 여부를 찾기 쉬움
- 같은 이유로
WHERE절 조건 앞에도 선행AND를 둘 수 있음
-
WHERE 1=1로 조건 테스트 쉽게 하기WHERE절에 더미 조건1=1을 넣으면 테스트 중 조건을 주석 처리해도 쿼리가 깨지지 않음- 모든 조건을 주석 처리해도
1=1이 남아 쿼리가 계속 실행될 수 있음
-
들여쓰기와 포매터
-
복잡한 쿼리에는 CTE 고려
- inline view를 2~3단계 이상 중첩하면 몇 주 뒤 다시 봤을 때 이해하기 어려운 쿼리가 되기 쉬움
- CTE는 긴 쿼리를 더 정리된 형태로 만들고, 재사용성과 디버깅을 돕는 방식으로 제시됨
-
주석은 “왜”를 설명하기
- 시간이 지난 뒤에는 특정 처리를 왜 했는지 기억하기 어려울 수 있음
- 주석은 일반적으로 코드가 “어떻게” 동작하는지보다 왜 그렇게 했는지를 설명하는 편이 좋음
- 예시는 새 CMS가 archive 비디오 포맷을 처리하지 못해 archive 콘텐츠를 제외하는 조건에 주석을 붙임
-
같은 이름 컬럼 조인은
USING- 두 테이블에서 같은 이름의 컬럼으로 조인할 때
USING을 쓰면ON보다 조인을 간단히 표현할 수 있음 USING은 공통 컬럼을 결과에서 중복 제거해 하나만 반환함ON을 사용할 때 공통 컬럼을 명시하지 않으면ambiguous column name오류가 날 수 있음
- 두 테이블에서 같은 이름의 컬럼으로 조인할 때
데이터 처리에 유용한 구문
-
anti-join으로 다른 테이블에 없는 행 찾기
- anti-join은 한 테이블에는 있지만 다른 테이블에는 매칭되지 않는 행을 반환할 때 사용함
- 예시는 archive되지 않은 콘텐츠의
video_id만 가져오는 상황을 다룸 - 구현 방식은 여러 가지가 있음
LEFT JOIN후 매칭 테이블의 키가NULL인 행만 필터링NOT IN과 서브쿼리 사용NOT EXISTS와 상관 서브쿼리 사용NOT IN은NULL값 때문에 의도대로 동작하지 않을 수 있어 사용을 권하지 않음
-
QUALIFY로 윈도 함수 결과 필터링QUALIFY는 윈도 함수 결과를 기준으로 쿼리 결과를 필터링할 수 있게 함- inline view 없이 필터링할 수 있어 코드 줄 수를 줄일 수 있음
- 예시는 제품별 상위 10개 시장을
DENSE_RANK()로 고른 뒤QUALIFY로 필터링함 QUALIFY는 Snowflake, Amazon Redshift, Google BigQuery 같은 큰 데이터 웨어하우스에서만 제공되는 것으로 보인다는 제한이 있음
-
컬럼 위치 기반
GROUP BY와ORDER BY- 컬럼 이름 대신 컬럼 위치로
GROUP BY 1,ORDER BY 2처럼 쓸 수 있음 - 임시 또는 일회성 쿼리에는 유용할 수 있음
- 프로덕션 코드에서는 항상 컬럼 이름을 직접 참조하는 방식을 권장함
- 컬럼 이름 대신 컬럼 위치로
-
GROUP BY ROLLUP으로 총합 만들기GROUP BY ROLLUP은 소계와 총계를 만드는 데 사용할 수 있음- 예시는 부서별 급여 합계를 구하면서 전체 급여 합계 행을 함께 생성함
- Transact-SQL 문서는
ROLLUP이 컬럼 표현식 조합별 그룹을 만들고 오른쪽에서 왼쪽으로 그룹 수를 줄이며 소계와 총계를 만든다고 설명함 COALESCE를 적용하면 총합 행을Total처럼 표시할 수 있음- 총합 행이 결과 하단에 오도록 정렬 컬럼을 신경 써야 함
-
EXCEPT로 두 결과 집합 차이 찾기EXCEPT는 첫 번째 쿼리 결과에는 있지만 두 번째 쿼리 결과에는 없는 행을 반환함EXCEPT와UNION ALL을 함께 쓰면 두 테이블이 같은 데이터를 갖는지 검증할 수 있음- 반환 행이 없으면 두 테이블이 동일함
- 반환 행이 있으면 그 행들이 차이를 만드는 원인임
성능과 정확성을 해치는 패턴
-
NULL가능 컬럼에서는NOT EXISTS가NOT IN보다 나음- 비교 대상 컬럼이
NULL을 허용하면NOT IN은 보통NOT EXISTS보다 느릴 수 있음 - Snowflake에서 이 현상을 겪었고, PostgreSQL Wiki의 Don’t Do This는
NOT IN (SELECT ...)가 잘 최적화되지 않는다고 적고 있음 NOT IN은 비교 대상 값에NULL이 있으면 의도대로 동작하지 않음- 컬럼이
NULL을 허용한다고 해서 실제NULL값이 있다는 뜻은 아니지만, 수정할 수 없는 테이블을 다룰 때는NOT EXISTS가 속도 개선에 도움이 될 수 있음
- 비교 대상 컬럼이
-
암시적 형변환은 느려지거나 실패할 수 있음
- 컬럼과 다른 데이터 타입의 값을 조건에 넣으면 데이터베이스가 암시적 형변환을 시도할 수 있음
- 예시는 문자열 타입
video_id컬럼에 정수200050을 비교하는 경우를 다룸 - 암시적 형변환에 의존하면 문제가 생길 수 있음
- 변환이 불가능한 값이 있으면 오류가 발생할 수 있음
- 각 값을 지정한 타입으로 변환하는 추가 작업 때문에 쿼리가 느려질 수 있음
- 컬럼과 같은 데이터 타입을 사용하거나, 오류를 피하려면 Snowflake의
TRY_TO_NUMBER같은 함수를 사용할 수 있음 - 속도 영향은 처리하는 데이터셋 크기에 따라 달라짐
자주 하는 실수
-
NOT IN과NULLNOT IN은 비교 대상 값에NULL이 있으면 동작하지 않음NULL은 Unknown을 나타내므로 SQL 엔진이 검사 값이 목록에 없다고 검증할 수 없음- 이 경우
NOT EXISTS를 사용하는 방식이 대안임
-
계산 필드 alias 충돌
- 계산 필드의 이름을 기존 컬럼과 같게 만들면 예상하지 못한 동작이 생길 수 있음
- Snowflake의
GROUP BY문서는GROUP BY절의 이름이 컬럼명과 alias 모두에 매칭되면 컬럼명을 사용한다고 적고 있음 - 예시에서
LEFT(product, 1) AS product로 alias를 만들고GROUP BY product를 쓰면, 첫 글자가 아니라 원래product컬럼으로 그룹화되어 3행이 반환됨 - 해결책은 두 가지임
product_letter처럼 고유 alias를 사용함GROUP BY LEFT(product, 1)처럼 표현식을 명시함- 윈도 함수에서도 alias 문제가 생길 수 있음
- 예시에서는
CASE로Robot의 revenue를 0으로 바꾸지만, 윈도 함수가 실행된 뒤 적용되어 순위가 기대와 다르게 나옴 - 가능한 경우 고유 alias를 쓰거나, 윈도 함수의
ORDER BY안에 계산식을 직접 넣는 방식이 필요함
-
컬럼이 어느 테이블 소속인지 명시
- 여러 조인이 있는 복잡한 쿼리에서는 값 문제를 원천 테이블까지 추적할 수 있어야 함
- 두 테이블이 같은 컬럼명을 공유할 때 컬럼 소속을 명시하지 않으면 RDBMS가 오류를 낼 수 있음
- 예시는
vc.video_id,metadata.season처럼 테이블 alias를 붙여 컬럼 출처를 분명히 함
실행 순서, 문서, 저장 이름
-
SQL 실행 순서 이해
- SQL을 배우는 사람에게 가장 중요한 조언 하나로 절의 실행 순서 이해를 꼽음
- 실행 순서를 알면 쿼리 작성 방식이 크게 달라질 수 있음
- 참고 자료로 A beginner’s guide to the true order of SQL operations를 제시함
-
문서는 끝까지 읽기
- Snowflake에서 여러 날짜 컬럼 중 최신 날짜를 반환하려고
GREATEST()를 사용한 사례가 있음 GREATEST()는 인자 중 하나가NULL이면NULL을 반환함- 문서를 더 읽었다면
COALESCE(GREATEST(...), ...)대신GREATEST_IGNORE_NULLS()를 사용할 수 있었음 - 많은 경우 문서를 훑는 데 1분 이하만 걸리며, 예상과 다르게 동작하는 원인을 찾는 수고를 줄일 수 있음
- Snowflake에서 여러 날짜 컬럼 중 최신 날짜를 반환하려고
-
저장 쿼리는 설명적인 이름 사용
- 다시 실행하거나 참고해야 할 쿼리를 찾지 못하는 상황을 피하려면 설명적인 이름으로 저장하는 편이 좋음
- 저장 이름에는 보통 쿼리 주제, 실행 월, 요청자 이름이 들어감
- 예시는
Lapsed users analysis - 2023-09-01 - Olivia Roberts형식임