본문 바로가기
데이터분석 부트캠프

데이터분석 부프캠프 14기 - 9주차_MySQL

by 푸리세상 2024. 6. 19.
728x90

◆ 목차

  • 문제풀기를 위한 정리
  • 실습 문제 풀기
  • 여러 문법

 

 


 

 

1. MySQL - 문제풀기를 위한 정리

 

 

날짜, 시간별 정리

SELECT NOW();
SELECT CURRENT_DATE();
SELECT EXTRACT(MONTH FROM "2021-01-01");  -- 날짜에서 month(월)만 뽑기
SELECT DAY("2021-01-01");  -- 날짜에서 day(일)만 뽑기
SELECT DATE_ADD("2021-01-01", INTERVAL 7 DAY); -- 날짜 더해주기
SELECT DATE_SUB("2017-06-15", INTERVAL 7 DAY); -- 날짜 빼기
SELECT DATEDIFF("2017-06-25", "2017-06-15"); -- 두개의 날짜 간격 뽑기
SELECT TIMEDIFF("2021-01-25 12:10:00", "2021-01-25 10:10:00"); -- 두개의 시간 간격 뽑기
SELECT DATE_FORMAT(NOW(), "%Y-%m-%d"); -- 현재 날짜, 시간에서 지정된 부분까지 조회

 

 

LIMIT와 OFFSET

LIMIT

  - 쿼리 결과에서 반환할 행(row)의 최대 개수를 지정

 

 

OFFSET

  - 쿼리 결과에서 시작할 위치를 지정

  - 단독 사용이 불가능, LIMIT와 같이 사용

 

WITH 문

  - 서브 쿼리를 사용해서 임시 테이블처럼 사용할 수 있는 구문

WITH 가상테이블명 AS
(
 SELECT * FROM 테이블명
 WHERE 조건식
)

SELECT * FROM 가상테이블명;

 

 

LAG

  - 이전 행을 가져옴

 

LEAD

  - 특정 위치의 행을 가져옴

  - default는 1 : 다음행을 가져

 

 


 

 

 

2. MySQL - 실습 문제 풀기

 

 

연습문제 1)

2020년 7월의 총 Revenue(수익)를 구하기

SELECT SUM(price) AS Revenue FROM tbl_purchase
WHERE YEAR(purchased_at) = "2020"
AND MONTH(purchased_at) = "7";

 

 

 

연습문제 2)

2020년 7월의 MAU(월별 활동한 이용자)를 구하기

SELECT DISTINCT COUNT(customer_id) FROM tbl_visit
WHERE YEAR(visited_at) = "2020"
AND MONTH(visited_at) = "7";

 

 

 

 

연습문제 3)

7월에 우리 Active 유저의 구매율(Paying Rate)은 어떻게 되나요?

-- 구매유저 수 / 전체 활성유저
-- 전체 활성 유저 : 16414
SELECT COUNT(DISTINCT customer_id) FROM tbl_visit
WHERE visited_at LIKE "2020-07%";

-- 구매유저 수 : 11174
SELECT COUNT(DISTINCT customer_id) FROM tbl_purchase
WHERE purchased_at LIKE "2020-07%";

SELECT ROUND(11174 / 16414 * 100);

 

 

 

연습문제 4)

7월에 구매 유저의 월 평균 구매금액은 어떻게 되나요?

SELECT AVG(Revenue) FROM (SELECT customer_id, sum(price) AS Revenue FROM tbl_purchase
WHERE purchased_at LIKE "2020-07%"
GROUP BY customer_id) AS coustomer_avg;

 

 

연습문제 5)

7월에 가장 많이 구매한 고객 Top3와 Top10 ~ 15 고객을 뽑기

-- Top3
SELECT DISTINCT customer_id, SUM(price) AS Revenue FROM tbl_purchase
WHERE purchased_at LIKE "2020-07%"
GROUP BY customer_id
ORDER BY Revenue DESC
LIMIT 3;

-- Top10 ~ 15
SELECT DISTINCT customer_id, SUM(price) AS Revenue FROM tbl_purchase
WHERE purchased_at LIKE "2020-07%"
GROUP BY customer_id
ORDER BY Revenue DESC
LIMIT 9, 5;

-- OFFSET 활용
SELECT DISTINCT customer_id, SUM(price) AS Revenue FROM tbl_purchase
WHERE purchased_at LIKE "2020-07%"
GROUP BY customer_id
ORDER BY Revenue DESC
LIMIT 6 OFFSET 10;

 

 

 

 

연습문제 6)

2020년 7월의 평균 DAU를 구하기, Active user 수가 증가하는 추세인가?

-- DAU = 일별 활동 유저
-- 구해보니 8월1일이 같이 조회 -> %T를 사용해서 시간이 바뀌는걸 확인하기
SELECT *, DATE_FORMAT(visited_at, "%Y-%m-%d %T") AS date
FROM tbl_visit
WHERE visited_at LIKE "2020-07%";


-- 적용하기
SELECT AVG(customer)
FROM(SELECT DATE_FORMAT(visited_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS date,
COUNT(DISTINCT customer_id) AS customer
FROM tbl_visit
WHERE visited_at LIKE "2020-07%"
GROUP BY 1
ORDER BY 1) AS AD;

 

 

 

연습문제 7)

2020년 7월의 평균 WAU를 구하기

-- %U = 몇번째 주를 나타냄
SELECT AVG(customer) FROM (SELECT DATE_FORMAT(visited_at - INTERVAL 9 HOUR, "%Y-%m-%U") AS date,
COUNT(DISTINCT customer_id) AS customer
FROM tbl_visit
WHERE visited_at >= "2020-07-05"
AND visited_at < "2020-07-26"
GROUP BY 1
ORDER BY 1) AS AD;

 

 

 

연습문제 8)

2020년 7월의 Daily Devenue는 증가하는 추세인가요? 평균 Daily Revenue도 구하기

-- 2020년 7월의 Daily Devenue 추세
SELECT DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS date_at, 
SUM(price) AS revenue
FROM tbl_purchase
WHERE purchased_at >= "2020-07-01"
AND purchased_at < "2020-08-01"
GROUP BY 1
ORDER BY 1;

-- 2020년 7월의 평균 Daily Revenue도 구하기
SELECT AVG(revenue) FROM
(SELECT DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS date_at, 
SUM(price) AS revenue
FROM tbl_purchase
WHERE purchased_at >= "2020-07-01"
AND purchased_at < "2020-08-01"
GROUP BY 1
ORDER BY 1) DD;

 

 

 

 

연습문제 9)

2020년 7월의 평균 Weekly Devenue 구하기

SELECT AVG(revenue) FROM (
SELECT DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%U") AS date_at, SUM(price) AS revenue
FROM tbl_purchase
WHERE purchased_at >= "2020-07-05"
AND purchased_at < "2020-07-26"
GROUP BY 1
ORDER BY 1) WD;

 

 

 

연습문제 10)

2020년 7월의 요일별 Devenue를 구하기/어느 요일이 Devenue 가장 높고 어느 요일이 Devenue 가장 낮은가요? 

SELECT DATE_FORMAT(date_at, "%W") AS day_order,
		DATE_FORMAT(date_at, "%W") AS day_name, 
		AVG(revenue) 
FROM(
	SELECT DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS date_at, 
				SUM(price) AS revenue
		FROM tbl_purchase
		WHERE purchased_at >= "2020-07-01"
		AND purchased_at < "2020-08-01"
		GROUP BY 1) WD
GROUP BY 1, 2
ORDER BY 1;

 

 

 

 

연습문제 11)

2020년 07월 시간대별 Revenue를 구하기 / 어느 시간대 Revenue가 가장 높고 어느 시간대 Revenue가 가장 낮은지

SELECT hour_at, AVG(revenue)
FROM(
	SELECT DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS date_at,
			DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%H") AS hour_at,
			SUM(price) AS revenue
		FROM tbl_purchase
		WHERE purchased_at >= "2020-07-01"
		AND purchased_at < "2020-08-01"
		GROUP BY 1, 2) AD
GROUP BY 1
ORDER BY 2 DESC;

 

 

 

 

연습문제 12)

2020년 07월 요일 및 시간대별 Revenue를 구하기 / 어느 요일 및 시간대가 Revenue이 가장 높고 어느 시간대가 Revenue이 가장 낮은지

SELECT day_week_at, hour_at, AVG(revenue) 
FROM (
		SELECT DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS date_at,
				DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%W") AS day_week_at,
				DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%H") AS hour_at,
				SUM(price) AS revenue
		FROM tbl_purchase
		WHERE purchased_at >= "2020-07-01"
		AND purchased_at < "2020-08-01"
		GROUP BY 1, 2, 3) AD
GROUP BY 1, 2
ORDER BY 3 DESC;

 

 

 

연습문제 13)

전체 유저의 Demograhic을 구하기 / 성, 연령별로 유저 숫자 구하기.
어느 세그먼트가 가장 숫자가 많은지?   성별은 하나로, 연련은 5세 단위로 적당히 묶고 숫자가 높은 순서대로 보이기

SELECT CASE WHEN length(gender) < 1 THEN "Others"
	    ELSE gender END AS gender,
	CASE WHEN age <= 15 THEN "0_15세 이하"
    	     WHEN age <= 20 THEN "1_15 ~ 20세"
             WHEN age <= 25 THEN "2_21 ~ 25세"
             WHEN age <= 30 THEN "3_26 ~ 30세"
       	     WHEN age <= 35 THEN "4_31 ~ 35세"
       	     WHEN age <= 40 THEN "5_36 ~ 40세"
       	     WHEN age <= 45 THEN "6_41 ~ 45세"
       	     WHEN age <= 50 THEN "7_46세 이상"
             END AS age,
	COUNT(*)
FROM tbl_customer
GROUP BY 1, 2
ORDER BY 3 DESC;

 

 

 

연습문제 14)

연습문제 13 결과의 성, 연령을 성별(연령) EX)남성(25세-29세 이하)으로 통합하기

각 성, 연령이 전체 고객에서 얼마나 차지하는지 분포(%)를 알려주세요. 분포가 높은 순서대로 

SELECT CONCAT(CASE WHEN length(gender) < 1 THEN "기타"
	           WHEN gender = "Others" THEN "기타"
                   WHEN gender = "M" THEN "남성"
                   WHEN gender = "F" THEN "여성"
                   END, "(",
	CASE WHEN age <= 15 THEN "0_15세 이하"
	     WHEN age <= 20 THEN "1_15 ~ 20세"
             WHEN age <= 25 THEN "2_21 ~ 25세"
             WHEN age <= 30 THEN "3_26 ~ 30세"
             WHEN age <= 35 THEN "4_31 ~ 35세"
             WHEN age <= 40 THEN "5_36 ~ 40세"
             WHEN age <= 45 THEN "6_41 ~ 45세"
             WHEN age <= 50 THEN "7_46세 이상"
             END, ")" ) AS segement,
	ROUND(COUNT(*) / (SELECT COUNT(*) FROM tbl_customer) * 100, 2) AS per
FROM tbl_customer
GROUP BY 1
ORDER BY 2 DESC;

 

 

 

연습문제 15)

2020년 7월의 성별/연령대에 따라 구매 건수와, 총 Revenue를 구하기

남, 녀 이외의 성별은 하나로 묶어주기

SELECT CASE WHEN B.gender = "M" THEN "남성"
 	    WHEN B.gender = "F" THEN "여성"
            WHEN B.gender = "Others" THEN "기타"
            WHEN LENGTH(B.gender) < 1 THEN "기타"
            END AS gender,
       COUNT(*) AS cnt,
       SUM(price) AS revenue
FROM tbl_purchase A
LEFT JOIN tbl_customer B
on A.customer_id = B.customer_id
WHERE A.purchased_at >= "2020-07-01"
AND A.purchased_at < "2020-08-01"
GROUP BY 1;

 

 

 

 

연습문제 16)

2020년 7월의 성별 / 연령대에 따라 구매 건수와 총 Revenue를 구하기

남, 녀 이외의 성별은 하나로 묶기

SELECT CASE WHEN B.gender = "M" THEN "남성"
            WHEN B.gender = "F" THEN "여성"
            WHEN B.gender = "Others" THEN "기타"
            WHEN LENGTH(B.gender) < 1 THEN "기타"
            END AS gender,
	CASE WHEN age <= 15 THEN "15세 이하"
             WHEN age <= 20 THEN "15 ~ 20세"
             WHEN age <= 25 THEN "21 ~ 25세"
             WHEN age <= 30 THEN "26 ~ 30세"
             WHEN age <= 35 THEN "31 ~ 35세"
             WHEN age <= 40 THEN "36 ~ 40세"
             WHEN age <= 45 THEN "41 ~ 45세"
             WHEN age >= 46 THEN "46세 이상"
             END AS age_group,
        COUNT(*) AS cnt,
        SUM(price) AS revenue
FROM tbl_purchase A
LEFT JOIN tbl_customer B
on A.customer_id = B.customer_id
WHERE A.purchased_at >= "2020-07-01"
AND A.purchased_at < "2020-08-01"
GROUP BY 1, 2
ORDER BY 4 DESC;

 

 

 

 

연습문제 17)

2020년 7월 일별 매출과 증감폭, 증감률을 구하기

-- 7월에 대한 일별 매출
SELECT DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS D_DATE,
       SUM(price) AS revenue
FROM tbl_purchase
WHERE purchased_at >= "2020-07-01"
AND purchased_at < "2020-08-01"
GROUP BY 1;

-- 임시 테이블 생성 WITH AS
WITH tbl_revenue AS(
SELECT DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS D_DATE,
       SUM(price) AS revenue
FROM tbl_purchase
WHERE purchased_at >= "2020-07-01"
AND purchased_at < "2020-08-01"
GROUP BY 1)


-- 전일 매출 가져오기
SELECT *, LAG(revenue) OVER(ORDER BY D_DATE ASC)
FROM tbl_revenue;

-- 전일 대비 증감폭
SELECT *, revenue - LAG(revenue) OVER(ORDER BY D_DATE ASC)
FROM tbl_revenue;

-- 증감률
SELECT *, revenue - LAG(revenue) OVER(ORDER BY D_DATE ASC) AS diff_revenue,
          ROUND((revenue - LAG(revenue) OVER(ORDER BY D_DATE ASC)) / LAG(revenue) OVER(ORDER BY D_DATE ASC) * 100, 2) AS chg_revenue
FROM tbl_revenue;

전일 매출 구하기

 

전일 대비 증감폭 구하기

 

증감률 구하기

 

CSV 데이터 저장-> 그래프로 확인

 

 

 

연습문제 18)

2020년 7월 일별로 많이 구매한 고객들한테 소정의 선물을 줄려고함

7월에 일별로 구매 금액 기준으로 가장 많이 지출한 고객 Top3는?

SELECT * FROM(
	         SELECT DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%d") D_DATE,
		        ustomer_id,
                        SUM(price),
		        DENSE_RANK() OVER (PARTITION BY DATE_FORMAT(purchased_at - INTERVAL 9 HOUR, "%Y-%m-%d") ORDER BY SUM(price) DESC) AS rank_rev
                 FROM tbl_purchase
                 WHERE purchased_at >= "2020-07-01"
                 AND purchased_at < "2020-08-01"
                 GROUP BY 1, 2) sub
WHERE rank_rev < 4;

 

 

 

 

연습문제 19)

2020년 7월 우리 신규유저가 하루 안에 결제로 넘어가는 비율이 어떻게 되는지 구하시오
그 비율이 어떤지 알고 싶고 결제까지 보통 몇 분 정도가 소요되는지 확인 / 
→ 신규유저의 가입일, 최초 구매일을 알아보기

-- 신규유저 가입일은 customer 테이블에서 확인
SELECT * FROM tbl_customer;

 

-- 최초 구매일
SELECT customer_id, MIN(purchased_at) AS purchased_at
FROM tbl_purchase
GROUP BY customer_id;

 

 

-- 결제하지 않은 고객 정보 알아내기
SELECT A.customer_id,
       A.created_at,
       B.customer_id AS paying_user,
       B.purchased_at
FROM tbl_customer A
LEFT JOIN (SELECT customer_id,
                  MIN(purchased_at) AS purchased_at
	   FROM tbl_purchase
	   GROUP BY 1) B
ON A.customer_id = B.customer_id
AND B.purchased_at < A.created_at + INTERVAL 1 DAY
WHERE A.created_at >= "2020-07-01"
AND A.created_at < "2020-08-01";

paying_user null이라면 구매한적이 X

 

 

-- 결제로 넘어가는 비율
WITH tbl_rt AS (
		SELECT A.customer_id,
	               B.customer_id AS paying_user,
                       TIME_TO_SEC(TIMEDIFF(B.purchased_at, A.created_at)) / 3600 AS diff_hour
                FROM tbl_customer A
                LEFT JOIN (SELECT customer_id,
		                  MIN(purchased_at) AS purchased_at
		           FROM tbl_purchase
		           GROUP BY 1) B
         ON A.customer_id = B.customer_id
         AND B.purchased_at < A.created_at + INTERVAL 1 DAY
         WHERE A.created_at >= "2020-07-01"
         AND A.created_at < "2020-08-01")
SELECT ROUND(COUNT(paying_user) / COUNT(customer_id) * 100, 2)
FROM tbl_rt;

 

 

-- 결제까지 보통 몇 분 정도 소요인지 구하기
WITH tbl_rt AS (
                SELECT A.customer_id,
                       B.customer_id AS paying_user,
                       TIME_TO_SEC(TIMEDIFF(B.purchased_at, A.created_at)) / 3600 AS diff_hour
                FROM tbl_customer A
                LEFT JOIN (SELECT customer_id,
                                  MIN(purchased_at) AS purchased_at
                           FROM tbl_purchase
                           GROUP BY 1) B
                ON A.customer_id = B.customer_id
                AND B.purchased_at < A.created_at + INTERVAL 1 DAY
                WHERE A.created_at >= "2020-07-01"
                AND A.created_at < "2020-08-01")
SELECT AVG(diff_hour)
FROM tbl_rt;

 

 

 

연습문제 20)

2020년 7월 우리 서비스는 유저의 재방문율이 높은 서비스인지?
이를 파악하기 위해 7월 기준 Day1 Retention이 어떤지 구해주고 추세를 보기 위해 Daily로 추출하기

SELECT DATE_FORMAT(A.visited_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS D_DATE,
       COUNT(DISTINCT A.customer_id) AS active_user,
       COUNT(DISTINCT B.customer_id) AS ratained_user,
       COUNT(DISTINCT B.customer_id) / COUNT(DISTINCT A.customer_id) AS retention
FROM tbl_visit A
LEFT JOIN tbl_visit B
ON A.customer_id = B.customer_id
AND DATE_FORMAT(A.visited_at - INTERVAL 9 HOUR, "%Y-%m-%d") = DATE_FORMAT(B.visited_at - INTERVAL 9 HOUR - INTERVAL 1 DAY, "%Y-%m-%d")
WHERE A.visited_at >= "2020-07-01"
AND A.visited_at < "2020-08-01"
GROUP BY 1;

 

 

 

연습문제 22)

우리 서비스는 신규유저가 많은지? 기존 유저가 많은지?
가입기간별로 고객 분포가 어떤지 DAU 기준으로 알려주기

WITH tbl_visit_by_joined AS (SELECT DATE_FORMAT(A.visited_at - INTERVAL 9 HOUR, "%Y-%m-%d") AS D_DATE,
                                    A.customer_id,
                                    B.created_at AS d_joined,
                                    MAX(A.visited_at) AS last_visit,
                                    DATEDIFF(MAX(A.visited_at), B.created_at) AS DATE_diff
                             FROM tbl_visit A
                             LEFT JOIN tbl_customer B
                             ON A.customer_id = B.customer_id
                             WHERE A.visited_at >= "2020-07-01"
                             AND A.visited_at < "2020-08-01"
                             GROUP BY 1, 2, 3)
SELECT A.D_DATE,
       CASE WHEN A.date_diff >= 730 THEN "2년 이상"
            WHEN A.date_diff >= 365 THEN "1년 이상"
            WHEN A.date_diff >= 183 THEN "6개월 이상"
            WHEN A.date_diff >= 91 THEN "3개월 이상"
            WHEN A.date_diff >= 30 THEN "1개월 이상"
            ELSE "1개월 미만"
            END AS segement,
       B.all_users,
       COUNT(A.customer_id) AS users,
       ROUND(COUNT(A.customer_id) / B.all_users * 100, 2) AS per
FROM tbl_visit_by_joined A
LEFT JOIN (SELECT D_DATE,
                  COUNT(customer_id) AS all_users
                  FROM tbl_visit_by_joined
                  GROUP BY 1) B
ON A.D_DATE = B.D_DATE
GROUP BY 1, 2, 3
ORDER BY 1,2;

 

 


 

3. 함수 정리

 

 

◆ PARTITION BY

  - 데이터를 특정 기준에 따라 파티셔닝(분할)하여 각 파티션 내에서 계산을 할 수 있도록 해줌

<윈도우 함수> OVER (PARTITION BY <파티션 기준 열> ORDER BY <정렬 기준 열>)

 

 

윈도우 프레임을 정의할 때 사용하는 절

  - ROWS

  - RANGE

 

ROWS

  - 물리적인 행 기준으로 윈도우 프레임을 정의 / 현재 행을 기준으로 몇 개의 행을 포함할지를 지정

 

RANGE

  - 논리적인 값 범위 기준으로 윈도우 프레임을 정의 / 현재 행의 값과 동일한 값 또는 범위에 속하는 행들을 포함

 

 

■ 윈도우 함수 종류

  - ROW_NUMBER() : 각 행에 순서를 매기는 함, 결과 집합에서 각 행에 순차적으로 번호를 할당

SELECT ROW_NUMBER() OVER (ORDER BY [컬럼명] DESC) AS rank, emp_name, salary
FROM [테이블명];

 

 

  - RANK() : 동점자가 있을 경우 같은 순위를 부여하고 다음 순위는 건너뛰는 함수

SELECT RANK() OVER (ORDER BY [컬럼명] DESC) AS rank, student_name, score
FROM [테이블명];

 

  - DENSE_RANK() : 동점자가 있을 경우 같은 순위를 부여하지만 다음 순위를 건너뛰지 않고 증가시키는 함수

SELECT DENSE_RANK() OVER (ORDER BY [컬럼명] DESC) AS rank, salesperson, sales
FROM [테이블명];

 

  - SUM()

  - AVG()

 

 PERCENT_RANK()

  -  행의 백분위 순위를 계산

 

■ CUME_DIST()

  - 행의 누적 분포를 계산

 

■ NTILE(n)

  - 행을 n개의 그룹으로 분할

 

 

■ WITH ROLLUP

  - 집계 함수를 사용할 때 추가적인 총계 행을 생성하는 기능

SELECT [컬럼 이름1], [컬럼 이름2], SUM(sales) AS total_sales
FROM [테이블 이름]
GROUP BY [컬럼 이름1], [컬럼 이름2] WITH ROLLUP;

 

 

예제)

-- 동물이 몇마리인지 구하기
SELECT animal, COUNT(*) FROM animal_info GROUP BY animal;

 

 

- 위에 각 동물에 대한 수를 구했다면 WITH ROLLUP을 사용하여 모든 동물의 총 합계를 구하기

-- 동물들의 총합계를 구하기
SELECT animal, COUNT(*) FROM animal_info GROUP BY animal WITH ROLLUP;

 

 

- 동물별 이름과 타입으로 GROUP BY

-- GROUP BY를 함께 사용하여 동물들의 타입별 총합계를 구하기
SELECT animal, type, COUNT(*) FROM animal_info GROUP BY animal, type WITH ROLLUP;

728x90