프로젝트 목록
01아이스크림아트CRM 데이터 기반

CRM 전략에 필요한 데이터를 직접 분석했습니다

SQL로 고객과 결제, 수업 이력을 연결하고 Python으로 가공했습니다. CRM 전략에 필요한 분석부터 빅인 도입을 위한 데이터 정의까지 담당했습니다.

SQL / MySQLPython / pandas고객 분석CRM 도입
맡은 역할SQL 추출 및 분석 / Python 데이터 가공 / CRM 데이터 정의 / 연동 요구사항 정리프로젝트 시기2024 - 2025

실제 자료로 보는 프로젝트

CTE로 이력을 분리최초/최근 성공 결제와 최근 수업 이력을 각각 정의한 뒤 고객별로 연결했습니다. 아래는 첫 번째 CTE의 원본 발췌입니다.
SQL / Script-21.sql실제 작성 코드 발췌
WITH FirstSuccessfulPayments AS (
    SELECT 
        s.id AS studentID,
        o.price AS fstpr,
        o.package_name AS fstpkg,
        p.created_at AS fsttime
    FROM
        bonbon.`User` u 
    JOIN
        bonbon.Student s ON u.id = s.user_id
    LEFT JOIN
        bonbon.`Order` o ON s.id = o.student_id 
    LEFT JOIN 
        bonbon.Payment p ON o.id = p.order_id 
    WHERE 
        p.status LIKE "%SUCCESS%"
    AND 
        p.created_at = (
            SELECT MIN(p2.created_at)
            FROM bonbon.Payment p2
            JOIN bonbon.`Order` o2 ON p2.order_id = o2.id
            WHERE o2.student_id = s.id AND p2.status LIKE "%SUCCESS%"
        )
    GROUP BY
        s.id
)

FirstSuccessfulPayments: 자녀별 최초 성공 결제의 상품, 금액, 시점을 추출한 부분입니다. 전체 쿼리는 하단 원본 자료에서 확인할 수 있습니다.

Python / MBTI_report.ipynb실제 작성 코드 발췌
df = pd.read_csv(raw_file)
df['phone_number'] = df['phone_number'].replace('-', '', regex=True)
unique_users = df.drop_duplicates(subset='phone_number', keep='first')
mbti_eded_users = unique_users[unique_users['Is_MBTI25'] == 1]
created_after_MBTI = mbti_eded_users[mbti_eded_users['ID_created_at'] >= '2024-07-10']
created_before_MBTI = mbti_eded_users[mbti_eded_users['ID_created_at'] < '2024-07-10']

CSV를 읽고 고객 중복을 정리한 뒤, 캠페인 시작 전후 가입자를 구분했습니다.

추출에서 활용까지고객 프로파일링, 체험 이후 구매 이력, 일별 캠페인 보고, CRM 연동 데이터 정의에 활용했습니다.

어떤 문제에서 시작했는가

필요한 고객 정보를 사업부에서 직접 확인할 수 없을까?

사업부에서는 분석이 필요할 때마다 개발팀에 고객 데이터를 요청했습니다. 필요한 정보를 직접 확인할 수 있어야 문제의 원인과 실행 우선순위를 빠르게 판단할 수 있다고 생각했습니다.

판단을 실행으로 연결한 과정

01

업무 범위를 스스로 확장

팀장에게 데이터 업무를 맡겠다고 제안하고 개발팀의 접근 권한 협조를 얻었습니다. SQL을 익히며 고객과 자녀, 주문, 결제, 수업 데이터의 관계를 파악했습니다.

02

업무 목적에 맞는 쿼리 작성

최초/최근 성공 결제와 최근 수업 이력을 CTE로 분리하고 고객별로 연결했습니다. 구매 금액과 성공/취소 건수, 수업 및 그리기 이력 등을 함께 추출해 고객 프로파일링에 활용했습니다.

03

분석을 반복 업무에 적용

SQL로 추출한 CSV를 Python으로 가공하고, 중복 제거와 조건별 고객군 분류를 수행했습니다. 일별 캠페인 집계와 보고에도 적용해 같은 데이터를 반복해서 수작업으로 정리하는 부담을 줄였습니다.

04

CRM에서 사용할 데이터 정리

빅인 CRM 도입 과정에서 내부 개발팀과 외부 업체 사이의 데이터 정의와 연동 요구사항을 정리했습니다. 2025년 CRM 전략 수립에서도 데이터 분석을 담당했습니다.

남긴 결과

고객 데이터 추출과 분석을 직접 수행하는 역할로 업무를 확장했습니다. 목적별 SQL과 데이터 연동 자료를 인수인계해 후속 업무에 활용할 수 있도록 남겼습니다.

+

이 경험이 제 일하는 방식에 남긴 것

도구를 배우는 것보다 중요한 것은 업무에서 답해야 할 질문을 정하고, 그 질문에 필요한 데이터를 정의하는 일이었습니다.

출처와 추가 자료

  • 인수인계서.docx
  • MBTI_report.ipynb / pandas 가공 및 일별 보고
  • Script-17.sql / Script-21.sql / Script-28.sql
  • FGI_class.sql / FGI_order.sql
  • DB연동 swagger명세서 cdp-api-new.json
원본 SQL 더 보기 / Script-21.sql
/*
고객 프로파일링 + 혼자 그리기 횟수
*/
USE bonbon;


WITH FirstSuccessfulPayments AS (
    SELECT 
        s.id AS studentID,
        o.price AS fstpr,
        o.package_name AS fstpkg,
        p.created_at AS fsttime
    FROM
        bonbon.`User` u 
    JOIN
        bonbon.Student s ON u.id = s.user_id
    LEFT JOIN
        bonbon.`Order` o ON s.id = o.student_id 
    LEFT JOIN 
        bonbon.Payment p ON o.id = p.order_id 
    WHERE 
        p.status LIKE "%SUCCESS%"
    AND 
        p.created_at = (
            SELECT MIN(p2.created_at)
            FROM bonbon.Payment p2
            JOIN bonbon.`Order` o2 ON p2.order_id = o2.id
            WHERE o2.student_id = s.id AND p2.status LIKE "%SUCCESS%"
        )
    GROUP BY
        s.id
), LatestSuccessfulPayments AS (
    SELECT 
        s.id AS studentID,
        o.price AS lstpr,
        o.package_name AS lstpkg,
        MIN(p.created_at) AS lsttime
    FROM
        bonbon.`User` u 
    JOIN
        bonbon.Student s ON u.id = s.user_id
    LEFT JOIN
        bonbon.`Order` o ON s.id = o.student_id 
    LEFT JOIN 
        bonbon.Payment p ON o.id = p.order_id 
    WHERE 
        p.status LIKE "%SUCCESS%"
    AND 
        p.created_at = (
            SELECT MAX(p2.created_at)
            FROM bonbon.Payment p2
            JOIN bonbon.`Order` o2 ON p2.order_id = o2.id
            WHERE o2.student_id = s.id AND p2.status LIKE "%SUCCESS%"
            )
    GROUP BY
        s.id
), LatestLesson AS (
                SELECT 
                        s.id AS studentID,
                        l.start_at AS lstLsntime,
                        l.status AS lstLsnSt,
                        l.curriculum_type_id AS lstLsnTp
                FROM bonbon.Student s 
                JOIN bonbon.Lesson l ON l.student_id = s.id 
                WHERE 
                        l.start_at = (
                                SELECT MAX(l2.start_at)
                                FROM bonbon.Student s2
                                JOIN bonbon.Lesson l2 ON l2.student_id = s2.id
                                WHERE l2.student_id = s.id
                                )
                GROUP BY s.id
)


SELECT 
        u.id AS userID,
        u.name AS username,
        NULL AS prntGender,
        NULL AS prntBirth,
        TRUNCATE((TO_DAYS(NOW()) - TO_DAYS(NULL)) / 365,0) AS prntAge,
        NULL AS prntOcptn,
        NULL AS prntAdr,
        NULL AS childInfo,
        u.phone_no AS phoneNumber,
        u.email_addr AS Email,
                    u.login_id AS loginID,
        u.created_at + INTERVAL 9 HOUR AS signUpDate,
        u.marketing_argreed_at + INTERVAL 9 HOUR AS mktAgrAt,
        MIN(p.created_at) + INTERVAL 9 HOUR AS firstPayment,
        fst.fsttime + INTERVAL 9 HOUR AS noRfndFstPTime,
        fst.fstpkg AS noRfndFstPName,
        fst.fstpr AS noRfndFstPPrice,
        lst.lsttime + INTERVAL 9 HOUR AS noRfndLstPTime,
        lst.lstpkg AS noRfndLstPName,
        lst.lstpr AS noRfndLstPPrice,
        lstlsn.lstLsnTp,
        lstlsn.lstLsnSt,
        SUM(CASE WHEN p.status LIKE '%SUCCESS%' THEN o.price ELSE 0 END) AS allPaid,
        DATEDIFF(lstlsn.lstLsntime, NOW()) AS dayPssdLstLsn,
        DATEDIFF(u.last_logined_at, NOW()) AS dayPssdLstgn,
        COUNT(CASE WHEN p.status LIKE '%SUCCESS%' THEN 1 END) AS successCount,
        COUNT(CASE WHEN p.status LIKE '%CANCEL%' THEN 1 END) AS cancelCount,
        COUNT(CASE WHEN p.status LIKE '%SUCCESS%' AND o.price = 0 THEN 1 END) AS zeroSuccess,
        COUNT(CASE WHEN p.status LIKE '%CANCEL%' AND o.price = 0 THEN 1 END) AS zeroCancel,
        COUNT(p.status) AS paymentCount,
        s.id AS stdntID,
        s.name AS stdntName,
        s.gender AS stdntGender,
        s.birth_date AS stdntBirth,
        TRUNCATE((TO_DAYS(NOW()) - TO_DAYS(s.birth_date)) / 365,0) AS stdntAge,
        drwcnt.drwcnt AS slfDrwCnt

FROM
        bonbon.`User` u 
JOIN
        bonbon.Student s ON u.id = s.user_id
LEFT JOIN
        bonbon.`Order` o ON s.id = o.student_id 
LEFT JOIN 
        bonbon.Payment p ON o.id = p.order_id 
LEFT JOIN 
        FirstSuccessfulPayments fst ON fst.studentID = s.id
LEFT JOIN 
        LatestSuccessfulPayments lst ON lst.studentID = s.id
LEFT JOIN
        LatestLesson lstlsn ON lstlsn.studentID = s.id
LEFT JOIN 
				(
				SELECT
					COUNT(*) AS drwcnt
					, da.student_id
				FROM
					bonbon.DrawingArchive da 
				WHERE 
					da.lesson_id IS NULL AND 
					da.category LIKE "%drawing%"
				GROUP BY
					da.student_id
				) drwcnt ON drwcnt.student_id = s.id
WHERE 
        o.id IS NOT NULL
GROUP BY 
        s.id 
ORDER BY allPaid DESC, u.id

보관 중인 작업 코드입니다. 실제 데이터베이스에 연결하거나 실행하지 않습니다.

인수인계서에 남긴 쿼리별 용도
17: 고객 프로파일링
21: 고객 프로파일링 + 혼자 그리기 횟수
22: 모든 구매 건에 대해 프로파일링
23: 한 달 기준 수업 스케줄 신청 분포
24: 선생님의 수업 가능한 슬롯 분포
25: 결제가 일어난 시각 분포
28: 체험 수업 완료 고객의 구매 이력
29: 체험이 아닌 결제를 성공한 고객의 구매 정보
원본 자료