μ€μκ° κ΅¬νκΈ°
μ€μκ°(median)μ λ°μ΄ν°λ₯Ό ν¬κΈ° μμλλ‘ λμ΄νμ λ, μ€μμ μμΉν κ°μ λ§ν©λλ€.
λ°μ΄ν°λ₯Ό λννλ κ° μ€ νλλ‘, νΉν κ·Ήλ¨μ μΈ μ΄μμΉμ μν₯μ λ λ°λ νΉμ§μ΄ μμ΄ νκ· λ³΄λ€ λ μμ μ μΈ μ§νλ‘ μ¬μ©λκΈ°λ ν©λλ€.
μ€μκ° κ΅¬νλ λ°©λ²:
- νμ κ°μ λ°μ΄ν°κ° μμ κ²½μ°:
→ κ°μ΄λ° μλ κ°μ΄ μ€μκ°μ λλ€.
μ: 3, 5, 7 → μ€μκ°μ 5 - μ§μ κ°μ λ°μ΄ν°κ° μμ κ²½μ°:
→ κ°μ΄λ° λ κ°μ νκ· μ΄ μ€μκ°μ λλ€.
μ: 2, 4, 6, 8 → μ€μκ°μ (4+6)/2 = 5
μ΄λ κ² λ°μ΄ν°μ κ°μκ° ν/μ§μΈμ§μ λ°λΌ μ€μκ°μ μμΉμ ꡬνλ λ°©λ²μ΄ λ¬λΌμ§κΈ° λλ¬Έμ
μ΄λ₯Ό λ°μνμ¬ SQL 쿼리λ₯Ό μ§μΌ ν©λλ€.
MySQLμ medianμ΄λΌλ ν¨μλ₯Ό μ§μνμ§ μκΈ° λλ¬Έμ μ½κ°μ μμ΄λμ΄κ° νμν©λλ€.
νμ© μμ
tipsλΌλ ν μ΄λΈμ νμ©νμ¬
κ° μμΌλ³ μ±λ³ μμ¬κΈμ‘μ μ€μκ°μ ꡬν΄λ³΄κ² μ΅λλ€.
| μμ± | μ€λͺ |
| day | μμΌ |
| sex | μ±λ³ |
| total_bill | μμ¬ κΈμ‘ |
λ°©λ² 1
첫λ²μ§Έ λ°©λ²μ, μ€μκ°μ μμ * 2 - μ 체 λ μ½λ μλ₯Ό ꡬν΄μ£Όλ κ²μ λλ€.
μλ μμ λ₯Ό ν΅ν΄ μμλ³΄κ² μ΅λλ€.


row_num : λ°μ΄ν°λ₯Ό μμλλ‘ λμ΄ν΄μ λ²νΈλ₯Ό λ§€κ²¨μ€ κ°μ λλ€.
tot_cnt : μ 체 λ μ½λ μμ λλ€.
βͺοΈλ μ½λκ° νμκ°
μ 체 λ μ½λ μκ° 9κ°μ΄λ©΄, μ€μκ°μ 5λ²μ§Έ μμμ μλ λ μ½λ κ°μΌ κ²μ λλ€.
λ°λΌμ μμ μ€μκ°μ μμ * 2 - μ 체 λ μ½λ μ 곡μμ νμ©νλ©΄
(5 * 2) - 9 = 1μ λλ€.
μ¦, μ€μκ°μ μμ * 2 - μ 체 λ μ½λ μκ° 1μΈ κ²½μ°λ₯Ό ꡬν΄μ£Όλ©΄ μ€μκ°μ ꡬν μ μμ΅λλ€.
βͺοΈλ μ½λκ° μ§μκ°
μ 체 λ μ½λ μκ° 10κ°μ΄λ©΄, μ€μκ°μ 5λ²μ§Έ, 6λ²μ§Έ λ μ½λμ νκ· μΌ κ²μ λλ€.
μμ 곡μμ μ μ©ν΄λ³΄λ©΄
(5 * 2) - 10 = 0 | (6 * 2) - 10 = 2μ λλ€.
μ¦, μ€μκ°μ μμ * 2 - μ 체 λ μ½λ μκ° 0κ³Ό 2μΈ λ μ½λμ νκ· μ ꡬν΄μ£Όλ©΄ μ€μκ°μ ꡬν μ μμ΅λλ€.
λ°©λ² 1 : SQL μ½λ
with rownum_table as
(select
day,
sex,
total_bill,
row_number() over (partition by day, sex order by total_bill asc) as row_num, -- day, sexλ³λ‘ λ μ½λ μμ 맀겨주기
count(*) over (partition by day, sex) as tot_cnt -- day, sexλ³λ‘ μ 체 λ μ½λ μ ꡬνκΈ°
from tips)
select
day,
sex,
avg(total_bill) as median_total_bill -- μ§μμΈ κ²½μ°λ₯Ό κ³ λ €ν΄μ κΈμ‘μ νκ· μ ꡬν΄μ€
from rownum_table
where row_num * 2 - tot_cnt between 0 and 2 -- νμ : 1, μ§μ : 0,2 μ λͺ¨λ λ§μ‘±
group by 1, 2
βͺοΈROW_NUMBER()
ROW_NUMBER() ν¨μλ μ λ ¬ μμμ λ°λΌ 1λΆν° μμν΄ μμ°¨μ μΌλ‘ 맀겨주λ μλμ° ν¨μ(Window Function)μ λλ€.
RANK(), DENSE_RANK()μ λ¬λ¦¬ κ°μ κ°μ΄ μ¬λ¬ κ° μμ΄λ μλ²μ μ€λ³΅ μμ΄ λ²νΈκ° 맀겨μ§λλ€.

κ²°κ³Ό ν μ΄λΈμ 보면 μμΌ, μ±λ³λ³λ‘ μμ¬ κΈμ‘μ μ€μκ°μ΄ μ ꡬν΄μ§ κ²μ νμΈν μ μμ΅λλ€.
λ°©λ² 2
λλ²μ§Έ λ°©λ²μ, μ€μκ°μ μμμ (μ 체 λ μ½λ μ + 1 / 2)λ₯Ό λΉκ΅ν΄μ£Όλ κ²μ λλ€.


βͺοΈλ μ½λκ° νμκ°
(μ 체 λ μ½λ μ + 1 / 2)μ ꡬν΄λ³΄λ©΄ (9 + 1) / 2 = 5μ λλ€.
μ¦, μ€μκ°μ λ μ½λμ μμμ (μ 체 λ μ½λ μ + 1 / 2)κ° μΌμΉνλ κ²½μ°μΌ κ²μ λλ€.
βͺοΈλ μ½λκ° μ§μκ°
(μ 체 λ μ½λ μ + 1 / 2)μ ꡬν΄λ³΄λ©΄ (10 + 1) / 2 = 5.5μ λλ€.
μ€μκ°μ (μ 체 λ μ½λ μ + 1) / 2μ ⌊λ΄λ¦Όκ°⌋κ³Ό ⌈μ¬λ¦Όκ°⌉ μμΉμ μλ κ°λ€μ νκ· μ λλ€.
λ°©λ² 2 : SQL μ½λ
with rownum_table as
(select
day,
sex,
total_bill,
row_number() over (partition by day, sex order by total_bill asc) as row_num, -- λ μ½λ μμ λ§€κΈ°κΈ°
count(*) over (partition by day, sex) as tot_cnt -- μ 체 λ μ½λ μ
from tips)
select
day,
sex,
avg(total_bill) as median_total_bill -- νκ· κ΅¬νκΈ°
from rownum_table
where row_num between floor((tot_cnt + 1) / 2.0) and ceil((tot_cnt + 1) / 2.0) -- λ΄λ¦Όν κ°κ³Ό μ¬λ¦Όν κ° μ¬μ΄μ μλ κ°
group by 1, 2
βͺοΈFLOOR & CEIL
floor : λ΄λ¦Ό ν¨μ
ceil (λλ ceiling) : μ¬λ¦Ό ν¨μ
κ²°κ³Όλ λμΌνκ² μ λμμ΅λλ€!
λ²μΈ ) wide ννμ νΌλ²ν μ΄λΈ ꡬνκΈ°

μ΄λ κ² longν ννμ ν μ΄λΈμ wide ννμ νΌλ² ν μ΄λΈλ‘ λ³ννμ¬ κ°λ μ±μ λμ¬λ³΄κ² μ΅λλ€.
-- μμ longν ννμ μ€μκ° ν
μ΄λΈμ cte long_tableλ‘ μ μνμμ΅λλ€.
SELECT
sex,
max(case when day = 'Thur' then median end) as 'Thursday',
max(case when day = 'Fri' then median end) as 'Friday',
max(case when day = 'Sat' then median end) as 'Saturday',
max(case when day = 'Sun' then median end) as 'Sunday'
from long_table
group by sex

κ²°κ³Όκ° wideν ννμ ν μ΄λΈλ‘ μ μΆλ ₯μ΄ λ κ²μ νμΈν μ μμ΅λλ€.
μ 리
μ€λμ μ€μκ°μ ꡬνλ ν¨μκ° μμ λ
μ§μ ꡬνλ λ°©λ²μ μμ보μμ΅λλ€.
μΆκ°λ‘ λ‘± ννμ ν μ΄λΈμ μμ΄λ ννλ‘ νΌλ²νλ λ°©λ²λ μμ보μμ΅λλ€.
λ§μ μ§μ ꡬννλ €λ©΄ ν·κ°λ¦΄ μ μλ λ΄μ©λ€μ΄λΌ, μ 리ν΄λλ©΄ μμΌλ‘ λΆμ μμ μ ν° λμμ΄ λ κ² κ°μ΅λλ€!