Skip to Content
CoreRx์šด์˜์ž ๋งค๋‰ด์–ผETL ํŒŒ์ดํ”„๋ผ์ธ

ETL ํŒŒ์ดํ”„๋ผ์ธ

UBIST ์ฒ˜๋ฐฉ์กฐ์ œ ๋ฐ์ดํ„ฐ๋ฅผ ์—‘์…€ ํŒŒ์ผ์—์„œ PostgreSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์ ์žฌํ•˜๋Š” ETL(Extract-Transform-Load) ํŒŒ์ดํ”„๋ผ์ธ์˜ ๊ตฌ์กฐ์™€ ์‹คํ–‰ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

UBIST ์—‘์…€ ๊ตฌ์กฐ

UBIST์—์„œ ์ œ๊ณตํ•˜๋Š” ์—‘์…€ ํŒŒ์ผ์€ Wide/Pivot ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ๋‚ด์šฉ
ํŒŒ์ผ๋ช…UBIST_D1 Sales.xlsx
์‹œํŠธSheet1
ํ–‰ ์ˆ˜์•ฝ 110,000ํ–‰
์ปฌ๋Ÿผ ๊ตฌ์กฐ10๊ฐœ ์ฐจ์› ์ปฌ๋Ÿผ + 3๊ฐœ ์ง€ํ‘œ x 60๊ฐœ์›” = 191 ์ปฌ๋Ÿผ
๋ฐ์ดํ„ฐ ๋ฒ”์œ„268๊ฐœ ์ œ์กฐ์‚ฌ, 13,265๊ฐœ ์ œํ’ˆ, 60๊ฐœ์›”๋ถ„

์ฐจ์› ์ปฌ๋Ÿผ (10๊ฐœ)

์—‘์…€ ์ปฌ๋ŸผDB ์ปฌ๋Ÿผ๋ช…์„ค๋ช…
์ œ์กฐ์‚ฌ (A์—ด)manufacturer268๊ฐœ ์ œ์กฐ์‚ฌ. ์ž์‚ฌ๋Š” โ€˜์œ ๋‹ˆ์˜จโ€™
ํŒ๋งค์‚ฌ (B์—ด)sellerํŒ๋งค์‚ฌ๋ช…
์ œํ’ˆ (C์—ด)product13,265๊ฐœ ์ œํ’ˆ๋ช…
ATC (E์—ด)atc_classATC ์•ฝํšจ๊ตฐ ๋ถ„๋ฅ˜
์„ฑ๋ถ„ (F์—ด)ingredient์ฃผ์„ฑ๋ถ„๋ช…
์‹์•ฝ์ฒ˜ (G์—ด)kfda_class์‹์•ฝ์ฒ˜ ๋ถ„๋ฅ˜
์ œํ˜• (H์—ด)dosage_form์ œํ˜• (์ •์ œ, ์บก์А ๋“ฑ)
ํˆฌ์—ฌ๊ฒฝ๋กœ (I์—ด)admin_route๊ฒฝ๊ตฌ, ์ฃผ์‚ฌ ๋“ฑ
๊ธ‰์—ฌ๊ตฌ๋ถ„ (J์—ด)reimbursement_type๊ธ‰์—ฌ/๋ณธ์ธ๋ถ€๋‹ด/๋น„๊ธ‰์—ฌ
๋ณ‘์ƒ (K์—ด)bed_size์˜๋ฃŒ๊ธฐ๊ด€ ๊ทœ๋ชจ (300+/100299/3099/30 ๋ฏธ๋งŒ)

์ง€ํ‘œ ์ปฌ๋Ÿผ (3๊ฐœ x 60๊ฐœ์›”)

์ง€ํ‘œ ์ ‘๋ฏธ์‚ฌDB ์ปฌ๋Ÿผ๋ช…๋‹จ์œ„
์ฒ˜๋ฐฉ๊ฑด์ˆ˜_Pprescription_count๊ฑด
์ฒ˜๋ฐฉ์กฐ์ œ์•ก(์›)prescription_amount์›
์ฒ˜๋ฐฉ๋Ÿ‰_Pprescription_qty์ˆ˜๋Ÿ‰

Wideโ†’Long ๋ณ€ํ™˜

ETL์˜ ํ•ต์‹ฌ์€ Wide ํ˜•ํƒœ(์›”์ด ์ปฌ๋Ÿผ)๋ฅผ Long ํ˜•ํƒœ(์›”์ด ํ–‰)๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ณ€ํ™˜ ์ „ (Wide)

์ œ์กฐ์‚ฌ | ์ œํ’ˆ | ATC | 2025-01_์ฒ˜๋ฐฉ๊ฑด์ˆ˜ | 2025-01_์ฒ˜๋ฐฉ์•ก | 2025-01_์ฒ˜๋ฐฉ๋Ÿ‰ | 2025-02_์ฒ˜๋ฐฉ๊ฑด์ˆ˜ | ... ์œ ๋‹ˆ์˜จ | A์ œํ’ˆ | N02 | 150 | 5000000 | 300 | 160 | ...

๋ณ€ํ™˜ ํ›„ (Long)

์ œ์กฐ์‚ฌ | ์ œํ’ˆ | ATC | ์—ฐ์›” | ์ฒ˜๋ฐฉ๊ฑด์ˆ˜ | ์ฒ˜๋ฐฉ์•ก | ์ฒ˜๋ฐฉ๋Ÿ‰ ์œ ๋‹ˆ์˜จ | A์ œํ’ˆ | N02 | 2025-01 | 150 | 5000000 | 300 ์œ ๋‹ˆ์˜จ | A์ œํ’ˆ | N02 | 2025-02 | 160 | 5200000 | 320

๋ณ€ํ™˜ ๊ทœ์น™

  • 3๊ฐœ ์ง€ํ‘œ(์ฒ˜๋ฐฉ๊ฑด์ˆ˜, ์ฒ˜๋ฐฉ์•ก, ์ฒ˜๋ฐฉ๋Ÿ‰)๊ฐ€ ๋ชจ๋‘ 0์ธ ์›”์€ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค (๋ถˆํ•„์š”ํ•œ ์ œ๋กœ ๋ ˆ์ฝ”๋“œ ๋ฐฉ์ง€).
  • ๋™์ผ ๋ฐฐ์น˜ ๋‚ด ์ค‘๋ณต ํ‚ค(์ œ์กฐ์‚ฌ+์ œํ’ˆ+์—ฐ์›”)๊ฐ€ ์กด์žฌํ•˜๋ฉด UPSERT๋กœ ์ตœ์‹  ๊ฐ’์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • D์—ด(์ œ์กฐ์‚ฌ ์ค‘๋ณต)์€ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค (A์—ด๊ณผ 100% ๋™์ผ).

์‹คํ–‰ ๋ช…๋ น์–ด

ETL ์Šคํฌ๋ฆฝํŠธ๋Š” scripts/ubist_import.py์ž…๋‹ˆ๋‹ค.

์ „์ฒด ์ ์žฌ

์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์ ์žฌํ•ฉ๋‹ˆ๋‹ค. ์ตœ์ดˆ ์‹คํ–‰ ๋˜๋Š” ์ „์ฒด ์žฌ์ ์žฌ ์‹œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

python3 scripts/ubist_import.py --file "UBIST_D1 Sales.xlsx" --direct

ํŠน์ • ์—ฐ๋„๋งŒ ์ ์žฌ

ํŠน์ • ์—ฐ๋„์˜ ๋ฐ์ดํ„ฐ๋งŒ ์ ์žฌํ•ฉ๋‹ˆ๋‹ค. ์ฆ๋ถ„ ์—…๋ฐ์ดํŠธ ์‹œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

python3 scripts/ubist_import.py --file "UBIST_D1 Sales.xlsx" --year 2025

๋“œ๋ผ์ด๋Ÿฐ (ํ…Œ์ŠคํŠธ)

์‹ค์ œ DB์— ์ ์žฌํ•˜์ง€ ์•Š๊ณ  ๋ณ€ํ™˜ ๊ฒฐ๊ณผ๋งŒ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ ํŒŒ์ผ ๊ฒ€์ฆ ์‹œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

python3 scripts/ubist_import.py --file "UBIST_D1 Sales.xlsx" --dry-run

ํŠน์ • ์—ฐ๋„ ์‚ญ์ œ

ํŠน์ • ์—ฐ๋„์˜ UBIST ๋ฐ์ดํ„ฐ๋ฅผ DB์—์„œ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.

python3 scripts/ubist_import.py --delete-year 2021

์‹คํ–‰ ํ๋ฆ„

ETL ์Šคํฌ๋ฆฝํŠธ์˜ ๋‚ด๋ถ€ ์ฒ˜๋ฆฌ ์ˆœ์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. ์—‘์…€ ํŒŒ์ผ ๋กœ๋“œ (openpyxl, read_only ๋ชจ๋“œ๋กœ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจํ™”)
  2. ํ—ค๋” ํŒŒ์‹ฑ (1~2ํ–‰: ์ง€ํ‘œ ๊ทธ๋ฃน + ์›” ํ—ค๋” ๋งคํ•‘)
  3. Wideโ†’Long ๋ณ€ํ™˜ (ํ–‰ x ์›” ์กฐํ•ฉ์„ ๊ฐœ๋ณ„ ๋ ˆ์ฝ”๋“œ๋กœ ๋ถ„ํ•ด)
  4. ์ œ๋กœ๊ฐ’ ์Šคํ‚ต (3๊ฐœ ์ง€ํ‘œ ๋ชจ๋‘ 0์ด๋ฉด ํ•ด๋‹น ๋ ˆ์ฝ”๋“œ ์ œ์™ธ)
  5. ๋ฐฐ์น˜ ๋‚ด ์ค‘๋ณต ์ œ๊ฑฐ (UPSERT ํ‚ค ๊ธฐ์ค€)
  6. ๋ฒŒํฌ UPSERT (psycopg2 execute_values, 5,000ํ–‰ ๋‹จ์œ„ ๋ฐฐ์น˜)
  7. ์ ์žฌ ์™„๋ฃŒ ํ›„ ์บ์‹œ ์ž๋™ ๊ฐฑ์‹  (refresh_all_kpi_cache() ํ˜ธ์ถœ)

์—๋Ÿฌ ์ฒ˜๋ฆฌ

ํŒŒ์ผ ๊ด€๋ จ ์—๋Ÿฌ

์—๋Ÿฌ์›์ธํ•ด๊ฒฐ
FileNotFoundErrorํŒŒ์ผ ๊ฒฝ๋กœ ์˜ค๋ฅ˜--file ๊ฒฝ๋กœ ํ™•์ธ, ํŒŒ์ผ๋ช… ๊ณต๋ฐฑ ์ฃผ์˜ (๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ๊ธฐ)
ํ—ค๋” ํŒŒ์‹ฑ ์‹คํŒจ์—‘์…€ ๊ตฌ์กฐ ๋ณ€๊ฒฝUBIST ํŒŒ์ผ ํ˜•์‹์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ. ์ปฌ๋Ÿผ ์ˆœ์„œ ๊ฒ€์ฆ
๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ96MB ํŒŒ์ผ ๋กœ๋“œ ์‹œread_only=True ๋ชจ๋“œ ํ™•์ธ. ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ ์—ฌ์œ  ํ™•๋ณด

DB ๊ด€๋ จ ์—๋Ÿฌ

์—๋Ÿฌ์›์ธํ•ด๊ฒฐ
Connection refusedDB ์—ฐ๊ฒฐ ์‹คํŒจSupabase Docker ์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ ํ™•์ธ
UNIQUE violation์ค‘๋ณต ํ‚ค ์ถฉ๋ŒUPSERT๊ฐ€ ์ •์ƒ ๋™์ž‘ํ•˜๋ฉด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ. ์Šคํฌ๋ฆฝํŠธ ๋ฒ„์ „ ํ™•์ธ
Disk full๋””์Šคํฌ ์šฉ๋Ÿ‰ ๋ถ€์กฑ๋””์Šคํฌ ์—ฌ์œ  ๊ณต๊ฐ„ ํ™•๋ณด ํ›„ ์žฌ์‹คํ–‰

์žฌ์‹คํ–‰ ์•ˆ์ „์„ฑ

ETL ์Šคํฌ๋ฆฝํŠธ๋Š” UPSERT(INSERT ON CONFLICT UPDATE) ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๋ฏ€๋กœ, ๊ฐ™์€ ํŒŒ์ผ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ์‹คํ–‰ํ•ด๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์ค‘๋ณต๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์›์ธ์„ ํ•ด๊ฒฐํ•œ ํ›„ ๋™์ผํ•œ ๋ช…๋ น์–ด๋กœ ์žฌ์‹คํ–‰ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์˜์กด์„ฑ

ETL ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰์— ํ•„์š”ํ•œ Python ํŒจํ‚ค์ง€์ž…๋‹ˆ๋‹ค.

ํŒจํ‚ค์ง€์—ญํ• 
openpyxl์—‘์…€ ํŒŒ์ผ ํŒŒ์‹ฑ
psycopg2-binaryPostgreSQL ์—ฐ๊ฒฐ ๋ฐ ๋ฒŒํฌ UPSERT
python-dotenv.env ํŒŒ์ผ์—์„œ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กœ๋“œ
pip install -r scripts/requirements.txt

ํ™˜๊ฒฝ๋ณ€์ˆ˜

ETL ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์˜ .env ํŒŒ์ผ์— ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

ํ™˜๊ฒฝ๋ณ€์ˆ˜์„ค๋ช…
DATABASE_URLPostgreSQL ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด
POSTGRES_PASSWORDPostgreSQL ๋น„๋ฐ€๋ฒˆํ˜ธ

๋‹ค์Œ ๋‹จ๊ณ„

Last updated on