데이터 분석을 위한 SQL 레시피 - 3장 데이터 가공을 위한 SQL

                    Contents
                    
                
                
            🗄️ 데이터분석을 위한 SQL 레시피 책을 읽고 정리 / 요약 한 내용입니다.
            데이터를 가공해야 하는 이유
        
        - 다룰 데이터가 데이터 분석 용도로 상정되지 않은 경우
- 연산할 때 비교 가능한 상태를 만들고 오류를 회피하기 위한 경우
5. 하나의 값 조작하기
5.1 코드 값을 레이블로 변경하기
디폴트 값을 지정할 경우 ELSE 구문을 사용한다.
|  |  | 
5.2 URL 요소 추출하기
            접근 로그 테이블
        
        | stamp | referrer | url | 
|---|---|---|
| 2016-08-26 12:02:00 | http://www.other.com/path1/index.php?k1=v1&k2=v2#Ref1 | http://www.example.com/video/detail?id=001 | 
| 2016-08-26 12:02:01 | http://www.other.net/path1/index.php?k1=v1&k2=v2#Ref1 | http://www.example.com/video#ref | 
| 2016-08-26 12:02:01 | https://www.other.com/ | http://www.example.com/book/detail?id=002 | 
레퍼러로 어떤 웹 페이지를 거쳐 넘어왔는지 판별하기
|  |  | 
            실행결과
        
        | stamp | referrer_host | 
|---|---|
| 2016-08-26 12:02:00 | www.other.com | 
| 2016-08-26 12:02:01 | www.other.net | 
| 2016-08-26 12:02:01 | www.other.com | 
URL에서 경로와 요청 매개변수 값 추출하기
|  |  | 
            실행결과
        
        | stamp | url | path | id | 
|---|---|---|---|
| 2016-08-26 12:02:00 | http://www.example.com/video/detail?id=001 | /video/detail | 001 | 
| 2016-08-26 12:02:01 | http://www.example.com/video#ref | /video | null | 
| 2016-08-26 12:02:01 | http://www.example.com/book/detail?id=002 | /book/detail | 002 | 
5.3 문자열을 배열로 분해하기
- 빅데이터 분석시 가장 많이 사용되는 자료형은 문자열
- 문자열 자료형은 범용적이므로 세부적으로 분해해서 사용하는 경우가 많음
|  |  | 
            실행결과
        
        | stamp | url | path1 | path2 | 
|---|---|---|---|
| 2016-08-26 12:02:00 | http://www.example.com/video/detail?id=001 | video | detail | 
| 2016-08-26 12:02:01 | http://www.example.com/video#ref | video | |
| 2016-08-26 12:02:01 | http://www.example.com/book/detail?id=002 | book | detail | 
- Redshift는 배열자료형을 지원하지 않음
- BigQuery는 배열의 인덱스를 0부터 시작하려면- OFFSET, 1부터 시작하려면- ORDINAL을 지정- 배열 길이 이상의 인덱스에 접근하면 오류를 리턴하며 NULL을 리턴하고 싶은 경우 SAFE_OFFSET또는SAFE_ORDINAL을 지정
 
- 배열 길이 이상의 인덱스에 접근하면 오류를 리턴하며 NULL을 리턴하고 싶은 경우 
5.4 날짜와 타임스탬프 다루기
- 날짜와 시간 정보는 로그 데이터에서 빠지지 않는 정보
- 타임존을 고려해야 하고 미들웨어 차이를 주의
현재 날짜와 타임스탬프 추출하기
- PostgreSQL을 제외한 미들웨어는 타임존 없는 타임스템프 리턴
- BigQuery는 UTC로 리턴됨
|  |  | 
            실행결과
        
        | dt | stamp | 
|---|---|
| 2023-11-21 | 2023-11-21 11:27:01.452351 +00:00 | 
지정한 값의 날짜/시각 데이터 추출
- 문자열로 지정한 날짜와 시각은 CAST 함수를 사용하는 방법이 범용적
|  |  | 
            실행결과
        
        | dt | stamp | 
|---|---|
| 2016-01-30 | 2016-01-30 12:00:00.000000 | 
날짜/시각에서 특정 필드 추가하기
|  |  | 
            실행결과
        
        | stamp | year | month | day | hour | 
|---|---|---|---|---|
| 2016-01-30 12:00:00.000000 | 2016 | 1 | 30 | 12 | 
|  |  | 
            실행결과
        
        | stamp | year | month | day | hour | year_month | 
|---|---|---|---|---|---|
| 2016-01-30 12:00:00 | 2016 | 01 | 30 | 12 | 2016-01 | 
5.5 결손 값을 디폴트 값으로 대치하기
- 문자열과 숫자를 다룰때는 NULL 값에 주의
- 처리 대상 데이터가 원하는 형태가 아닐 경우 반드시 데이터를 가공
            쿠폰 사용 여부가 함께 있는 구매로그 테이블
        
        | purchase_id | amount | coupon | 
|---|---|---|
| 10001 | 3280 | null | 
| 10002 | 4650 | 500 | 
| 10003 | 3870 | null | 
|  |  | 
            실행결과
        
        | purchase_id | amount | coupon | discount_amount | discount_amount2 | 
|---|---|---|---|---|
| 10001 | 3280 | null | null | 3280 | 
| 10002 | 4650 | 500 | 4150 | 4150 | 
| 10003 | 3870 | null | null | 3870 | 
6. 여러 개의 값에 대한 조작
            새로운 지표 정의하기
        
        - 여러 지표를 기반으로 새로운 지표를 정의 할 수 있음
- 페이지 뷰/- 방문자 수=- 사용자 한 명이 페이지를 몇 번이나 방문 했는가?
 
- 방문한 사용자 중 특정 행동(클릭 또는 구매 등)을 한 사용자의 비율
- CTR(Click Through Rate) : 클릭율
- CVR(Conversion Rate) : 전환율
 
6.1 문자열 연결하기
            사용자 주소 정보 테이블
        
        | user_id | pref_name | city_name | 
|---|---|---|
| U001 | 서울특별시 | 강서구 | 
| U002 | 경기도수원시 | 장안구 | 
| U003 | 제주특별자치도 | 서귀포시 | 
문자열을 연결하는 쿼리
|  |  | 
            실행결과
        
        | user_id | perf_city | pref_city2 | 
|---|---|---|
| U001 | 서울특별시강서구 | 서울특별시강서구 | 
| U002 | 경기도수원시장안구 | 경기도수원시장안구 | 
| U003 | 제주특별자치도서귀포시 | 제주특별자치도서귀포시 | 
6.2 여러 개의 값 비교하기
            4분기 매출 테이블
        
        | year | q1 | q2 | q3 | q4 | 
|---|---|---|---|---|
| 2015 | 82000 | 83000 | 78000 | 83000 | 
| 2016 | 85000 | 85000 | 80000 | 81000 | 
| 2017 | 92000 | 81000 | null | null | 
분기별 매출 증감 판정하기
- SIGN: 매개변수 양수이면 1, 0이면 0, 음수 -1을 리턴하는 함수
|  |  | 
            실행결과
        
        | year | q1 | q2 | judge_q1_q2 | diff_q2_q1 | sign_q1_q2 | 
|---|---|---|---|---|---|
| 2015 | 82000 | 83000 | + | 1000 | 1 | 
| 2016 | 85000 | 85000 | 0 | 0 | |
| 2017 | 92000 | 81000 | - | -11000 | -1 | 
연간 최대/최소 4분기 매출 찾기
- 여러 컬럼에서 최댓값, 최솟값 찾기 : greatest, least
- greatest, least 함수 둘다 SQL 표준에는 포함되지 않지만 대부분의 SQL 쿼리 엔진에서 구현
|  |  | 
            실행결과
        
        | year | greatest_sales | least_sales | 
|---|---|---|
| 2015 | 83000 | 78000 | 
| 2016 | 85000 | 80000 | 
| 2017 | 92000 | 81000 | 
연간 평균 4분기 매출 계산하기
단순한 연산으로 평균 4분기 매출을 구하는 쿼리
- NULL 값에 대해 COALESCE함수를 사용해 적절한 값으로 변환
|  |  | 
            실행결과
        
        | year | average | 
|---|---|
| 2015 | 81500 | 
| 2016 | 82750 | 
| 2017 | null | 
COALESCE를 사용해 NULL을 0으로 변환하고 평균값을 구하는 쿼리
- q3, q4를 매출을 0으로 변환하면 q1, q2 의 매출 합계를 4로 나누게 되어 평균값이 낮아짐
|  |  | 
            실행결과
        
        | year | average | 
|---|---|
| 2015 | 81500 | 
| 2016 | 82750 | 
| 2017 | 43250 | 
NULL이 아닌 컬럼만 사용해서 평균값을 구한는 쿼리
- 2017년의 q1, q2의 매출로만 평균을 구하려면 NULL 이 아닌 컬럼의 수를 세서 나눠야함
- COALESCE, SIGN 함수를 사용하여 NULL이 아닌 컬럼의 수를 셀 수 있다.
|  |  | 
            실행결과
        
        | year | average3 | 
|---|---|
| 2015 | 81500 | 
| 2016 | 82750 | 
| 2017 | 86500 | 
6.3 2개의 값 비율 계산하기
            광고 통계 정보 테이블
        
        | dt | ad_id | impressions | clicks | 
|---|---|---|---|
| 2017-04-01 | 001 | 100000 | 3000 | 
| 2017-04-01 | 002 | 120000 | 1200 | 
| 2017-04-01 | 003 | 500000 | 10000 | 
| 2017-04-02 | 001 | 0 | 0 | 
| 2017-04-02 | 002 | 130000 | 1400 | 
| 2017-04-02 | 003 | 620000 | 15000 | 
정수 자료형의 데이터 나누기
- 데이터에서 각 광고의 CTR (클릭수 / 노출 수)을 계산
- 실수를 상수로 앞에 두고 계산하면, 암묵적으로 자료형 변환
- 나는 해당 결과가 실수가 안나옴
 
|  |  | 
            실행결과
        
        | year | average3 | 
|---|---|
| 2015 | 81500 | 
| 2016 | 82750 | 
| 2017 | 86500 | 
0 으로 나누는 것 피하기
- NULL 전파를 사용하면 0으로 나누는 것을 피할 수 있다.
- NULL 전파란 연산 결과 모두가 NULL이 되는 SQL 성질
- NULLIF(impressions, 0): impressions 값이 0이면 NULL로 처리
 
|  |  | 
6.4 두 값의 거리 계산하기
- 데이터 분석 분야에서는 물리적 공간 길이가 아닌 거리라는 개념이 많이 등장
- 시험을 보았을 때 평균에서 어느정도 떨어져 있는가?
- 작년 매출과 올해 매출에 어느 정도의 차이가 있는가
- 어떤 사용자가 있을 때, 해당 사용자와 구매 경향이 비슷한 사용자를 뽑을 때도 사용
 
숫자 데이터의 절대값, 제곱 평균 제곱근(RMS) 계산하기
            일차원 위치 정보 테이블
        
        | x1 | x2 | 
|---|---|
| 5 | 10 | 
| 10 | 5 | 
| -2 | 4 | 
| 3 | 3 | 
| 0 | 1 | 
- x1, x2의 거리를 절댓값을 사용
- 평균 제곱근 : 두 값의 차이를 제곱한 뒤 제곱근을 적용
- ABS : 절대값 계산
- POWER : 제곱 계산
- SQRT : 제곱근 계산
|  |  | 
            실행결과
        
        | abs | rms | 
|---|---|
| 5 | 5 | 
| 5 | 5 | 
| 6 | 6 | 
| 0 | 0 | 
| 1 | 1 | 
xy 평면 위에 있는 두 점의 유클리드 거리 계산하기
            이차원 위치 정보 테이블
        
        | x1 | y1 | x2 | y2 | 
|---|---|---|---|
| 0 | 0 | 2 | 2 | 
| 3 | 5 | 1 | 2 | 
| 5 | 3 | 2 | 1 | 
- xy 평면위에 존재하는 두 점 (x1, y1), (x2, y2) 사이의 유클리드 거리 계산
- PostgreSQL에는 POINT 자료형으로 좌표를 다루는 자료구조가 존재
- POINT 자료형으로 변환한뒤 거리 연산자 <->을 이용
 
- POINT 자료형으로 변환한뒤 거리 연산자 
|  |  | 
            실행결과
        
        | dist | 
|---|
| 2.8284271247461903 | 
| 3.605551275463989 | 
| 3.605551275463989 | 
6.5 날짜/시간 계산하기
            등록 시간과 생일을 포함하는 사용자 마스터
        
        | user_id | register_stamp | birth_date | 
|---|---|---|
| U001 | 2016-02-28 10:00:00 | 2000-02-29 | 
| U002 | 2016-02-29 10:00:00 | 2000-02-29 | 
| U003 | 2016-03-01 10:00:00 | 2000-02-29 | 
미래 또는 과거의 날짜/시간을 계산하는 쿼리
|  |  | 
날짜 데이터의 차이 계산하기
- 날짜/시간 데이터의 계산은 미들웨어에 따라 표현에 차이가 큼
- 실무에서는 날짜/시간 데이터는 수치 또는 문자열 등으로 변환해 다루는 것이 편한 경우가 많음
|  |  | 
사용자의 생년월일로 나이 계산하기
- 나이는 윤년 등으로 단순히 365로 나누어 계산 할 수 없음
- PostgreSQL의 경우 age함수로 나이 계산 가능 (Redshift도 age 함수가 존재하나 공식지원은 아님)
age 함수를 사용해 나이를 계산하는 쿼리
|  |  | 
            실행결과
        
        | user_id | today | register_date | birth_date | current_age | register_age | 
|---|---|---|---|---|---|
| U001 | 2023-11-21 | 2016-02-28 | 2000-02-29 | 23 | 15 | 
| U002 | 2023-11-21 | 2016-02-29 | 2000-02-29 | 23 | 16 | 
| U003 | 2023-11-21 | 2016-03-01 | 2000-02-29 | 23 | 16 | 
연 부분 차이를 계산하는 쿼리
|  |  | 
등록 시점과 현재 시점의 나이를 문자열로 계산하는 쿼리
|  |  | 
            실행결과
        
        | user_id | register_date | birth_date | register_age | register_age | t | 
|---|---|---|---|---|---|
| U001 | 2016-02-28 | 2000-02-29 | 15 | 23 | 20231121 | 
| U002 | 2016-02-29 | 2000-02-29 | 16 | 23 | 20231121 | 
| U003 | 2016-03-01 | 2000-02-29 | 16 | 23 | 20231121 | 
6.6 IP 주소 다루기
IP 주소 자료형 활용하기
- PostgreSQL에는 IP 주소를 다루기 위한 inet 자료형이 존재
- inet 자료형을 사용하여 IP 주소를 쉽게 비교 가능
|  |  | 
            실행결과
        
        | lt | gt | 
|---|---|
| true | false | 
inet 자료형을 사용해 IP 주소 범위를 다루는 쿼리
|  |  | 
            실행결과
        
        | is_contained | 
|---|
| true | 
정수 또는 문자열로 IP 주소 다루기
- IP 주소 전용 자료형이 제공되지 않는 미들웨어에 사용
IP 주소를 정수 자료형으로 변환하기
- IP 주소를 정수 자료형으로 변경하면 대소 비교가 가능
- 텍스트 자료형으로 정의된 IP 주소에 4개의 10진수 부분을 정수 자료형으로 추출
|  |  | 
            실행결과
        
        | ip | ip_part_1 | ip_part_2 | ip_part_3 | ip_part_4 | 
|---|---|---|---|---|
| 192.168.0.1 | 192 | 168 | 0 | 1 | 
IP 주소를 정수 자료형 표기로 변환하는 쿼리
- 4개의 10진수 부분을 2^24, 2^16, 2^8, 2^0 만큼 곱하고 더하여 정수 자료형으로 표기
- IP 주소가 정수 자료형으로 변환되므로 대소 비교 또는 범위 판정 가능
|  |  | 
            실행결과
        
        | ip | ip_integer | 
|---|---|
| 192.168.0.1 | 3232235521 | 
IP 주소를 0으로 메우기
- 각 10진수 부분을 3자리 숫자가 되게 앞 부분을 0으로 채워 문자열로 변환
|  |  | 
            실행결과
        
        | ip | ip_padding | 
|---|---|
| 192.168.0.1 | 192168000001 | 
- lpad : 지정한 문자 수가 되게 문자열 왼쪽을 메우기