์‹ ๊ทœ ๊ฐ€์ž…์ž์˜ 2์ฃผ์ฐจ ์žฌ๋ฐฉ๋ฌธ์œจ ๊ฐ์†Œ ๋ฌธ์ œ

์‹ ๊ทœ ๊ฐ€์ž…์ž์˜ 2์ฃผ์ฐจ ์žฌ๋ฐฉ๋ฌธ์œจ ๊ฐ์†Œ ๋ฌธ์ œ

2025. 7. 22. 17:46ใ†Tools & Skills/SQL

PM๊ณผ ๋งˆ์ผ€ํŒ… ํŒ€์˜ ์˜๊ฒฌ ์ฐจ์ด

๐Ÿ“Œ ๋น„์ง€๋‹ˆ์Šค ๋ฌธ์ œ ์‹œ๋‚˜๋ฆฌ์˜ค

์ตœ๊ทผ ์›”๊ฐ„ ํ™œ์„ฑ ์‚ฌ์šฉ์ž(MAU)๋Š” ์•ˆ์ •์ ์ด์ง€๋งŒ, ์‹ ๊ทœ ๊ฐ€์ž…์ž์˜ 2์ฃผ์ฐจ ์žฌ๋ฐฉ๋ฌธ์œจ์ด 20% ์ดํ•˜๋กœ ๋–จ์–ด์ง
PM์€ "์ถ”์ฒœ ์ƒํ’ˆ ๋…ธ์ถœ ๋ฐฉ์‹"์ด ๋ฌธ์ œ๋ผ๊ณ  ๊ฐ€์„ค์„ ์„ธ์› ์ง€๋งŒ,
๋งˆ์ผ€ํŒ…ํŒ€์€ "์‹ ๊ทœ ์‚ฌ์šฉ์ž ์˜จ๋ณด๋”ฉ ํ”„๋กœ์„ธ์Šค์˜ ๋ถ€์กฑ"์ด ๋” ํฐ ๋ฌธ์ œ๋ผ๊ณ  ์ฃผ์žฅํ•จ.

ํ˜„์žฌ ํŒ€์˜ ํ•ต์‹ฌ ๊ณผ์ œ๋Š” ๋ฆฌํ…์…˜(์‚ฌ์šฉ์ž ์žฌ๋ฐฉ๋ฌธ) ๊ฐœ์„ ์ž…๋‹ˆ๋‹ค.

 

๋ฏธ์…˜:

  1. ๋ฆฌํ…์…˜ ๋ถ„์„์„ ํ†ตํ•ด 2์ฃผ์ฐจ ์žฌ๋ฐฉ๋ฌธ์œจ์ด ๋‚ฎ์€ ์›์ธ์„ ๊ทœ๋ช…ํ•˜๋Š” ์ฝ”๋“œ ์ž‘์„ฑ๊ณผ,
  2. ์ž‘์„ฑํ•œ SQL์„ ์ตœ์ ํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ์ž‘์„ฑํ•˜์—ฌ ๋ถ„์„ ์†๋„๋ฅผ ๋†’์ด๋ฉฐ,
  3. ์ตœ์ข…์ ์œผ๋กœ ์‹คํ–‰ ๊ณ„ํš์„ ์˜ˆ์ธก → ์˜ตํ‹ฐ๋งˆ์ด์ € ๊ด€์ ์—์„œ ์„ฑ๋Šฅ์„ ์ ๊ฒ€ํ•ด์•ผ ํ•จ

 


โžก๏ธ Step 1. ๋ฆฌํ…์…˜ ๋ถ„์„ ๊ธฐ๋ณธ ํ”„๋ ˆ์ž„ ์„ค๊ณ„

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด ๋‚˜์•„๊ฐ€ ๋ณด์ž.

 

โ“ ๋ฆฌํ…์…˜์„ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์ •์˜ํ•  ๊ฒƒ์ธ๊ฐ€? (์ฝ”ํ˜ธํŠธ vs ๋น„์ฝ”ํ˜ธํŠธ)

๋ฆฌํ…์…˜์€ ๊ธฐ๋ณธ์ ์œผ๋กœ “ํŠน์ • ์‹œ์  ์ดํ›„ ๋‹ค์‹œ ๋Œ์•„์˜จ ์‚ฌ์šฉ์ž ๋น„์œจ”์ด๊ณ ,

์ด๋ฅผ ์–ด๋–ค ๋‹จ์œ„๋กœ ์ธก์ •ํ• ์ง€์— ๋”ฐ๋ผ ๋ฐฉ์‹์ด ๋‹ฌ๋ผ์ง„๋‹ค.

 

๊ทธ๋ ‡๋‹ค๋ฉด ๋˜์ ธ์•ผ ํ•˜๋Š” ์งˆ๋ฌธ์€?

 

๋ˆ„๊ตฌ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ธก์ •ํ•  ๊ฒƒ์ธ๊ฐ€?

A: ๊ฐ€์ž…์ผ(๋˜๋Š” ์ฒซ ๋ฐฉ๋ฌธ์ผ)์„ ๊ธฐ์ค€์œผ๋กœ ์‚ผ๋Š”๋‹ค → “์‹ ๊ทœ ์‚ฌ์šฉ์ž ๋ฆฌํ…์…˜”

B: ํŠน์ • ์ด๋ฒคํŠธ(์˜ˆ: ๊ตฌ๋งค, ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์ถ”๊ฐ€)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์‚ผ๋Š”๋‹ค → “์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋ฆฌํ…์…˜”

C: ์ „์ฒด ํ™œ์„ฑ ์‚ฌ์šฉ์ž ๋Œ€์ƒ → “์ „์ฒด ์œ ์ € ์ž”์กด์œจ”

ํ˜„์žฌ ๋น„์ง€๋‹ˆ์Šค ๋ชฉํ‘œ๋Š” ์‹ ๊ทœ ๊ฐ€์ž…์ž์˜ 2์ฃผ์ฐจ ์žฌ๋ฐฉ๋ฌธ์œจ์ด๋‹ˆ, A๊ฐ€ ์ ํ•ฉํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค.

 

๋ฆฌํ…์…˜์„ ๋ฉฐ์น  ๋‹จ์œ„๋กœ ๋ณผ๊นŒ?

์„ ํƒ์ง€ A: ์ผ ๋‹จ์œ„ → D1(๋‹ค์Œ๋‚ ), D7(7์ผ์ฐจ), D14(14์ผ์ฐจ), …

์„ ํƒ์ง€ B: ์ฃผ ๋‹จ์œ„ → W1(1์ฃผ์ฐจ), W2(2์ฃผ์ฐจ), …

์„ ํƒ์ง€ C: ์›” ๋‹จ์œ„ → M1(1๊ฐœ์›”์ฐจ), M2(2๊ฐœ์›”์ฐจ), …

→ ์ดˆ๋ฐ˜์—๋Š” ์ผ ๋‹จ์œ„๋กœ ์„ธ๋ฐ€ํžˆ ๋ณธ ๋‹ค์Œ ๋‚˜์ค‘์— ๋ฌถ๋Š” ์ „๋žต์„ ์„ ํƒํ•ด๋ณด์ž.

 

๋ˆ„๊ฐ€ ์ž”์กด(๋ฆฌํ…์…˜)ํ–ˆ๋‹ค๊ณ  ๋ณผ๊นŒ?

์„ ํƒ์ง€ A: ์•ฑ์— ํ•œ ๋ฒˆ์ด๋ผ๋„ ์ ‘์†ํ•˜๋ฉด ์ž”์กด์œผ๋กœ ๋ณธ๋‹ค (๋‹จ์ˆœ ๋ฐฉ๋ฌธ ๋ฆฌํ…์…˜)

์„ ํƒ์ง€ B: ํŠน์ • ํ–‰๋™(๊ตฌ๋งค, ์žฅ๋ฐ”๊ตฌ๋‚˜ ์ถ”๊ฐ€ ๋“ฑ)์„ ํ•˜๋ฉด ์ž”์กด์œผ๋กœ ๋ณธ๋‹ค (ํ–‰๋™ ๊ธฐ๋ฐ˜ ๋ฆฌํ…์…˜)

๋‹จ์ˆœ ๋ฐฉ๋ฌธ์œผ๋กœ ๋ฆฌํ…์…˜์„ ์ง‘๊ณ„ํ•˜๊ฒŒ ๋˜๋ฉด ํ™œ์„ฑํ™” ์‚ฌ์šฉ์ž ์ˆ˜๋ฅผ ๊ณผ๋„ํ•˜๊ฒŒ ์ธก์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ,

์‹๋ณ„์ด ๋ถˆ๊ฐ€ํ•œ ์‚ฌ์šฉ์ž๋“ค๋กœ ์ธํ•ด ๋ฆฌํ…์…˜์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.

๋”ฐ๋ผ์„œ, ํŠน์ • ํ–‰๋™์„ ํ•œ ์œ ์ €์— ๋Œ€ํ•ด ์ž”์กดํ–ˆ๋‹ค๊ณ  ๋ณธ๋‹ค.

 

๋ฆฌํ…์…˜์„ ์ฝ”ํ˜ธํŠธ๋ณ„๋กœ ๋ณผ๊นŒ์š”, ์•„๋‹ˆ๋ฉด ์ „์ฒด ํ‰๊ท ์œผ๋กœ ๋ณผ๊นŒ?

์„ ํƒ์ง€ A: ์ฝ”ํ˜ธํŠธ ๋ฆฌํ…์…˜ → ๊ฐ€์ž… ์ฃผ์ฐจ๋ณ„๋กœ ๋ฌถ์–ด์„œ ๋ณธ๋‹ค. (์˜ˆ: 7์›” ์ฒซ์งธ์ฃผ ๊ฐ€์ž…์ž vs ๋‘˜์งธ ์ฃผ ๊ฐ€์ž…์ž ๋น„๊ต)

์„ ํƒ์ง€ B: ๋น„์ฝ”ํ˜ธํŠธ ๋ฆฌํ…์…˜ → ์ „์ฒด ํ‰๊ท ์ ์œผ๋กœ D7=20%, D14=10%์ฒ˜๋Ÿผ ํ•œ ๋ˆˆ์— ๋ณธ๋‹ค

→ PM๊ณผ ๋งˆ์ผ€ํŒ…ํŒ€์ด “์™œ ๋‚ฎ์€์ง€ ์›์ธ ๊ทœ๋ช…”์„ ์›ํ•˜๋ฏ€๋กœ, ์ฝ”ํ˜ธํŠธ ๋ฆฌํ…์…˜์ด ์œ ๋ฆฌํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ

 

 

โžก๏ธ Step 2. ํ…Œ์ด๋ธ” ๊ตฌ์กฐ ์„ค๊ณ„

๋ฆฌํ…์…˜ ๋ถ„์„์— ํ•„์š”ํ•œ ์ตœ์†Œ ํ…Œ์ด๋ธ” ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์„ ๊ฒƒ์ด๋‹ค.

๐Ÿ“ ์œ ์ € ํ…Œ์ด๋ธ” (users)

user_id  signup_date
101 2025-07-01
102 2025-07-03
103 2025-07-05

 

๐Ÿ“ ์œ ์ € ํ–‰๋™ ํ…Œ์ด๋ธ” (user_events)

→ ๋ฆฌํ…์…˜ ์ •์˜๊ฐ€ “ํŠน์ • ํ–‰๋™ ๊ธฐ๋ฐ˜”์ด๋ฏ€๋กœ ๋‹ค์Œ ๊ตฌ์กฐ๊ฐ€ ํ•„์š”ํ•  ๊ฒƒ์ด๋‹ค.

user_id event_date event_type
101 2025-07-02 view_product
101 2025-07-05 purchase
102 2025-07-10 add_to_cart
103 2025-07-14 purchase

 

 

โžก๏ธ Step 3. ์ฝ”ํ˜ธํŠธ ๋ฆฌํ…์…˜ SQL ์ดˆ์•ˆ 

์œ„ ํ…Œ์ด๋ธ” ๊ตฌ์กฐ์— ๋งž์ถฐ SQL์„ ์ž‘์„ฑํ•ด๋ณด์ž.

-- 1) ๊ธฐ๋ณธ ์ฝ”ํ˜ธํŠธ ๋ฆฌํ…์…˜ ๊ณ„์‚ฐ
SELECT
    WEEK(u.signup_date) AS cohort_week, -- ๊ฐ€์ž… ์ฃผ์ฐจ
    DATEDIFF(e.event_date, u.signup_date) AS days_since_signup, -- ๊ฐ€์ž… ํ›„ n์ผ์ฐจ
    COUNT(DISTINCT u.user_id) AS retained_users
FROM users u
JOIN user_evnts e
    ON u.user_id = e.user_id
    AND e.event_type IN ('purchase', 'add_to_chart') -- ํ–‰๋™ ๊ธฐ๋ฐ˜ ๋ฆฌํ…์…˜
    AND DATEDIFF(e.event_type, u.signup_date) BETWEEN 1 AND 14 -- 14์ผ์ฐจ๊นŒ์ง€
GROUP BY cohort_week, days_since_signup
ORDER BY cohort_week, days_since_signup;

 

โœ… SQL์ด ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฆฌํ…์…˜ ํ‘œ ์˜ˆ์‹œ

| cohort_week  | days_since_signup   | retained_users  |
| ------------ | ------------------- | --------------- |
| 2025-06-30   | 1                   | 50              |
| 2025-06-30   | 7                   | 20              |
| 2025-06-30   | 14                  | 10              |
| 2025-07-07   | 1                   | 80              |
| 2025-07-07   | 7                   | 30              |

 

 


๐Ÿค” SQL ํŠœ๋‹ ๊ด€์ ์—์„œ ์ƒ๊ฐํ•ด ๋ณผ ํฌ์ธํŠธ๋Š” ์–ด๋–ค ๊ฒƒ๋“ค์ด ์žˆ์„๊นŒ?

๋‹จ๊ณ„๋ณ„๋กœ ์งˆ๋ฌธ ํ•ด๋ณด์ž.

 

1. ์œ„ SQL์„ ์‹คํ–‰ํ•˜๋ฉด ์˜ตํ‹ฐ๋งˆ์ด์ €๋Š” ์–ด๋–ค ์‹คํ–‰ ๊ณ„ํš์„ ์„ ํƒํ• ๊นŒ?

users์™€ user_events๋Š” u.user_id = e.user_id๋ผ๋Š” ๋™๋“ฑ ์กฐ์ธ(=)์œผ๋กœ ์—ฐ๊ฒฐ๋˜๋ฉฐ,

์˜ตํ‹ฐ๋งˆ์ด์ €๋Š” ์„ ํƒ๋„(Selectivity)๊ฐ€ ๋” ์ข‹์€ ์ชฝ(์ฆ‰, ํ•„ํ„ฐ๋ง์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๋” ์ค„์–ด๋“œ๋Š” ์ชฝ)์„ ๋จผ์ € ์—‘์„ธ์Šค ํ•˜๋ ค ํ•  ๊ฒƒ์ด๋‹ค.

 

์ถ”๊ฐ€๋กœ ํ™•์žฅํ•œ๋‹ค๋ฉด

์˜ตํ‹ฐ๋งˆ์ด์ €๋Š” ์กฐ์ธ ์ˆœ์„œ๋ฅผ ์„ ํƒํ•  ๋•Œ users์˜ row ์ˆ˜(๊ฐ€์ž…์ž ์ˆ˜)์™€ user_events์˜ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์ˆ˜๋ฅผ ๋น„๊ตํ•ด

๋น„์šฉ์ด ๋” ๋‚ฎ์€ ์ชฝ์„ ๋“œ๋ผ์ด๋น™ ํ…Œ์ด๋ธ”(๋จผ์ € ์ฝ๋Š” ํ…Œ์ด๋ธ”)๋กœ ์„ ํƒํ•œ๋‹ค.

 

์ง€๊ธˆ ์ฟผ๋ฆฌ์—์„œ๋Š” user_events๊ฐ€ ์ˆ˜์ฒœ๋งŒ ๊ฑด์ด๋ผ๋ฉด,

์˜ตํ‹ฐ๋งˆ์ด์ €๋Š” users → user_events ์ˆœ์œผ๋กœ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์œผ๋กœ ์œ ๋ฆฌํ•˜๋‹ค.

 

์ด๋Š” u.signup_date๊ฐ€ JOIN ์ด์ „์— ๋จผ์ € ์ฝ”ํ˜ธํŠธ ๊ธฐ์ค€์œผ๋กœ ํ•„ํ„ฐ๋ง๋˜๋ฉด,

์ดํ›„ user_events๋ฅผ ์กฐ์ธํ•  ๋•Œ user_id ํ›„๋ณด ์ˆ˜๊ฐ€ ํ›จ์”ฌ ์ค„์–ด๋“ ๋‹ค.

1. users์—์„œ cohort_week, signup_date ํ•„ํ„ฐ -> ํ›„๋ณด user_id ์ถ”์ถœ
2. ํ›„๋ณด user_id๋งŒ user_events์—์„œ ํƒ์ƒ‰ -> IN (user_id_list)

 

 

2. e.event_type๊ณผ DATEDIFF ์กฐ๊ฑด์€ ์ธ๋ฑ์Šค ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•œ๊ฐ€?

event_type ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•˜๋ฉด MySQL ์˜ตํ‹ฐ๋งˆ์ด์ €๋Š”

IN ์ ˆ์— ๋ช…์‹œ๋œ ๊ฐ’๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

ALTER TABLE user_events ADD INDEX idx_event_type (event_type);

 

๊ทธ๋Ÿฌ๋‚˜, DATEDIFF()์™€ ๊ฐ™์ด ์ปฌ๋Ÿผ์— ํ•จ์ˆ˜๋ฅผ ์ ์šฉํ•œ ๊ฒฐ๊ณผ๊ฐ’์— ๋Œ€ํ•ด์„œ๋Š” ์ผ๋ฐ˜์ ์ธ ์ธ๋ฑ์Šค๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

์ธ๋ฑ์Šค๋Š” ์ปฌ๋Ÿผ์˜ ์›๋ž˜ ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ •๋ ฌ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—,

์ฟผ๋ฆฌ ์‹คํ–‰ ์‹œ๋งˆ๋‹ค ํ•จ์ˆ˜๊ฐ€ ๊ณ„์‚ฐ๋˜๋ฏ€๋กœ, ์ธ๋ฑ์Šค๋œ ๊ฐ’์„ ํ™œ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

 

๊ทธ๋Ÿผ ์–ด๋–กํ•˜์ฃ ?

event_date ์ž์ฒด๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฒ”์œ„ ์กฐ๊ฑด์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๊ฐ„์ ‘์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

e.event_date BETWEEN DATE_ADD(u.signup_date, INTERVAL 1 DAY)
AND DATE_ADD(u.signup_date, INTERVAL 14 DAY)

 

 

3. ๋งŒ์•ฝ user_events๊ฐ€ ์ˆ˜์ฒœ๋งŒ ๊ฑด์ด๋ผ๋ฉด, ์ด SQL์˜ ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์€ ์–ด๋””์—์„œ ์ƒ๊ธธ๊นŒ?

์˜ตํ‹ฐ๋งˆ์ด์ €๋Š” 2๊ฐ€์ง€์˜ ๋ณ‘๋ชฉ์„ ๊ฒช๋Š”๋‹ค.

 

1. user_events์˜ Full-Scan ์œ„ํ—˜

JOIN ์ˆœ์„œ๊ฐ€ ์ž˜๋ชป ์žกํžˆ๊ฑฐ๋‚˜ DATEDIFF๋กœ ์ธํ•ด ์ธ๋ฑ์Šค๋ฅผ ํ™œ์šฉํ•˜์ง€ ๋ชปํ•˜๋ฉด,

์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ user_events๋ฅผ Full-scan ํ•ด์•ผํ•œ๋‹ค.

 

2. GROUP BY ๋น„์šฉ

-- JOIN ์ดํ›„
GROUP BY cohort_week, days_since_signup

 

GROUP BY๋Š” JOIN ์ดํ›„ ์ˆ˜ํ–‰๋˜๋ฏ€๋กœ, JOIN ๊ฒฐ๊ณผ๊ฐ€ ์ปค์ง€๋ฉด GROUPING ๋น„์šฉ์ด ๊ธ‰์ฆํ•œ๋‹ค.

 

 

๐Ÿ’ก ๋ณ‘๋ชฉ ์™„ํ™” ์ „๋žต

1. ์‚ฌ์ „ ํ•„ํ„ฐ๋ง

users์—์„œ 2์ฃผ ์ด๋‚ด ๊ฐ€์ž…์ž๋งŒ ๋จผ์ € ํ•„ํ„ฐ๋งํ•ด ๋“œ๋ผ์ด๋น™ ํ…Œ์ด๋ธ”์˜ ํฌ๊ธฐ๋ฅผ ์ค„์ธ๋‹ค.

-- 1, ์‚ฌ์ „ ํ•„ํ„ฐ๋ง์„ ํ†ตํ•œ ํŠœ๋‹
-- ์กฐ์ธ ์ „์— ๊ฐ ํ…Œ์ด๋ธ”์„ ๋จผ์ € ํ•„ํ„ฐ๋งํ•˜์—ฌ ์ฒ˜๋ฆฌํ•  ๋ฐ์ดํ„ฐ๋Ÿ‰ ๊ฐ์†Œ
SELECT
    WEEK(filtered_users.signup_date) AS cohort_week,
    DATEDIFF(filtered_events.event_date, filtered_users.signup_date) AS days_since_signup,
    COUNT(DISTINCT filtered_users.user_id) AS retained_users
FROM (
    SELECT user_id, signup_date
    FROM users
    WHERE signup_date >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
) filtered_users
JOIN (
    -- ์ด๋ฒคํŠธ ํ…Œ์ด๋ธ” ์‚ฌ์ „ ํ•„ํ„ฐ๋ง (๊ด€๋ จ ์ด๋ฒคํŠธ๋งŒ)
    SELECT user_id, event_date
    FROM user_events
    WHERE event_type IN ('purchase', 'add_to_cart')
        AND event_date >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
) filtered_events ON filtered_users.user_id = filtered_events.user_id
WHERE DATEDIFF(filtered_events.event_date, filtered_users.signup_date) BETWEEN 1 AND 14
GROUP BY cohort_week, days_since_signup
ORDER BY cohort_week, days_since_signup

 

2. ์ธ๋ฑ์Šค ์žฌ์„ค๊ณ„

user_events์— (event_type, event_date, user_id) ๋ณตํ•ฉ ์ธ๋ฑ์Šค๋ฅผ ๊ณ ๋ คํ•œ๋‹ค.

-- 2. ์ธ๋ฑ์Šค ์žฌ์„ค๊ณ„๋ฅผ ํ†ตํ•œ ํŠœ๋‹
-- user ํ…Œ์ด๋ธ” ์ธ๋ฑ์Šค
CREATE INDEX idx_users_signup_date_id ON users(signup_date, user_id);

-- user_events ํ…Œ์ด๋ธ” ์ธ๋ฑ์Šค
CREATE INDEX idx_events_type_date_user ON user_events(event_type, event_date, user_id);
CREATE INDEX idx_events_user_type_date ON user_events(user_id, event_type, event_date);

-- ์ธ๋ฑ์Šค ํ™œ์šฉ ์ตœ์ ํ™”๋œ ์ฟผ๋ฆฌ
SELECT
    WEEK(u.signup_date) AS cohort_week,
    DATEDIFF(e.event_date, u.signup_date) AS days_since_signup,
    COUNT(DISTINCT u.user_id) AS retained_users
FROM users u
INNER JOIN user_events e ON u.user_id = e.user_id
WHERE u.signup_date >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH) -- ์ธ๋ฑ์Šค ํ™œ์šฉ
    AND e.event_type IN('purchase', 'add_to_cart') -- ์ธ๋ฑ์Šค ํ™œ์šฉ
    AND e.event_date >= u.signup_date -- ๋ถˆํ•„์š”ํ•œ ๊ณผ๊ฑฐ ์ด๋ฒคํŠธ ์ œ์™ธ
    AND e.event_date <= DATE_ADD(u.signup_date, INTERVAL 14 DAY) -- ๋ฒ”์œ„ ์ตœ์ ํ™”
GROUP BY cohort_week, days_since_signup
ORDER BY cohort_week, days_since_signup;

 

3. Subquery๋กœ ์ด๋ฒคํŠธ ํ•„ํ„ฐ๋ง ํ›„ ์กฐ์ธ

์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ๊ฐ•์ œ๋กœ ํ•„ํ„ฐ ์ˆœ์„œ๋ฅผ ๋ฐ”๊พธ๊ฒŒ๋” ์œ ๋„ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.

-- 3. ์„œ๋ธŒ์ฟผ๋ฆฌ๋กœ ์ด๋ฒคํŠธ ํ•„ํ„ฐ๋ง ํ›„ ์กฐ์ธํ•˜๋Š” ํŠœ๋‹
-- ๊ด€๋ จ ์ด๋ฒคํŠธ๊ฐ€ ์žˆ๋Š” ์‚ฌ์šฉ์ž๋งŒ ๋จผ์ € ์‹๋ณ„ํ•œ ํ›„ ์กฐ์ธ
WITH cohort_users AS (
    -- Step 1: ์ฝ”ํ˜ธํŠธ๋ณ„ ์‚ฌ์šฉ์ž ๊ธฐ๋ณธ ์ •๋ณด
    SELECT
        user_id,
        signup_date,
        WEEK(signup_date) AS cohort_week
    FROM users
    WHERE signup_date >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)
),
relevant_events AS (
    -- Step 2: ๋ฆฌํ…์…˜ ๊ด€๋ จ ์ด๋ฒคํŠธ๋งŒ ์ถ”์ถœ
    SELECT
        e.user_id,
        e.event_date
    FROM user_events e
    INNER JOIN cohort_users cu ON e.user_id = cu.user_id
    WHERE e.event_type IN ('purchase', 'add_to_cart')
        AND e.event_date > cu.signup_date -- ๊ฐ€์ž…์ผ ์ดํ›„๋งŒ
        AND e.event_date <= DATE_ADD(cu.signup_date, INTERVAL 14 DAY) -- 14์ผ ์ด๋‚ด๋งŒ
),
retention_data AS (
    -- Step 3: ๋ฆฌํ…์…˜ ๋ฐ์ดํ„ฐ ๊ณ„์‚ฐ
    SELECT
        cu.cohort_week,
        cu.user_id,
        DATEDIFF(re.event_date, cu.signup_date) AS days_since_signup
    FROM cohort_users cu
    INNER JOIN relevant_events re ON cu.user_id = re.user_id
)
-- Step 4: ์ตœ์ข… ์ง‘๊ณ„
SELECT cohort_week,
       days_since_signup,
       COUNT(DISTINCT user_id) AS retained_users
FROM retention_data
GROUP BY cohort_week, days_since_signup
ORDER BY cohort_week, days_since_signup;

 

 

์ด๋ ‡๊ฒŒ ๋ณ‘๋ชฉ ์™„ํ™” ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค๋ฉด,

๊ฐœ์„ ๋œ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•˜๋Š” ์ฟผ๋ฆฌ๋„ ์ž‘์„ฑํ•ด ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

-- ์„ฑ๋Šฅ ์ธก์ • ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง ์ฟผ๋ฆฌ
-- ์‹คํ–‰ ๊ณ„ํš ํ™•์ธ
EXPLAIN EXTENDED [์œ„์˜ ํŠœ๋‹๋œ ์ฟผ๋ฆฌ ์ค‘ ํ•˜๋‚˜];

-- ์ธ๋ฑ์Šค ์‚ฌ์šฉ๋ฅ  ํ™•์ธ
SHOW INDEX FROM users;
SHOW INDEX FROM user_events;

-- ํ…Œ์ด๋ธ” ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
ANALYZE TABLE users, user_events;

 

์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ์‹คํ–‰๊ณ„ํš์„ ์„ธ์šธ ๋•Œ ์˜ตํ‹ฐ๋งˆ์ด์ €์—๊ฒŒ ์ค‘์š”ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š”

์นดํƒˆ๋กœ๊ทธ ๋งค๋‹ˆ์ € (ํ…Œ์ด๋ธ” ํ†ต๊ณ„)๋ฅผ ๋ฐ˜๋“œ์‹œ ์—…๋ฐ์ดํŠธ ํ•ด์•ผ ํ•œ๋‹ค.

 

๋งŒ์•ฝ ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ์ž˜๋ชป๋œ ์‹คํ–‰ ๊ณ„ํš์„ ์„ ํƒํ–ˆ์„ ๋•Œ ์ตœํ›„์˜ ์ˆ˜๋‹จ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” 'ํžŒํŠธ ๊ตฌ'๋ผ๋Š” ๊ฒƒ๋„ ์กด์žฌํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ user_events๋ฅผ ๋จผ์ € ๋“œ๋ผ์ด๋น™ ํ…Œ์ด๋ธ”๋กœ ์„ ํƒํ•ด ํ’€์Šค์บ”ํ•œ๋‹ค๋ฉด,

ํžŒํŠธ ๊ตฌ๋กœ users๋ฅผ ๋จผ์ € ์ฝ๊ฒŒ ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ๋‹ค.

-- LEADING(u e) -> u(users)๋ฅผ ๋จผ์ € ์ฝ๊ณ  e(user_events)๋ฅผ ์กฐ์ธํ•˜๋ผ๋Š” ์˜๋ฏธ
SELECT /*+ LEADING(u e) */ 
       DATE_TRUNC('week', u.signup_date) AS cohort_week, ...
FROM users u
JOIN user_events e
  ON u.user_id = e.user_id
...