목차
1) 문자열 연결하기
#6-1 문자열을 연결하는 쿼리
SELECT
user_id
,CONCAT(pref_name, city_name) AS pref_city
# = ,pref_name || city_name AS pref_city (postgresql은 || 연산자도 가능)
FROM
mst_user_location
;
#실행결과
user_id | pref_city
----------------------
U001 | 서울특별시강서구
대부분의 미들웨어에서 CONCAT 함수를 사용해 원하는 만큼의 문자열을 연결할 수 있음.
하지만 Redshift의 concat은 매개변수에 2개의 문자열만 전달할 수 있음. 대신 redshift의 경우는 concat 대신 || 연산자를 사용하면 됨.
2) 여러 개의 값 비교하기
- 분기별 매출 증감 판정하기
각 컬럼의 크고 작음 비교는 case 식, 차이는 빼기, 차이와 sign 함수 조합하면 case 식보다 간단하게 값의 증감 판정 가능
sign 함수 → 매개변수가 양수라면 1, 0이라면 0, 음수라면 -1을 리턴하는 함수
SELECT
year,
q1,
q2
-- q1과 q2의 매출 변화 평가하기
, CASE
WHEN q1 < q2 THEN '+'
WHEN q1 = q2 THEN ' '
ELSE '-'
END AS judge_q1_q2
-- q1과 q2의 매출액의 차이 계산하기
, q2 - q1 AS diff_q2_q1
-- q1과 q2의 매출 변화를 1, 0, -1로 표현하기
,SIGN(q2-q1) AS sign_q2_q1
FROM
quarterly_sales
ORDER BY
year
;
- 연간 최대/최소 4분기 매출 찾기
컬럼 값에서 최댓값 또는 최솟값을 찾을 때는 greatest 또는 least 함수 사용
ex) greatest(q1, q2, q3, q4) AS greatest_sales
MIN 과 MAX는 다중 ROW에서 그룹을 지어서 하나의 ROW로 표현할때 최대값과 최소값을 가져오지만 → 집계함수
GREATEST와 LEAST는 하나의 ROW나 여러개의 값들중에 최대,최소값을 가져올 수 있다.
MAX, MIN은 레코드중에서 최대/최소값을 구하는 것이고(컬럼 안에서 최대,최소 구할 때), GREATEST, LEAST는 여러 컬럼 값 중에서 최대/최소값을 구하는 것이다.
greatest는 여러컬럼중에 최댓값이 나오는 거라 테이블처럼 나오니까 max랑 같이 쓰면 오류남.
max를 groupby랑 같이 쓰면 괜찮음.
- 연간 평균 4분기 매출 계산하기
단순한 연산 → (q1 + q2 + q3 + q4) / 4 AS average
-NULL 값을 사칙 연산하려면 COALESCE 함수 사용해 적절한 값으로 변환해야하지만, 0으로 변환해버리면 매출합계를 4로 나누게 되어 평균값이 크게 낮아짐.
→ null 이 아닌 컬럼의 수를 세서 나눠야함.
# 6-6 NULL이 아닌 컬럼만을 사용해서 평균값을 구하는 쿼리
SELECT
year
, (COALESCE(q1,0) + COALESCE(q2,0) + COALESCE(q3,0) + COALESCE(q4,0))
/ (SIGN(COALESCE(q1,0)) + SIGN(COALESCE(q2,0)) + SIGN(COALESCE(q3,0)) + SIGN(COALESCE(q4,0))) AS average
FROM
quarterly_sales
ORDER BY
year
;
3) 2개의 값 비율 계산하기
- 정수 자료형의 데이터 나누기
postgreSQL의 경우, 정수 자료형이 두 컬럼끼리 나누면 계산 결과도 정수 자료형이 되어버려 0이 나옴.
왜?
→ CAST 함수를 사용해 clicks를 double precision 자료형으로 변환하고 계산해여 결과도 double precision 자료형으로 나옴.
# 6-7
SELECT
dt
,ad_id
--hive,redshift 등은 정수를 나눌 때 자동으로 실수로 변환되어 그냥 나눠주면 됨
--postgresql의 경우 정수를 나누면 소수점이 잘리므로 명시적으로 자료형 변환
, CAST(clicks AS double precision) / impressions AS ctr
-- 실수를 상수로 앞에 두고 계산하면 암묵적으로 자료형 변환이 일어남
,100.0 * clicks / impressions AS ctr_as_percent
FROM
advertising_stats
WHERE
dt = '2017-04-01'
ORDER BY
dt, ad_id
;
- 0으로 나누는 것 피하기
- CASE 식을 사용해 impressions가 0인지 확인하는 것.
impressions가 0보다 큰 경우 CTR을 계산하고 이외의 경우에는 NULL을 출력
→ null 포함한 데이터의 연산 결과가 모두 null 이 되는 SQL의 성질. 0으로 나누는 것을 피할 수 있음.
+ifnull vs nullif
ifnull(1,2) : 1이 null 이면 2 반환 -> coalesce 함수와 같은 기능. coalesce는 1,2 뿐만 아니라 여러 값을 넣을 수 있음
nullif(1,2) : 1과 2가 같으면 null 반환 -> nullif(impression, 0) : impression이 0이면 null 반환
# 6-8 0으로 나누는 것을 피해 CTR을 계산하는 쿼리
SELECT
dt
,ad_id
--CASE 식으로 분모가 0일 경우를 분기해서, 0으로 나누지 않게 만드는 방법
, CASE
WHEN impressions > 0 THEN 100.0 * clicks / impressions
END AS ctr_as_percent_by_case
-- 분모가 0이라면 NULL로 변환해서, 0으로 나누지 않게 만드는 방법
,100.0 * clicks / NULLIF(impressions, 0) AS ctr_as_percent_by_null
FROM
advertising_stats
ORDER BY
dt, ad_id
;
4) 두 값의 거리 계산하기
- 숫자 데이터의 절댓값, 제곱 평균 제곱근(RMS) 계산하기
1- 절댓값 계산 → ABS 함수(abstract)를 사용
2- 제곱평균제곱근 → 두 값의 차이를 제곱한뒤(POWER 함수), 제곱근을 적용해서 나오는 값(SORT 함수)
#6-9 일차원 데이터의 절댓값과 제곱 평균 제곱근을 계산하는 쿼리
SELECT
abs(x1-x2) AS abs
, sqrt(power(x1 - x2, 2)) AS rms
FROM location_1d
;
# 일차원 데이터에선 두 값 같음
- xy 평면 위에 있는 두 점의 유클리드 거리 계산하기
→ 제곱 평균 제곱근 사용, postgresql에는 point자료형이라고 불리는 좌표를 다루는 자료구조가 있음.
# 6-10 이차원 테이블에 대해 제곱 평균 제곱근(유클리드 거리)을 구하는 거리
SELECT
sqrt(power(x1 - x2, 2) + power(y1 - y2, 2)) AS dist
, point(x1,y1) <-> point(x2,y2) AS dist
FROM location_2d
;
5) 날짜/시간 계산하기
나이 → 생년월일 저장하고, 이후에 계산해서 나이 구함.
# 6-11 미래 또는 과거의 날짜, 시간을 계산하는 쿼리
# 회원 등록시간 1시간 후와 30분 전의 시간, 등록일의 다음 날과 1달 전의 날짜를 계산하는 쿼리, postgresql 기준
SELECT
user_id
, register_stamp::timestamp AS register_stamp
, register_stamp::timestamp + '1 hour' :: interval AS after_1_hour
, register_stamp::timestamp - '30 minutes' :: interval AS before_30_minutes
, register_stamp::date AS register_date
,(register_stamp::date + '1 day'::interval) :: date AS after_1_day
,(register_stamp::date - '1 month'::interval)::date AS before_1_month
FROM mst_users_with_dates
;
#실행결과
-[RECORD 1]--------
user_id | U001
register_stamp | 2016-02-28 10:00:00
after_1_hour | 2016-02-28 11:00:00
before_30_minutes | 2016-02-28 09:30:00
register_date | 2016-02-28
after_1_day | 2016-02-29
before_1_month | 2016-01-28
- 날짜 데이터들의 차이 계산하기
#6-12 두 날짜의 차이를 계산하는 쿼리
SELECT
user_id
,CURRENT_DATE AS today
,register_stamp::date AS register_date
,CURRENT_DATE - register_stamp::date AS diff_days
FROM mst_users_with_dates
;
- 사용자의 생년월일로 나이 계산하기
age 함수 사용 → 리턴값은 interval 자료형의 날짜 단위이므로, EXTRACT 함수로 연도(YEAR) 부분만 추출해야함.
디폴트로 현재 나이를 리턴하지만, 특정 날짜를 지정하면 해당 날짜에서의 나이를 리턴함.
#6-13 age 함수를 사용해 나이를 계산하는 쿼리
SELECT
user_id
,CURRENT_DATE AS today
,register_stamp::date AS register_date
,birth_date::date AS birth_date
,EXTRACT(YEAR FROM age(birth_date::date)) AS current_age
,EXTRACT(YEAR FROM age(register_stamp::date, birth_date::date)) AS register_age
FROM mst_users_with_dates
;
#6-16 등록 시점과 현재 시점의 나이를 문자열로 계산하는 쿼리
SELECT
user_id
,substring(register_stamp,1,10) AS register_date
,birth_date
--등록 시점의 나이 계산하기
,floor(
( CAST(replace(substring(register_stamp,1,10),'-','') AS integer)
- CAST(replace(birth_date, '-','') AS integer)
) / 10000
) AS register_age
--현재 시점의 나이 계산하기
,floor(
( CAST(replace(CAST(CURRENT_DATE AS text),'-','') AS integer)
- CAST(replace(birth_date, '-' , '') AS integer)
) /10000
) AS current_age
FROM mst_users_with_dates
;
6) IP 주소 다루기
- IP 주소 자료형 활용하기
inet 자료형 사용.
# 6-17 inet 자료형을 사용한 IP 주소 비교 쿼리
SELECT
CAST('127.0.0.1' AS inet) < CAST('127.0.0.2' AS inet) AS lt
,CAST('127.0.0.1' AS inet) > CAST('192.168.0.1' AS inet) AS gt
;
#실행결과
lt | gt
--------
t | f
추가로, address/y 형식의 네트워크 범위에 IP 주소가 포함되는지도 판정 가능 → >> 또는 << 연산자 사용
- 정수 또는 문자열로 IP 주소 다루기
- IP 주소를 정수 자료형으로 변환하기
# 6-19 IP 주소에서 4개의 10진수 부분을 추출하는 쿼리 SELECT ip ,CAST(split_part(ip, '.', 1) AS integer) AS ip_part_1 #.을 기준으로 나눈 것 중 첫번째 인수 ,CAST(split_part(ip, ',', 2) AS integer) AS ip_part_2 FROM (SELECT CAST('192.168.0.1' AS text) AS ip) AS t ;
# 6-20 IP 주소를 정수 자료형 표기로 변환하는 쿼리 SELECT ip ,CAST(split_part(ip, '.', 1) AS integer) * 2^24 + CAST(split_part(ip, ',', 2) AS integer) * 2^16 + CAST(split_part(ip, ',', 3) AS integer) * 2^8 + CAST(split_part(ip, ',', 4) AS integer) * 2^0 AS ip_integer FROM (SELECT CAST('192.168.0.1' AS text) AS ip) AS t ;
- IP주소를 0으로 메우기
# 6-21 IP 주소를 0으로 메운 문자열로 변환하는 쿼리
SELECT
ip
, lpad(split_part(ip,',',1) ,3 ,'0')
|| lpad(split_part(ip,',',2) ,3 ,'0')
|| lpad(split_part(ip,',',3) ,3 ,'0')
|| lpad(split_part(ip,',',4) ,3 ,'0')
AS ip_padding
FROM
(SELECT CAST('192.168.0.1' AS text) AS ip) AS t
;
#실행결과
ip | ip_padding
----------------
192.168.0.1 | 192168000001
lpad 함수는 지정한 문자수가 되게 문자열의 왼쪽을 메우는 함수.
모든 10진수가 3자리 수가 되게, 문자열의 왼쪽을 0으로 메우고 있음.
그렇게 메운 문자열을 || 연산자로 연결함.
'SQL' 카테고리의 다른 글
[데이터 분석을 위한 SQL 레시피] 9강 - 시계열 기반으로 데이터 집계하기 (0) | 2023.12.26 |
---|---|
[데이터 분석을 위한 SQL 레시피] 8강 - 여러 개의 테이블 조작하기 (0) | 2023.12.26 |
[데이터 분석을 위한 SQL 레시피] 7강 -하나의 테이블에 대한 조작 (0) | 2023.12.26 |
[데이터 분석을 위한 SQL 레시피] 5강 - 하나의 값 조작하기 (1) | 2023.12.26 |
[데이터 분석을 위한 SQL 레시피] 4강 - 데이터 (1) | 2023.12.26 |