4. SQL - window

Jasonยท2026๋…„ 1์›” 12์ผ

SQL

๋ชฉ๋ก ๋ณด๊ธฐ
4/47

ROW_NUMBER, LAG



SQL ์œˆ๋„์šฐ ํ•จ์ˆ˜ โ€” ROW_NUMBER, RANK, LAG์˜ ๋งˆ๋ฒ•

๐Ÿ’ก ์ฝ”๋”ฉํ…Œ์ŠคํŠธ ๋‹จ๊ณจ! ์œˆ๋„์šฐ ํ•จ์ˆ˜๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์ •๋ฆฌํ•œ๋‹ค.


๐Ÿค” ์œˆ๋„์šฐ ํ•จ์ˆ˜๊ฐ€ ๋ญ์•ผ?

GROUP BY ์—†์ด ์ง‘๊ณ„ํ•˜๋ฉด์„œ ๊ฐœ๋ณ„ ํ–‰๋„ ์œ ์ง€ํ•˜๋Š” ํ•จ์ˆ˜.

GROUP BY vs ์œˆ๋„์šฐ ํ•จ์ˆ˜

-- GROUP BY: ํ–‰์ด ํ•ฉ์ณ์ง
SELECT user_id, SUM(amount)
FROM orders
GROUP BY user_id;
-- ๊ฒฐ๊ณผ: 3ํ–‰ (์œ ์ € ์ˆ˜๋งŒํผ)

-- ์œˆ๋„์šฐ ํ•จ์ˆ˜: ํ–‰์ด ์œ ์ง€๋จ
SELECT 
    user_id, 
    amount,
    SUM(amount) OVER(PARTITION BY user_id) AS user_total
FROM orders;
-- ๊ฒฐ๊ณผ: 6ํ–‰ (์›๋ณธ ๊ทธ๋Œ€๋กœ)

โ†’ ์›๋ณธ ๋ฐ์ดํ„ฐ + ์ง‘๊ณ„ ๊ฒฐ๊ณผ๋ฅผ ๋™์‹œ์—!


๐Ÿ“Š ์˜ˆ์ œ ํ…Œ์ด๋ธ”

[orders]
| order_id | user_id | amount | order_date |
|----------|---------|--------|------------|
| 1 | 101 | 150000 | 2024-01-10 |
| 2 | 101 | 50000 | 2024-01-20 |
| 3 | 101 | 100000 | 2024-02-05 |
| 4 | 102 | 200000 | 2024-01-15 |
| 5 | 102 | 80000 | 2024-02-10 |
| 6 | 103 | 300000 | 2024-02-15 |


๐Ÿ”ง ์œˆ๋„์šฐ ํ•จ์ˆ˜ ๊ธฐ๋ณธ ๋ฌธ๋ฒ•

ํ•จ์ˆ˜๋ช…() OVER (
    PARTITION BY ๊ทธ๋ฃน_์ปฌ๋Ÿผ    -- ๊ทธ๋ฃน ๋‚˜๋ˆ„๊ธฐ (์„ ํƒ)
    ORDER BY ์ •๋ ฌ_์ปฌ๋Ÿผ        -- ์ •๋ ฌ ๊ธฐ์ค€ (์„ ํƒ)
)

๐Ÿ“ˆ ์œˆ๋„์šฐ ํ•จ์ˆ˜ ์ข…๋ฅ˜

1. ์ˆœ์œ„ ํ•จ์ˆ˜

ํ•จ์ˆ˜์„ค๋ช…
ROW_NUMBER()์ˆœ์ฐจ ๋ฒˆํ˜ธ (1,2,3,4,5)
RANK()๋™์  ์‹œ ๊ฐ™์€ ์ˆœ์œ„, ๋‹ค์Œ ์ˆœ์œ„ ๊ฑด๋„ˆ๋œ€ (1,2,2,4,5)
DENSE_RANK()๋™์  ์‹œ ๊ฐ™์€ ์ˆœ์œ„, ๋‹ค์Œ ์ˆœ์œ„ ์—ฐ์† (1,2,2,3,4)

2. ์ง‘๊ณ„ ํ•จ์ˆ˜

ํ•จ์ˆ˜์„ค๋ช…
SUM()ํ•ฉ๊ณ„
AVG()ํ‰๊ท 
COUNT()๊ฐœ์ˆ˜
MAX() / MIN()์ตœ๋Œ€/์ตœ์†Œ

3. ๊ฐ’ ์ฐธ์กฐ ํ•จ์ˆ˜

ํ•จ์ˆ˜์„ค๋ช…
LAG()์ด์ „ ํ–‰ ๊ฐ’
LEAD()๋‹ค์Œ ํ–‰ ๊ฐ’
FIRST_VALUE()์ฒซ ๋ฒˆ์งธ ๊ฐ’
LAST_VALUE()๋งˆ์ง€๋ง‰ ๊ฐ’

1๏ธโƒฃ ROW_NUMBER โ€” ์ˆœ์ฐจ ๋ฒˆํ˜ธ

๊ธฐ๋ณธ ์‚ฌ์šฉ

SELECT 
    *,
    ROW_NUMBER() OVER(ORDER BY amount DESC) AS rn
FROM orders;
order_iduser_idamountrn
61033000001
41022000002
11011500003
31011000004
5102800005
2101500006

PARTITION BY์™€ ํ•จ๊ป˜ โ€” ๊ทธ๋ฃน๋ณ„ ๋ฒˆํ˜ธ

SELECT 
    *,
    ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY amount DESC) AS rn
FROM orders;
order_iduser_idamountrn
11011500001
31011000002
2101500003
41022000001
5102800002
61033000001

โ†’ ์œ ์ €๋ณ„๋กœ ๊ธˆ์•ก ์ˆœ์œ„!

๐Ÿ’ก ์‹ค์ „ ํ™œ์šฉ: ๊ทธ๋ฃน๋ณ„ Top 1

-- ๊ฐ ์œ ์ €์˜ ๊ฐ€์žฅ ํฐ ์ฃผ๋ฌธ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ
WITH ranked AS (
    SELECT 
        *,
        ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY amount DESC) AS rn
    FROM orders
)
SELECT * FROM ranked WHERE rn = 1;
order_iduser_idamountrn
11011500001
41022000001
61033000001

์ฝ”ํ…Œ ๋‹จ๊ณจ ํŒจํ„ด! ๐Ÿ”ฅ


2๏ธโƒฃ RANK vs DENSE_RANK

๋น„๊ต

SELECT 
    amount,
    ROW_NUMBER() OVER(ORDER BY amount DESC) AS row_num,
    RANK() OVER(ORDER BY amount DESC) AS rank,
    DENSE_RANK() OVER(ORDER BY amount DESC) AS dense_rank
FROM (
    SELECT 300000 AS amount UNION ALL
    SELECT 200000 UNION ALL
    SELECT 200000 UNION ALL
    SELECT 100000 UNION ALL
    SELECT 50000
) t;
amountrow_numrankdense_rank
300000111
200000222
200000322
100000443
50000554

์ฐจ์ด์ :

  • ROW_NUMBER: ๋ฌด์กฐ๊ฑด 1,2,3,4,5
  • RANK: ๋™์  ์‹œ ๊ฐ™์€ ์ˆœ์œ„, ๋‹ค์Œ ๊ฑด๋„ˆ๋œ€ (2๋“ฑ 2๋ช… โ†’ 4๋“ฑ)
  • DENSE_RANK: ๋™์  ์‹œ ๊ฐ™์€ ์ˆœ์œ„, ๋‹ค์Œ ์—ฐ์† (2๋“ฑ 2๋ช… โ†’ 3๋“ฑ)

3๏ธโƒฃ SUM OVER โ€” ๋ˆ„์ ํ•ฉ

์ „์ฒด ํ•ฉ๊ณ„ ์ถ”๊ฐ€

SELECT 
    *,
    SUM(amount) OVER() AS total
FROM orders;
order_idamounttotal
1150000880000
250000880000
......880000

์œ ์ €๋ณ„ ํ•ฉ๊ณ„

SELECT 
    *,
    SUM(amount) OVER(PARTITION BY user_id) AS user_total
FROM orders;
order_iduser_idamountuser_total
1101150000300000
210150000300000
3101100000300000
4102200000280000
............

๋ˆ„์ ํ•ฉ (Running Total)

SELECT 
    *,
    SUM(amount) OVER(ORDER BY order_date) AS running_total
FROM orders;
order_idorder_dateamountrunning_total
12024-01-10150000150000
42024-01-15200000350000
22024-01-2050000400000
32024-02-05100000500000
52024-02-1080000580000
62024-02-15300000880000

โ†’ ๋‚ ์งœ ์ˆœ์œผ๋กœ ๋ˆ„์ !


4๏ธโƒฃ LAG / LEAD โ€” ์ด์ „/๋‹ค์Œ ํ–‰

LAG: ์ด์ „ ํ–‰ ๊ฐ’

SELECT 
    order_id,
    order_date,
    amount,
    LAG(amount) OVER(ORDER BY order_date) AS prev_amount
FROM orders;
order_idorder_dateamountprev_amount
12024-01-10150000NULL
42024-01-15200000150000
22024-01-2050000200000
32024-02-0510000050000
............

LEAD: ๋‹ค์Œ ํ–‰ ๊ฐ’

SELECT 
    order_id,
    amount,
    LEAD(amount) OVER(ORDER BY order_date) AS next_amount
FROM orders;

LAG ํ™œ์šฉ: ์ฆ๊ฐ ๊ณ„์‚ฐ

SELECT 
    order_date,
    amount,
    LAG(amount) OVER(ORDER BY order_date) AS prev_amount,
    amount - LAG(amount) OVER(ORDER BY order_date) AS diff
FROM orders;
order_dateamountprev_amountdiff
2024-01-10150000NULLNULL
2024-01-1520000015000050000
2024-01-2050000200000-150000

LAG ํŒŒ๋ผ๋ฏธํ„ฐ

LAG(์ปฌ๋Ÿผ, N, ๊ธฐ๋ณธ๊ฐ’)
-- N: ๋ช‡ ํ–‰ ์ „? (๊ธฐ๋ณธ 1)
-- ๊ธฐ๋ณธ๊ฐ’: NULL ๋Œ€์‹  ์‚ฌ์šฉํ•  ๊ฐ’
SELECT 
    amount,
    LAG(amount, 1, 0) OVER(ORDER BY order_date) AS prev_1,
    LAG(amount, 2, 0) OVER(ORDER BY order_date) AS prev_2
FROM orders;

5๏ธโƒฃ FIRST_VALUE / LAST_VALUE

FIRST_VALUE: ๊ทธ๋ฃน ์ฒซ ๋ฒˆ์งธ ๊ฐ’

SELECT 
    user_id,
    order_date,
    amount,
    FIRST_VALUE(amount) OVER(
        PARTITION BY user_id 
        ORDER BY order_date
    ) AS first_order_amount
FROM orders;
user_idorder_dateamountfirst_order_amount
1012024-01-10150000150000
1012024-01-2050000150000
1012024-02-05100000150000

6๏ธโƒฃ ํ”„๋ ˆ์ž„ ์ง€์ • (ROWS/RANGE)

์ด๋™ ํ‰๊ท 

SELECT 
    order_date,
    amount,
    AVG(amount) OVER(
        ORDER BY order_date
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_3
FROM orders;

โ†’ ํ˜„์žฌ ํ–‰ ํฌํ•จ ์ตœ๊ทผ 3๊ฐœ์˜ ํ‰๊ท 

ํ”„๋ ˆ์ž„ ์˜ต์…˜

ROWS BETWEEN ์‹œ์ž‘ AND ๋

-- ์‹œ์ž‘/๋ ์˜ต์…˜
UNBOUNDED PRECEDING  -- ์ฒ˜์Œ๋ถ€ํ„ฐ
N PRECEDING          -- Nํ–‰ ์ „
CURRENT ROW          -- ํ˜„์žฌ ํ–‰
N FOLLOWING          -- Nํ–‰ ํ›„
UNBOUNDED FOLLOWING  -- ๋๊นŒ์ง€

์˜ˆ์‹œ:

-- ํ˜„์žฌ ํ–‰ ๊ธฐ์ค€ ์•ž๋’ค 1ํ–‰์”ฉ ํฌํ•จ
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING

-- ์ฒ˜์Œ๋ถ€ํ„ฐ ํ˜„์žฌ๊นŒ์ง€ (๋ˆ„์ )
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

๐ŸŽฏ ์‹ค์ „ ๋ฌธ์ œ

๋ฌธ์ œ 1: ๊ทธ๋ฃน๋ณ„ Top 2

๊ฐ ์œ ์ €๋ณ„ ์ฃผ๋ฌธ ๊ธˆ์•ก ์ƒ์œ„ 2๊ฐœ๋ฅผ ๊ตฌํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
WITH ranked AS (
    SELECT 
        *,
        ROW_NUMBER() OVER(
            PARTITION BY user_id 
            ORDER BY amount DESC
        ) AS rn
    FROM orders
)
SELECT * FROM ranked WHERE rn <= 2;

๋ฌธ์ œ 2: ๋ˆ„์ ํ•ฉ

๋‚ ์งœ ์ˆœ์œผ๋กœ ๋ˆ„์  ๋งค์ถœ์„ ๊ตฌํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
SELECT 
    order_date,
    amount,
    SUM(amount) OVER(ORDER BY order_date) AS cumulative_sum
FROM orders;

๋ฌธ์ œ 3: ์ „์ผ ๋Œ€๋น„ ์ฆ๊ฐ

๊ฐ ์ฃผ๋ฌธ์˜ ์ „์ผ ์ฃผ๋ฌธ ๋Œ€๋น„ ๊ธˆ์•ก ์ฐจ์ด๋ฅผ ๊ตฌํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
SELECT 
    order_date,
    amount,
    LAG(amount) OVER(ORDER BY order_date) AS prev_amount,
    amount - LAG(amount) OVER(ORDER BY order_date) AS diff
FROM orders;

๋ฌธ์ œ 4: ๋น„์œจ ๊ณ„์‚ฐ

๊ฐ ์ฃผ๋ฌธ์ด ํ•ด๋‹น ์œ ์ € ์ด ์ฃผ๋ฌธ๊ธˆ์•ก์—์„œ ์ฐจ์ง€ํ•˜๋Š” ๋น„์œจ์„ ๊ตฌํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
SELECT 
    user_id,
    order_id,
    amount,
    SUM(amount) OVER(PARTITION BY user_id) AS user_total,
    ROUND(amount * 100.0 / SUM(amount) OVER(PARTITION BY user_id), 2) AS pct
FROM orders;

๋ฌธ์ œ 5: ์—ฐ์† ์ฆ๊ฐ€ ์ฐพ๊ธฐ

์ฃผ๋ฌธ ๊ธˆ์•ก์ด ์ด์ „๋ณด๋‹ค ์ฆ๊ฐ€ํ•œ ์ฃผ๋ฌธ์„ ์ฐพ์œผ์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
WITH with_prev AS (
    SELECT 
        *,
        LAG(amount) OVER(ORDER BY order_date) AS prev_amount
    FROM orders
)
SELECT *
FROM with_prev
WHERE amount > prev_amount;

๐Ÿ’ก ์œˆ๋„์šฐ ํ•จ์ˆ˜ ํŒ

1. ๋ณ„์นญ์€ ๋ชป ์”€

-- โŒ ์—๋Ÿฌ
SELECT 
    SUM(amount) OVER() AS total,
    amount / total AS ratio  -- total ์‚ฌ์šฉ ๋ถˆ๊ฐ€!
FROM orders;

-- โœ… CTE ์‚ฌ์šฉ
WITH totals AS (
    SELECT *, SUM(amount) OVER() AS total
    FROM orders
)
SELECT *, amount * 1.0 / total AS ratio
FROM totals;

2. WHERE์—์„œ ๋ชป ์”€

-- โŒ ์—๋Ÿฌ
SELECT *
FROM orders
WHERE ROW_NUMBER() OVER(ORDER BY amount) <= 3;

-- โœ… CTE ์‚ฌ์šฉ
WITH ranked AS (
    SELECT *, ROW_NUMBER() OVER(ORDER BY amount) AS rn
    FROM orders
)
SELECT * FROM ranked WHERE rn <= 3;

3. ์„ฑ๋Šฅ ์ฃผ์˜

  • PARTITION BY ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค ๊ณ ๋ ค
  • ๋„ˆ๋ฌด ๋งŽ์€ ์œˆ๋„์šฐ ํ•จ์ˆ˜๋Š” ์„ฑ๋Šฅ ์ €ํ•˜ ๊ฐ€๋Šฅ

๐Ÿ“ ํ•ต์‹ฌ ์ •๋ฆฌ

[์ˆœ์œ„]
ROW_NUMBER: ์ˆœ์ฐจ ๋ฒˆํ˜ธ
RANK: ๋™์  ์‹œ ๊ฐ™์€ ์ˆœ์œ„, ๊ฑด๋„ˆ๋œ€
DENSE_RANK: ๋™์  ์‹œ ๊ฐ™์€ ์ˆœ์œ„, ์—ฐ์†

[์ง‘๊ณ„]
SUM/AVG/COUNT OVER(): ๊ทธ๋ฃน ์œ ์ง€ํ•˜๋ฉฐ ์ง‘๊ณ„

[๊ฐ’ ์ฐธ์กฐ]
LAG: ์ด์ „ ํ–‰
LEAD: ๋‹ค์Œ ํ–‰
FIRST_VALUE: ์ฒซ ๋ฒˆ์งธ ๊ฐ’

[๋ฌธ๋ฒ•]
OVER(PARTITION BY ๊ทธ๋ฃน ORDER BY ์ •๋ ฌ)

๐Ÿ”— ๋‹ค์Œ ํฌ์ŠคํŠธ

[SQL ์‹œ๋ฆฌ์ฆˆ #5] ์‹ค์ „ ํŒจํ„ด โ€” ์ฝ”ํ…Œ ์ž์ฃผ ๋‚˜์˜ค๋Š” ์œ ํ˜• ์ด์ •๋ฆฌ


๐Ÿ“ ์œˆ๋„์šฐ ํ•จ์ˆ˜๋Š” ์ฝ”๋”ฉํ…Œ์ŠคํŠธ์˜ ํ•ต์‹ฌ์ด๋‹ค.
ROW_NUMBER + CTE ํŒจํ„ด๋งŒ ํ™•์‹คํžˆ ์•Œ์•„๋„ ๋งŽ์€ ๋ฌธ์ œ๋ฅผ ํ’€ ์ˆ˜ ์žˆ๋‹ค!

profile
Data Analyst | Thoughts Become Things. ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ํ•  ์ˆ˜ ์—†๋‹ค. | www.linkedin.com/in/๋ช…์ˆ˜-์ œ-7ab843200

0๊ฐœ์˜ ๋Œ“๊ธ€