게임 재화 시세를 들여다볼 때마다 막히는 지점이 있었다. A게임은 “골드 1억에 3,500원”, B게임은 “다이아 5만 개 묶음 12,000원”. 숫자만 놓고 보면 도무지 뭐가 싼지 감이 안 온다. 단위도 묶음 크기도 다르니까. 오늘은 이 뒤죽박죽인 시세를 단위당 현금가(price per unit)로 정규화해서 한 줄에 세워 비교하는 집계 모델을 정리해 뒀다.

⚠️ 아래 데이터·가격은 전부 합성/더미 값이다. 실제 거래 시세나 투자 판단이 아니며, 특정 게임·거래소를 지칭하지 않는다.

왜 원본 시세는 그냥 비교하면 안 되나?

문제의 핵심은 세 가지가 뒤섞여 있다는 것이다. 재화 단위(unit)가 다르고, 한 번에 파는 묶음 크기(lot size)가 다르고, 그래서 표시가격의 기준점(base)이 제각각이다.

flowchart LR
  subgraph RAW["① 원본 시세 (비교 불가)"]
    A["A게임<br/>골드 1억 = 3,500원"]
    B["B게임<br/>다이아 5만 = 12,000원"]
    C["C게임<br/>은화 200만 = 900원"]
  end
  subgraph NORM["② 정규화 (비교 가능)"]
    N["단위당 현금가<br/>= 1억 재화당 원"]
  end
  RAW --> STEP["③ base 통일<br/>수량으로 나눔"] --> NORM
  classDef a fill:#e7f5ff,stroke:#1c7ed6,color:#10548f;
  classDef b fill:#fff3bf,stroke:#f08c00,color:#a45500;
  classDef c fill:#d3f9d8,stroke:#2f9e44,color:#1a6b2c;
  class RAW,A,B,C a
  class STEP b
  class NORM,N c

정규화의 뼈대는 단순하다. 단위당 현금가 = 판매가 ÷ 재화 수량. 여기에 자릿수가 커지니 “1억 재화당 원” 같은 공통 기준(base_unit)을 곱해 사람이 읽기 좋은 스케일로 맞춘다.

원천 데이터는 어떻게 두어야 하나?

거래 한 건을 그대로 적재하는 listings 테이블 하나면 충분하다. 핵심은 수량과 가격을 절대 미리 합치지 말고 원자값 그대로 저장하는 것. 환산은 조회 시점에 한다.

컬럼의미예시(더미)
listing_id거래 식별자1001
game_code게임 구분AAA
currency_amt재화 수량(개)100000000
price_krw판매가(원)3500
listed_at등록 시각2026-07-10
-- 합성 더미 데이터
CREATE TABLE listings (
  listing_id  BIGINT,
  game_code   TEXT,
  currency_amt NUMERIC,   -- 재화 수량
  price_krw   NUMERIC,     -- 판매가(원)
  listed_at   DATE
);
 
INSERT INTO listings VALUES
  (1001, 'AAA', 100000000, 3500, '2026-07-10'),
  (1002, 'AAA', 250000000, 8400, '2026-07-11'),
  (1003, 'BBB',     50000, 12000, '2026-07-10'),
  (1004, 'CCC',   2000000,   900, '2026-07-12');

단위당 현금가는 어떻게 집계하나?

환산식을 SQL 한 줄로 박아 넣는다. base_unit을 1억(100000000)으로 두면 “1억 재화당 원” 지표가 나온다. 게임마다 재화 단위(개/원 기준)가 달라도 결과는 같은 축 위에 놓인다.

WITH normalized AS (
  SELECT
    game_code,
    currency_amt,
    price_krw,
    -- 단위당 현금가: 1억 재화당 원
    ROUND(price_krw / currency_amt * 100000000, 2) AS krw_per_100m,
    listed_at
  FROM listings
)
SELECT
  game_code,
  COUNT(*)                       AS n,
  ROUND(AVG(krw_per_100m), 1)    AS avg_price,
  MIN(krw_per_100m)              AS floor_price  -- 최저가(시세 바닥)
FROM normalized
GROUP BY game_code
ORDER BY avg_price;

결과를 표로 옮기면 비로소 “무엇이 싼가”가 보인다.

game_code원본 표시가krw_per_100m(환산)해석
CCC은화 200만=900원45,000가장 비쌈
AAA골드 1억=3,500원3,500중간
BBB다이아 5만=12,000원(별도 축)단위 규모가 달라 주의

여기서 바로 함정 하나가 튀어나온다.

서로 다른 재화를 같은 축에 세워도 되나?

안 된다. 정규화는 같은 재화 종류 안에서만 유효하다. B게임 다이아처럼 애초에 통용 규모가 작은 재화를 1억 기준으로 끌어올리면 숫자가 폭발해 오해를 부른다. 그래서 나는 환산 지표에 항상 currency_type을 함께 물려서, 비교는 같은 타입끼리만 하도록 가드를 건다.

flowchart TD
  Q["환산값 krw_per_100m"] --> G{"같은 재화 타입인가?"}
  G -->|"예"| OK["⭕ 비교 가능<br/>floor/avg 정렬"]
  G -->|"아니오"| NO["❌ 비교 금지<br/>타입별 분리 축"]
  classDef a fill:#e7f5ff,stroke:#1c7ed6,color:#10548f;
  classDef ok fill:#d3f9d8,stroke:#2f9e44,color:#1a6b2c;
  classDef no fill:#ffe3e3,stroke:#e03131,color:#a12020;
  class Q a
  class OK ok
  class NO no

또 하나, 최저가(floor)와 평균(avg)을 반드시 같이 봐야 한다. 평균만 보면 소수의 대량 덤핑 매물이 시세를 왜곡한다. 실무에서는 최저 10% 매물을 잘라낸 절사평균이나 중앙값을 같이 뽑아 이상치에 둔감한 시세선을 만든다.

정리 — 이 모델이 주는 것

  • 원자값 저장 → 조회 시 환산: 수량과 가격을 미리 합치지 않아야 base_unit을 자유롭게 바꿔가며 볼 수 있다.
  • 단위당 현금가라는 공통 축 하나로 뒤죽박죽 시세가 한 줄에 선다.
  • 타입 가드 + 최저가 병행: 다른 재화를 억지로 비교하지 않고, 덤핑에 흔들리지 않는 시세선을 얻는다.

결국 데이터 모델링에서 배운 그대로다. 비교하려면 먼저 같은 단위로 만들어라. 게임 재화 시세도 예외가 아니었다.