CRM 전략에 필요한 데이터를 직접 분석했습니다
SQL로 고객과 결제, 수업 이력을 연결하고 Python으로 가공했습니다. CRM 전략에 필요한 분석부터 빅인 도입을 위한 데이터 정의까지 담당했습니다.
ACTUAL WORK
실제 자료로 보는 프로젝트
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: 자녀별 최초 성공 결제의 상품, 금액, 시점을 추출한 부분입니다. 전체 쿼리는 하단 원본 자료에서 확인할 수 있습니다.
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를 읽고 고객 중복을 정리한 뒤, 캠페인 시작 전후 가입자를 구분했습니다.
CONTEXT
어떤 문제에서 시작했는가
필요한 고객 정보를 사업부에서 직접 확인할 수 없을까?
사업부에서는 분석이 필요할 때마다 개발팀에 고객 데이터를 요청했습니다. 필요한 정보를 직접 확인할 수 있어야 문제의 원인과 실행 우선순위를 빠르게 판단할 수 있다고 생각했습니다.
MY CONTRIBUTION
판단을 실행으로 연결한 과정
업무 범위를 스스로 확장
팀장에게 데이터 업무를 맡겠다고 제안하고 개발팀의 접근 권한 협조를 얻었습니다. SQL을 익히며 고객과 자녀, 주문, 결제, 수업 데이터의 관계를 파악했습니다.
업무 목적에 맞는 쿼리 작성
최초/최근 성공 결제와 최근 수업 이력을 CTE로 분리하고 고객별로 연결했습니다. 구매 금액과 성공/취소 건수, 수업 및 그리기 이력 등을 함께 추출해 고객 프로파일링에 활용했습니다.
분석을 반복 업무에 적용
SQL로 추출한 CSV를 Python으로 가공하고, 중복 제거와 조건별 고객군 분류를 수행했습니다. 일별 캠페인 집계와 보고에도 적용해 같은 데이터를 반복해서 수작업으로 정리하는 부담을 줄였습니다.
CRM에서 사용할 데이터 정리
빅인 CRM 도입 과정에서 내부 개발팀과 외부 업체 사이의 데이터 정의와 연동 요구사항을 정리했습니다. 2025년 CRM 전략 수립에서도 데이터 분석을 담당했습니다.
OUTCOME
남긴 결과
고객 데이터 추출과 분석을 직접 수행하는 역할로 업무를 확장했습니다. 목적별 SQL과 데이터 연동 자료를 인수인계해 후속 업무에 활용할 수 있도록 남겼습니다.
이 경험이 제 일하는 방식에 남긴 것
도구를 배우는 것보다 중요한 것은 업무에서 답해야 할 질문을 정하고, 그 질문에 필요한 데이터를 정의하는 일이었습니다.
WORK EVIDENCE
출처와 추가 자료
- 인수인계서.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: 체험이 아닌 결제를 성공한 고객의 구매 정보