<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko">
	<id>https://devcafe.co.kr/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Devcafe</id>
	<title>데브카페 - 사용자 기여 [ko]</title>
	<link rel="self" type="application/atom+xml" href="https://devcafe.co.kr/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Devcafe"/>
	<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/%ED%8A%B9%EC%88%98:%EA%B8%B0%EC%97%AC/Devcafe"/>
	<updated>2026-04-04T02:52:45Z</updated>
	<subtitle>사용자 기여</subtitle>
	<generator>MediaWiki 1.42.1</generator>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EB%B6%80%EC%9E%90%EC%95%84%EB%B9%A0_%ED%83%80%EC%A7%B1_%ED%88%AC%EC%9E%90%EA%B8%B0%EB%A1%9D&amp;diff=2371</id>
		<title>부자아빠 타짱 투자기록</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EB%B6%80%EC%9E%90%EC%95%84%EB%B9%A0_%ED%83%80%EC%A7%B1_%ED%88%AC%EC%9E%90%EA%B8%B0%EB%A1%9D&amp;diff=2371"/>
		<updated>2026-04-02T09:45:25Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: @yhdgj675 (たーちゃん＠バブルおじさん) 씨의 **실제 투자 경험**, **과거 주요 보유·집중 투자 종목**, **인터뷰·책 내용**을 바탕으로 **투자 방법**을 중심으로 요약 정리해 드릴게요.  ### 투자 여정 요약 (50만 엔 → 80억 엔 이상) - **1998년 (대학생 시절)**: 아르바이트 돈으로 **50만 엔** 시작. 『금-rich 아버지 가난한 아버지』 읽고 투자 시작. 첫 종목은 **세가사미 홀딩...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;@yhdgj675 (たーちゃん＠バブルおじさん) 씨의 **실제 투자 경험**, **과거 주요 보유·집중 투자 종목**, **인터뷰·책 내용**을 바탕으로 **투자 방법**을 중심으로 요약 정리해 드릴게요.&lt;br /&gt;
&lt;br /&gt;
### 투자 여정 요약 (50만 엔 → 80억 엔 이상)&lt;br /&gt;
- **1998년 (대학생 시절)**: 아르바이트 돈으로 **50만 엔** 시작. 『금-rich 아버지 가난한 아버지』 읽고 투자 시작. 첫 종목은 **세가사미 홀딩스 (구 사미, 6460)**.&lt;br /&gt;
- **2000년**: 세가 주식 성공으로 자산 500만 엔.&lt;br /&gt;
- **2003~2005년**: 폭락한 **호주 금광주**에 **전 재산 집중 투자** → 자산 **1억 엔** 달성 (첫 억만장자).&lt;br /&gt;
- **2005년 이후**: 집중 투자에서 **바리ュー株 분산 투자**로 전환.&lt;br /&gt;
- **2008년 리먼 쇼크**: 바닥 근처에서 산 종목들이 회복되거나 TOB(공개매수)로 거의 손실 없이 버팀.&lt;br /&gt;
- **2009~2013년**: **아크랜드 서비스 홀딩스 (3085, 카츠야 등)** 지속 매수 → **테ンバ거(10배)**.  &lt;br /&gt;
  **아이풀 (8515)** 과지불금 문제로 타격받던 시점에 집중 투자 → 반년 만에 약 7배.&lt;br /&gt;
- **2013년**: 자산 **10억 엔** 달성, 연 배당금 3,000만 엔 이상 (의사 수입 초과). 38세에 한 번 FIRE했으나 지루해서 복귀.&lt;br /&gt;
- **2016년**: **히로세 통상 (7185)** 거의 유통주식 전량 매수 → 반년 만에 약 3배.&lt;br /&gt;
- **2020년대**: **분산 + 집중** 병행. 2024년경 자산 **50억 엔**, 현재 **80억 엔** 이상으로 성장.&lt;br /&gt;
- **2024~2026년 현재**: **HD한국조선해양 (009540)**에 **거의 전 재산 집중 투자**. 2025년 연초부터 큰 상승 (+96% 이상 보고, 한때 +110% 넘음). 조선업 사이클 회복 기대.&lt;br /&gt;
&lt;br /&gt;
### 주요 투자 방법: **시클리컬 밸류 (Cyclical Value) 주식 투자** + **분산 × 집중**&lt;br /&gt;
그의 핵심 철학은 **“안 싸서 사는 게 아니라, 가치가 있어서 사는 것”**입니다. 책 『50만원을 50억엔으로 불린 투자자의 아버지가 딸에게 주는 가르침』에서 **3가지 바리ュー 투자법**을 체계적으로 설명해요.&lt;br /&gt;
&lt;br /&gt;
1. **자산 바리ュー株 투자**  &lt;br /&gt;
   기업이 보유한 **순자산(자산 가치)**에 비해 주가가 너무 낮은 종목을 사는 방법.  &lt;br /&gt;
   “안전마진”이 크고, 최악의 경우에도 자산 가치로 보호받을 수 있음.&lt;br /&gt;
&lt;br /&gt;
2. **수익 바리ュー株 투자**  &lt;br /&gt;
   기업의 **미래 수익력(이익 창출 능력)**이 주가에 충분히 반영되지 않은 종목.  &lt;br /&gt;
   안정적으로 돈을 버는 기업을 저평가 상태에서 사는 전형적인 바리ュー.&lt;br /&gt;
&lt;br /&gt;
3. **시클리컬 바리ュー株 투자 (그의 메인 무기)**  &lt;br /&gt;
   - **경기 순환(시클리컬)**이 강한 업종(조선, 금광, 금융 등)에서 **불황·적자**일 때 저점 매수.&lt;br /&gt;
   - 경기가 회복되면 **이익이 급증**하면서 주가가 크게 오름 (테ンバ거 가능성 높음).&lt;br /&gt;
   - “적자 기업일수록 대박 날 가능성이 크다”는 게 핵심.  &lt;br /&gt;
   - 중요한 점: 단순히 적자라고 사는 게 아니라, **업황 회복 시 이익 폭발력**을 미리 읽어야 함.  &lt;br /&gt;
   - “4년 주기 경기 순환”을 고려해 타이밍을 잡음.&lt;br /&gt;
&lt;br /&gt;
**실전 운용 스타일**:&lt;br /&gt;
- 평소: **20종목 정도 분산** 투자 (방어적, 안정 위주).&lt;br /&gt;
- 확신 높을 때: **한두 종목에 과감한 집중 투자** (공격적).  &lt;br /&gt;
  예: 호주 금광주, 아크랜드, 아이풀, 히로세 통상, 최근 HD한국조선해양.&lt;br /&gt;
- 매수 기준: **저평가 + 업황 회복 신호** (재무제표, IR, 사계보 등 철저 분석).&lt;br /&gt;
- 매도: 고점에서 팔기보다는 **가치가 실현될 때까지** 오래 보유하는 경우 많음.&lt;br /&gt;
- 전체적으로 **“안 싸서 사는 게 아니라 가치가 있어서 산다”**는 철저한 바리ュー 투자.&lt;br /&gt;
&lt;br /&gt;
### 최근 상황 (2025~2026년)&lt;br /&gt;
- **HD한국조선해양**을 메인으로 거의 올인.  &lt;br /&gt;
  이유: 조선업황 회복 (LNG선, 컨테이너선 수요 증가, 중국 규제 수혜 등).  &lt;br /&gt;
  이익률이 12~25%까지 상승할 여력 있다고 판단.&lt;br /&gt;
- 암 투병 중에도 투자 계속하며, 책을 통해 **딸들에게 남기는 유산**으로 투자법을 정리.&lt;br /&gt;
&lt;br /&gt;
### 한 줄 요약&lt;br /&gt;
타-chan 씨는 **일반적인 바리ュー 투자**를 기반으로 하되, **경기 순환 업종의 불황 저점에서 집중 매수**해 큰 수익을 내는 **시클리컬 밸류 전문가**예요.  &lt;br /&gt;
위험을 감수한 집중 투자와 철저한 분석이 결합된 스타일로, “일하면서도 큰돈을 불릴 수 있다”는 메시지를 강조합니다.&lt;br /&gt;
&lt;br /&gt;
**주의**: 이는 과거 경험과 공개된 내용 요약일 뿐, 현재 추천이 아닙니다.  &lt;br /&gt;
투자는 본인 판단과 책임으로 하시고, 책을 직접 읽어보시는 걸 강력 추천해요 (실전적이고 재현성 높은 내용이 많아요).&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EB%B6%80%EC%9E%90%EC%95%84%EB%B9%A0_%ED%83%80%EC%A7%B1_%ED%88%AC%EC%9E%90%EB%B2%95&amp;diff=2370</id>
		<title>부자아빠 타짱 투자법</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EB%B6%80%EC%9E%90%EC%95%84%EB%B9%A0_%ED%83%80%EC%A7%B1_%ED%88%AC%EC%9E%90%EB%B2%95&amp;diff=2370"/>
		<updated>2026-04-02T09:40:42Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: @yhdgj675 (たーちゃん＠バブルおじさん) 씨가 **X(트위터)에 직접 “이 주식을 사라!”라고 적극적으로 추천**한 글은 거의 없어요.   대신 본인의 실제 투자 경험, 과거 보유·집중 투자했던 종목, 인터뷰·책 내용을 바탕으로 **연도별로 주요 투자/주목했던 주식**을 정리해 드릴게요. (책과 인터뷰, 과거 포스트 기반)  ### 초기 (학생 시절 ~ 2000년대 초반) - **1998년경**: **...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;@yhdgj675 (たーちゃん＠バブルおじさん) 씨가 **X(트위터)에 직접 “이 주식을 사라!”라고 적극적으로 추천**한 글은 거의 없어요. &lt;br /&gt;
&lt;br /&gt;
대신 본인의 실제 투자 경험, 과거 보유·집중 투자했던 종목, 인터뷰·책 내용을 바탕으로 **연도별로 주요 투자/주목했던 주식**을 정리해 드릴게요. (책과 인터뷰, 과거 포스트 기반)&lt;br /&gt;
&lt;br /&gt;
### 초기 (학생 시절 ~ 2000년대 초반)&lt;br /&gt;
- **1998년경**: **세가사미 홀딩스 (구 사미, 6460)**  &lt;br /&gt;
  첫 투자 종목. 게임 관련으로 관심을 가지고 매수.&lt;br /&gt;
- **2003~2005년경**: **호주 금광주** (구체적 종목은 공개 적음)  &lt;br /&gt;
  전 재산 집중 투자로 테ンバ거(10배)급 성공. 29세頃에 억만장자 달성의 계기.&lt;br /&gt;
&lt;br /&gt;
### 2010년대&lt;br /&gt;
- **2009~2013년경**: **아크랜드 서비스 홀딩스 (3085, 카츠야 등)**  &lt;br /&gt;
  지속 매수 → **테ンバ거(10배)** 달성. 주요 종목으로 키움.&lt;br /&gt;
- **2012년경**: **아이풀 (8515)**  &lt;br /&gt;
  과지불금 문제로 타격받던 시점에 집중 투자 → 약 6개월 만에 7배 상승.&lt;br /&gt;
- **2016년경**: **히로세 통상 (7185)**  &lt;br /&gt;
  동종업계 GMO보다 저평가된 점을 보고 거의 유통주식 전량 매수 → 6개월 만에 약 3배.&lt;br /&gt;
&lt;br /&gt;
이 시기부터 “평소에는 분산 투자, 확신 높은 시클리컬(경기 순환) 종목에서는 집중 투자” 스타일이 자리 잡음.&lt;br /&gt;
&lt;br /&gt;
### 2020년대 (최근 주요 종목)&lt;br /&gt;
- **2023~2024년 상반기**: **나무라 조선소 (명촌 조선소)**  &lt;br /&gt;
  조선 버블 관련으로 집중 투자.&lt;br /&gt;
- **2024년 하반기 ~ 2025~2026년 현재**: **HD한국조선해양 (009540, 한국 주식)**  &lt;br /&gt;
  “올해 상반기는 나무라 조선소, 하반기는 HD한국조선해양을 메인으로”, “현재는 거의 전 재산을 한 종목에 집중”이라고 직접 언급.  &lt;br /&gt;
  2025년 연초부터 큰 상승을 보이며 +96%~+115%대 성과 보고 (배당·출금 포함).&lt;br /&gt;
&lt;br /&gt;
### 최근 인터뷰·책에서 언급·주목한 섹터·종목 (2025년)&lt;br /&gt;
- **조선업**: HD한국조선해양 (계속 메인)&lt;br /&gt;
- **토목·유리·공구 관련**: 우크라이나 재건 수요 기대&lt;br /&gt;
- **업무용 냉장고**: 요코레이 (밸류에이션 매력적, 과점화 기대)&lt;br /&gt;
- 기타 과거 언급: **고베물산 (업무 슈퍼, 業スー)** – 2006년 상장 전 매수 후 큰 성공 (판매는 일찍 했다고 후회)&lt;br /&gt;
&lt;br /&gt;
### 그의 투자 철학 요약&lt;br /&gt;
- **시클리컬 밸류 (Cyclical Value)**: 경기 순환으로 저평가된 업종·기업을 바닥에서 사서 회복 때까지 기다리는 스타일.&lt;br /&gt;
- 평소 20종목 정도 분산 + 확신 높은 종목에 과감한 집중.&lt;br /&gt;
- X 게시물에서는 구체적 “추천”보다는 본인 보유 상황과 생각을 공유하는 경우가 대부분입니다. 더 구체적인 추천은 TwitCasting(라이브)이나 인터뷰에서 나오는 편.&lt;br /&gt;
&lt;br /&gt;
**주의사항**  &lt;br /&gt;
- 이는 과거 실제 투자·언급 사례를 정리한 것이지, 현재 추천이 아닙니다.  &lt;br /&gt;
- 투자 결정은 본인 책임이며, 과거 성과가 미래를 보장하지 않습니다.&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2369</id>
		<title>시클리컬 주요 산업별 대장주</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2369"/>
		<updated>2026-04-02T09:34:37Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 시클리컬(경기순환) 주식 산업별 분류 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 시클리컬(경기순환) 주식 산업별 분류 ==&lt;br /&gt;
&lt;br /&gt;
=== 🏗️ 소재 / 철강 / 금속 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|철강  || POSCO홀딩스, 현대제철|| Nucor, US Steel &lt;br /&gt;
|-&lt;br /&gt;
|비철금속|| 고려아연, 풍산      || Freeport-McMoRan&lt;br /&gt;
|-&lt;br /&gt;
|화학  || LG화학, 롯데케미칼   ||BASF, Dow       &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== ⚓ 조선 / 해운 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|조선 ||HD현대중공업, 삼성중공업, 한화오션||    &lt;br /&gt;
|-&lt;br /&gt;
|해운 ||HMM, 장금상선           ||Maersk, MSC&lt;br /&gt;
|-&lt;br /&gt;
|벌크선||                    ||COSCO(중국)  &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 🛢️ 에너지 / 정유 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|정유   ||SK이노베이션, GS칼텍스(비상장)||ExxonMobil, Chevron      &lt;br /&gt;
|-&lt;br /&gt;
|유전서비스||                   ||Schlumberger, Halliburton&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 🏠 건설 / 부동산 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|건설 ||삼성물산, 현대건설, GS건설||Lennar, D.R. Horton&lt;br /&gt;
|-&lt;br /&gt;
|시멘트||쌍용C&amp;amp;E, 한일시멘트    ||Martin Marietta    &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 🚗 자동차 / 부품 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|완성차||현대차, 기아    ||GM, Ford, Toyota&lt;br /&gt;
|-&lt;br /&gt;
|부품 ||현대모비스, HL만도||BorgWarner      &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== ✈️ 항공 / 여행 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|항공    ||대한항공, 아시아나||Delta, United&lt;br /&gt;
|-&lt;br /&gt;
|호텔/카지노||파라다이스, GKL||MGM, Wynn    &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 🖥️ 반도체 / 디스플레이 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|메모리  ||삼성전자, SK하이닉스||Micron&lt;br /&gt;
|-&lt;br /&gt;
|디스플레이|LG디스플레이     ||      &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 🔋 배터리 / 소재 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|배터리  ||삼성SDI, LG에너지솔루션, SK온||CATL(중국) &lt;br /&gt;
|-&lt;br /&gt;
|배터리소재||에코프로, 포스코퓨처엠        ||Albemarle&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
* 📌 시클리컬 투자 핵심 포인트&lt;br /&gt;
&lt;br /&gt;
* 경기 사이클과 매수 타이밍&lt;br /&gt;
** 바닥 매수 원칙 : 업황 최악일 때 → PBR 낮고 적자일 때가 매수 기회&lt;br /&gt;
** PER 함정 주의 : 호황기엔 PER 낮아 보여도 이미 고점일 수 있음&lt;br /&gt;
** 재고 사이클 확인 필수 (반도체, 화학 특히 중요)&lt;br /&gt;
업종별 선행지표&lt;br /&gt;
**	철강/조선 → 발틱운임지수(BDI), 중국 PMI&lt;br /&gt;
**	반도체 → DRAM 현물가, 재고 수준&lt;br /&gt;
**	정유 → WTI/브렌트유 가격, 정제마진&lt;br /&gt;
**	화학 → 에틸렌 스프레드​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2368</id>
		<title>시클리컬 주요 산업별 대장주</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2368"/>
		<updated>2026-04-02T09:25:37Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 🏗️ 소재 / 철강 / 금속 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 시클리컬(경기순환) 주식 산업별 분류 ==&lt;br /&gt;
&lt;br /&gt;
=== 🏗️ 소재 / 철강 / 금속 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|철강  || POSCO홀딩스, 현대제철|| Nucor, US Steel &lt;br /&gt;
|-&lt;br /&gt;
|비철금속|| 고려아연, 풍산      || Freeport-McMoRan&lt;br /&gt;
|-&lt;br /&gt;
|화학  || LG화학, 롯데케미칼   ||BASF, Dow       &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== ⚓ 조선 / 해운 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국                  |글로벌        |&lt;br /&gt;
|---|--------------------|-----------|&lt;br /&gt;
|조선 |HD현대중공업, 삼성중공업, 한화오션|           |&lt;br /&gt;
|해운 |HMM, 장금상선           |Maersk, MSC|&lt;br /&gt;
|벌크선|                    |COSCO(중국)  |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🛢️ 에너지 / 정유 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분   |한국                 |미국                       |&lt;br /&gt;
|-----|-------------------|-------------------------|&lt;br /&gt;
|정유   |SK이노베이션, GS칼텍스(비상장)|ExxonMobil, Chevron      |&lt;br /&gt;
|유전서비스|                   |Schlumberger, Halliburton|&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🏠 건설 / 부동산 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국              |미국                 |&lt;br /&gt;
|---|----------------|-------------------|&lt;br /&gt;
|건설 |삼성물산, 현대건설, GS건설|Lennar, D.R. Horton|&lt;br /&gt;
|시멘트|쌍용C&amp;amp;E, 한일시멘트    |Martin Marietta    |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🚗 자동차 / 부품 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국         |글로벌             |&lt;br /&gt;
|---|-----------|----------------|&lt;br /&gt;
|완성차|현대차, 기아    |GM, Ford, Toyota|&lt;br /&gt;
|부품 |현대모비스, HL만도|BorgWarner      |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== ✈️ 항공 / 여행 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분    |한국        |미국           |&lt;br /&gt;
|------|----------|-------------|&lt;br /&gt;
|항공    |대한항공, 아시아나|Delta, United|&lt;br /&gt;
|호텔/카지노|파라다이스, GKL|MGM, Wynn    |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🖥️ 반도체 / 디스플레이 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분   |한국          |미국    |&lt;br /&gt;
|-----|------------|------|&lt;br /&gt;
|메모리  |삼성전자, SK하이닉스|Micron|&lt;br /&gt;
|디스플레이|LG디스플레이     |      |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔋 배터리 / 소재 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분   |한국                  |글로벌      |&lt;br /&gt;
|-----|--------------------|---------|&lt;br /&gt;
|배터리  |삼성SDI, LG에너지솔루션, SK온|CATL(중국) |&lt;br /&gt;
|배터리소재|에코프로, 포스코퓨처엠        |Albemarle|&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 📌 시클리컬 투자 핵심 포인트&lt;br /&gt;
&lt;br /&gt;
* 경기 사이클과 매수 타이밍&lt;br /&gt;
** 바닥 매수 원칙 : 업황 최악일 때 → PBR 낮고 적자일 때가 매수 기회&lt;br /&gt;
** PER 함정 주의 : 호황기엔 PER 낮아 보여도 이미 고점일 수 있음&lt;br /&gt;
** 재고 사이클 확인 필수 (반도체, 화학 특히 중요)&lt;br /&gt;
업종별 선행지표&lt;br /&gt;
**	철강/조선 → 발틱운임지수(BDI), 중국 PMI&lt;br /&gt;
**	반도체 → DRAM 현물가, 재고 수준&lt;br /&gt;
**	정유 → WTI/브렌트유 가격, 정제마진&lt;br /&gt;
**	화학 → 에틸렌 스프레드​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2367</id>
		<title>시클리컬 주요 산업별 대장주</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2367"/>
		<updated>2026-04-02T09:24:58Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 🏗️ 소재 / 철강 / 금속 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 시클리컬(경기순환) 주식 산업별 분류 ==&lt;br /&gt;
&lt;br /&gt;
=== 🏗️ 소재 / 철강 / 금속 ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ 캡션 텍스트&lt;br /&gt;
|-&lt;br /&gt;
! 표 제목 !! 표 제목 !! 표 제목&lt;br /&gt;
|-&lt;br /&gt;
| 예시 || 예시 || 예시&lt;br /&gt;
|-&lt;br /&gt;
| 예시 || 예시 || 예시&lt;br /&gt;
|-&lt;br /&gt;
| 예시 || 예시 || 예시&lt;br /&gt;
|}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 구분 !! 한국!! 미국/글로벌&lt;br /&gt;
|-&lt;br /&gt;
|철강  || POSCO홀딩스, 현대제철|| Nucor, US Steel &lt;br /&gt;
|-&lt;br /&gt;
|비철금속|| 고려아연, 풍산      || Freeport-McMoRan&lt;br /&gt;
|-&lt;br /&gt;
|화학  || LG화학, 롯데케미칼   ||BASF, Dow       &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== ⚓ 조선 / 해운 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국                  |글로벌        |&lt;br /&gt;
|---|--------------------|-----------|&lt;br /&gt;
|조선 |HD현대중공업, 삼성중공업, 한화오션|           |&lt;br /&gt;
|해운 |HMM, 장금상선           |Maersk, MSC|&lt;br /&gt;
|벌크선|                    |COSCO(중국)  |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🛢️ 에너지 / 정유 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분   |한국                 |미국                       |&lt;br /&gt;
|-----|-------------------|-------------------------|&lt;br /&gt;
|정유   |SK이노베이션, GS칼텍스(비상장)|ExxonMobil, Chevron      |&lt;br /&gt;
|유전서비스|                   |Schlumberger, Halliburton|&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🏠 건설 / 부동산 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국              |미국                 |&lt;br /&gt;
|---|----------------|-------------------|&lt;br /&gt;
|건설 |삼성물산, 현대건설, GS건설|Lennar, D.R. Horton|&lt;br /&gt;
|시멘트|쌍용C&amp;amp;E, 한일시멘트    |Martin Marietta    |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🚗 자동차 / 부품 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국         |글로벌             |&lt;br /&gt;
|---|-----------|----------------|&lt;br /&gt;
|완성차|현대차, 기아    |GM, Ford, Toyota|&lt;br /&gt;
|부품 |현대모비스, HL만도|BorgWarner      |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== ✈️ 항공 / 여행 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분    |한국        |미국           |&lt;br /&gt;
|------|----------|-------------|&lt;br /&gt;
|항공    |대한항공, 아시아나|Delta, United|&lt;br /&gt;
|호텔/카지노|파라다이스, GKL|MGM, Wynn    |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🖥️ 반도체 / 디스플레이 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분   |한국          |미국    |&lt;br /&gt;
|-----|------------|------|&lt;br /&gt;
|메모리  |삼성전자, SK하이닉스|Micron|&lt;br /&gt;
|디스플레이|LG디스플레이     |      |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔋 배터리 / 소재 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분   |한국                  |글로벌      |&lt;br /&gt;
|-----|--------------------|---------|&lt;br /&gt;
|배터리  |삼성SDI, LG에너지솔루션, SK온|CATL(중국) |&lt;br /&gt;
|배터리소재|에코프로, 포스코퓨처엠        |Albemarle|&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 📌 시클리컬 투자 핵심 포인트&lt;br /&gt;
&lt;br /&gt;
* 경기 사이클과 매수 타이밍&lt;br /&gt;
** 바닥 매수 원칙 : 업황 최악일 때 → PBR 낮고 적자일 때가 매수 기회&lt;br /&gt;
** PER 함정 주의 : 호황기엔 PER 낮아 보여도 이미 고점일 수 있음&lt;br /&gt;
** 재고 사이클 확인 필수 (반도체, 화학 특히 중요)&lt;br /&gt;
업종별 선행지표&lt;br /&gt;
**	철강/조선 → 발틱운임지수(BDI), 중국 PMI&lt;br /&gt;
**	반도체 → DRAM 현물가, 재고 수준&lt;br /&gt;
**	정유 → WTI/브렌트유 가격, 정제마진&lt;br /&gt;
**	화학 → 에틸렌 스프레드​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2366</id>
		<title>시클리컬 주요 산업별 대장주</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2366"/>
		<updated>2026-04-02T09:20:32Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 시클리컬(경기순환) 주식 산업별 분류 ==&lt;br /&gt;
&lt;br /&gt;
=== 🏗️ 소재 / 철강 / 금속 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분  |한국            |미국/글로벌          |&lt;br /&gt;
|----|--------------|----------------|&lt;br /&gt;
|철강  |POSCO홀딩스, 현대제철|Nucor, US Steel |&lt;br /&gt;
|비철금속|고려아연, 풍산      |Freeport-McMoRan|&lt;br /&gt;
|화학  |LG화학, 롯데케미칼   |BASF, Dow       |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== ⚓ 조선 / 해운 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국                  |글로벌        |&lt;br /&gt;
|---|--------------------|-----------|&lt;br /&gt;
|조선 |HD현대중공업, 삼성중공업, 한화오션|           |&lt;br /&gt;
|해운 |HMM, 장금상선           |Maersk, MSC|&lt;br /&gt;
|벌크선|                    |COSCO(중국)  |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🛢️ 에너지 / 정유 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분   |한국                 |미국                       |&lt;br /&gt;
|-----|-------------------|-------------------------|&lt;br /&gt;
|정유   |SK이노베이션, GS칼텍스(비상장)|ExxonMobil, Chevron      |&lt;br /&gt;
|유전서비스|                   |Schlumberger, Halliburton|&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🏠 건설 / 부동산 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국              |미국                 |&lt;br /&gt;
|---|----------------|-------------------|&lt;br /&gt;
|건설 |삼성물산, 현대건설, GS건설|Lennar, D.R. Horton|&lt;br /&gt;
|시멘트|쌍용C&amp;amp;E, 한일시멘트    |Martin Marietta    |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🚗 자동차 / 부품 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국         |글로벌             |&lt;br /&gt;
|---|-----------|----------------|&lt;br /&gt;
|완성차|현대차, 기아    |GM, Ford, Toyota|&lt;br /&gt;
|부품 |현대모비스, HL만도|BorgWarner      |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== ✈️ 항공 / 여행 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분    |한국        |미국           |&lt;br /&gt;
|------|----------|-------------|&lt;br /&gt;
|항공    |대한항공, 아시아나|Delta, United|&lt;br /&gt;
|호텔/카지노|파라다이스, GKL|MGM, Wynn    |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🖥️ 반도체 / 디스플레이 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분   |한국          |미국    |&lt;br /&gt;
|-----|------------|------|&lt;br /&gt;
|메모리  |삼성전자, SK하이닉스|Micron|&lt;br /&gt;
|디스플레이|LG디스플레이     |      |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 🔋 배터리 / 소재 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분   |한국                  |글로벌      |&lt;br /&gt;
|-----|--------------------|---------|&lt;br /&gt;
|배터리  |삼성SDI, LG에너지솔루션, SK온|CATL(중국) |&lt;br /&gt;
|배터리소재|에코프로, 포스코퓨처엠        |Albemarle|&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* 📌 시클리컬 투자 핵심 포인트&lt;br /&gt;
&lt;br /&gt;
* 경기 사이클과 매수 타이밍&lt;br /&gt;
** 바닥 매수 원칙 : 업황 최악일 때 → PBR 낮고 적자일 때가 매수 기회&lt;br /&gt;
** PER 함정 주의 : 호황기엔 PER 낮아 보여도 이미 고점일 수 있음&lt;br /&gt;
** 재고 사이클 확인 필수 (반도체, 화학 특히 중요)&lt;br /&gt;
업종별 선행지표&lt;br /&gt;
**	철강/조선 → 발틱운임지수(BDI), 중국 PMI&lt;br /&gt;
**	반도체 → DRAM 현물가, 재고 수준&lt;br /&gt;
**	정유 → WTI/브렌트유 가격, 정제마진&lt;br /&gt;
**	화학 → 에틸렌 스프레드​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2365</id>
		<title>시클리컬 주요 산업별 대장주</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EC%A3%BC%EC%9A%94_%EC%82%B0%EC%97%85%EB%B3%84_%EB%8C%80%EC%9E%A5%EC%A3%BC&amp;diff=2365"/>
		<updated>2026-04-02T09:17:02Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: == 시클리컬(경기순환) 주식 산업별 분류 ==  === 🏗️ 소재 / 철강 / 금속 === &amp;lt;source lang=bash&amp;gt; |구분  |한국            |미국/글로벌          | |----|--------------|----------------| |철강  |POSCO홀딩스, 현대제철|Nucor, US Steel | |비철금속|고려아연, 풍산      |Freeport-McMoRan| |화학  |LG화학, 롯데케미칼   |BASF, Dow       | &amp;lt;/source&amp;gt;  === ⚓ 조선 / 해운 === &amp;lt;source lang=bash&amp;gt; |구분 |한국                  |글...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 시클리컬(경기순환) 주식 산업별 분류 ==&lt;br /&gt;
&lt;br /&gt;
=== 🏗️ 소재 / 철강 / 금속 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분  |한국            |미국/글로벌          |&lt;br /&gt;
|----|--------------|----------------|&lt;br /&gt;
|철강  |POSCO홀딩스, 현대제철|Nucor, US Steel |&lt;br /&gt;
|비철금속|고려아연, 풍산      |Freeport-McMoRan|&lt;br /&gt;
|화학  |LG화학, 롯데케미칼   |BASF, Dow       |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== ⚓ 조선 / 해운 ===&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
|구분 |한국                  |글로벌        |&lt;br /&gt;
|---|--------------------|-----------|&lt;br /&gt;
|조선 |HD현대중공업, 삼성중공업, 한화오션|           |&lt;br /&gt;
|해운 |HMM, 장금상선           |Maersk, MSC|&lt;br /&gt;
|벌크선|                    |COSCO(중국)  |&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
🛢️ 에너지 / 정유&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|구분   |한국                 |미국                       |&lt;br /&gt;
|-----|-------------------|-------------------------|&lt;br /&gt;
|정유   |SK이노베이션, GS칼텍스(비상장)|ExxonMobil, Chevron      |&lt;br /&gt;
|유전서비스|                   |Schlumberger, Halliburton|&lt;br /&gt;
&lt;br /&gt;
🏠 건설 / 부동산&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|구분 |한국              |미국                 |&lt;br /&gt;
|---|----------------|-------------------|&lt;br /&gt;
|건설 |삼성물산, 현대건설, GS건설|Lennar, D.R. Horton|&lt;br /&gt;
|시멘트|쌍용C&amp;amp;E, 한일시멘트    |Martin Marietta    |&lt;br /&gt;
&lt;br /&gt;
🚗 자동차 / 부품&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|구분 |한국         |글로벌             |&lt;br /&gt;
|---|-----------|----------------|&lt;br /&gt;
|완성차|현대차, 기아    |GM, Ford, Toyota|&lt;br /&gt;
|부품 |현대모비스, HL만도|BorgWarner      |&lt;br /&gt;
&lt;br /&gt;
✈️ 항공 / 여행&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|구분    |한국        |미국           |&lt;br /&gt;
|------|----------|-------------|&lt;br /&gt;
|항공    |대한항공, 아시아나|Delta, United|&lt;br /&gt;
|호텔/카지노|파라다이스, GKL|MGM, Wynn    |&lt;br /&gt;
&lt;br /&gt;
🖥️ 반도체 / 디스플레이&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|구분   |한국          |미국    |&lt;br /&gt;
|-----|------------|------|&lt;br /&gt;
|메모리  |삼성전자, SK하이닉스|Micron|&lt;br /&gt;
|디스플레이|LG디스플레이     |      |&lt;br /&gt;
&lt;br /&gt;
🔋 배터리 / 소재&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|구분   |한국                  |글로벌      |&lt;br /&gt;
|-----|--------------------|---------|&lt;br /&gt;
|배터리  |삼성SDI, LG에너지솔루션, SK온|CATL(중국) |&lt;br /&gt;
|배터리소재|에코프로, 포스코퓨처엠        |Albemarle|&lt;br /&gt;
&lt;br /&gt;
📌 시클리컬 투자 핵심 포인트&lt;br /&gt;
경기 사이클과 매수 타이밍&lt;br /&gt;
	∙	바닥 매수 원칙 : 업황 최악일 때 → PBR 낮고 적자일 때가 매수 기회&lt;br /&gt;
	∙	PER 함정 주의 : 호황기엔 PER 낮아 보여도 이미 고점일 수 있음&lt;br /&gt;
	∙	재고 사이클 확인 필수 (반도체, 화학 특히 중요)&lt;br /&gt;
업종별 선행지표&lt;br /&gt;
	∙	철강/조선 → 발틱운임지수(BDI), 중국 PMI&lt;br /&gt;
	∙	반도체 → DRAM 현물가, 재고 수준&lt;br /&gt;
	∙	정유 → WTI/브렌트유 가격, 정제마진&lt;br /&gt;
	∙	화학 → 에틸렌 스프레드​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=2364</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=2364"/>
		<updated>2026-04-02T09:15:23Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 주식 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
{{#metadata:key=데브카페}}&lt;br /&gt;
{{#metadata:key=파이썬}}&lt;br /&gt;
{{#metadata:key=프로그램개발}}&lt;br /&gt;
{{#metadata:key=autoit}}&lt;br /&gt;
{{#metadata:key=미디어위키}}&lt;br /&gt;
&lt;br /&gt;
= Welcome to DEV CAFE =&lt;br /&gt;
* {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}})  , 이세상 모든 개발에 대한 이야기 {{SERVER}}&lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://devcafe.co.kr/w/images/devcafe_main3.jpeg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:python|파이썬]]: {{PAGESINCATEGORY: python}} 건 ,  [[:Category:미디어위키]] : {{PAGESINCATEGORY: 미디어위키}} 건 ,  [[:Category:autoit|오토잇]]: {{PAGESINCATEGORY: autoit}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건, [[:Category:음악]]: {{PAGESINCATEGORY: 음악}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
== IT/기술 ==&lt;br /&gt;
=== [[파이썬|파이썬 프로그래밍]] ===&lt;br /&gt;
==== 파이썬 설치 ====&lt;br /&gt;
# [[파이썬 설치]]&lt;br /&gt;
# [[Python 가상환경|파이썬 가상화]]&lt;br /&gt;
# [[pip 이용 파이썬 라이브러리 설치|파이썬 라이브러리 설치]]&lt;br /&gt;
# [[pip 이용 파이썬 라이브러리 설치#설치 패키지 추출|설치된 파이썬 라이브러리 추출하기]]&lt;br /&gt;
# [[Python whl 설치|.whl 파일 다운로드 설치법]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 기초 ====&lt;br /&gt;
{{알림&lt;br /&gt;
|내용=&#039;&#039;&#039;[[:category:Python|Python]] Foundation&#039;&#039;&#039; &lt;br /&gt;
# [[PYTHON 데이터형|파이썬 데이터형]]&lt;br /&gt;
# [[튜플 리스트 딕셔너리 차이점,특징,사용예시|튜플 리스트 딕셔너리]]&lt;br /&gt;
# [[파이썬 명명규칙]]&lt;br /&gt;
# [[파이썬 클래스]]&lt;br /&gt;
|아이콘이름=accessibility&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 고급 ====&lt;br /&gt;
:# [[파이썬 개발 추천기능 TOP10|파이썬 고급 기능 TOP 10]]&lt;br /&gt;
:# [[Python Cheat Sheet|파이썬 치트 쉬트 ]]&lt;br /&gt;
:# [[파이썬 config parser|파이썬 .ini 환경파일에 읽고쓰기]]&lt;br /&gt;
:# [[파이썬 로깅]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 응용 ====&lt;br /&gt;
# [[PyQt|파이썬 GUI 프로그램 개발]]&lt;br /&gt;
# [[Process memory share|쉐어 메모리 접근]]&lt;br /&gt;
# [[Python Cheat Sheet#Parallel Processing|병렬처리 프로세싱]]&lt;br /&gt;
# [[멀티프로세싱]]&lt;br /&gt;
# [[파이썬 스케줄러 모듈]]&lt;br /&gt;
# [[파이썬 장고]]&lt;br /&gt;
# [[flask]]&lt;br /&gt;
# [[Json 이용 키-값 추가 수정 삭제 방법|JSON 키값 수정/삭]]&lt;br /&gt;
# [[파이썬 mybatis xml sql 파싱 방법|XML 파싱(Mybatis)]]&lt;br /&gt;
# [[스케줄러 만들기]]&lt;br /&gt;
&lt;br /&gt;
==== 판다스(Pandas) 기초 ====&lt;br /&gt;
{{알림&lt;br /&gt;
|아이콘이름=apps &lt;br /&gt;
|내용= &#039;&#039;&#039;[[:category:pandas|PANDAS]] Foundation&#039;&#039;&#039;&lt;br /&gt;
# [[pandas|판다스(pandas) 기초]]&lt;br /&gt;
# [https://pandas.pydata.org/docs/ pandas 공식문서]&lt;br /&gt;
# [https://realpython.com/pandas-dataframe/ 리얼 판다스 튜토리얼]&lt;br /&gt;
# [https://www.geeksforgeeks.org/pandas-tutorial/ 그릭스 판다스 튜토리얼]&lt;br /&gt;
# [https://towardsdatascience.com/?gi=196f5e0ef243 투왈즈 튜터리얼]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== 판다스 응용 ====&lt;br /&gt;
# [[PANDAS 와 SQL 쿼리]]&lt;br /&gt;
# [[Pandas mysql insert]]&lt;br /&gt;
# [[Pandas 엑셀]]&lt;br /&gt;
&lt;br /&gt;
==== [[:category:streamlit|스트림릿 (Streamlit)]] 기초 ====&lt;br /&gt;
# [[Streamlit 기초]]&lt;br /&gt;
# [[Streamlit 개발 사이트|Streamlit 참고 사이트]]&lt;br /&gt;
# [[Streamlit Extention]]&lt;br /&gt;
# [[Streamlit 개발|Streamlit 프로그램]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 응용프로그램 개발 ====&lt;br /&gt;
# [[주식 자동매매 프로그램 개발]]&lt;br /&gt;
## [[대신증권 API 활용]]&lt;br /&gt;
## [[키움증권 api 매수매도|키움증권 API 활용]]&lt;br /&gt;
# [[개인 PC 텍스트 검색 채봇]]&lt;br /&gt;
## [[개인 PC 텍스트 검색 챗봇 웹 화면]]&lt;br /&gt;
# [[SQL 정리 툴|SQL Formater(정리)]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[:category:java|JAVA 프로그래밍]] ===&lt;br /&gt;
==== [[Java openjdk 설치|JAVA 설치]] ====&lt;br /&gt;
==== [[JAVA 기초]] ====&lt;br /&gt;
# [[JAVA 명명 규칙]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[:category:autoit|Autotit 프로그래밍]] ===&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|아이콘이름=adb&lt;br /&gt;
|내용 = &amp;lt;big&amp;gt;오토잇 프로그래밍 설치 및 기초 문법&amp;lt;/big&amp;gt;&lt;br /&gt;
# [[Auotit 설치]]&lt;br /&gt;
# [[Autoit 기초 문법]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== [[Autoit gui|Autoit GUI]] ===&lt;br /&gt;
:# [[Autoit GUI 컨트롤 스타일|GUI 컨트롤 스타일]]&lt;br /&gt;
:# [[Handle의 class instance 값 구하는 방법|GUI 컨트롤 핸들 찾기]]&lt;br /&gt;
:# [[Autoit 멀티 윈도우 컨트롤]]&lt;br /&gt;
:# [[리스트뷰(그리드) 생성]]&lt;br /&gt;
&lt;br /&gt;
=== [[Autoit 프로그램 개발]] ===&lt;br /&gt;
:# [[Autoit sqlite|SQLite 디비 연결]]&lt;br /&gt;
:# [[Autoit oracle connect|ORACLE 디비 연결]]&lt;br /&gt;
:# [[Autoit 키보드 후킹|키보드 후킹]]&lt;br /&gt;
:# [[Autoit 자동 업그레이드 프로그램 개발|자동 업데이트 프로그램 개발]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[에디터 활용]] ===&lt;br /&gt;
==== [[:category:vscode|VS 코드(VS Code)]] ====&lt;br /&gt;
# [[Vscode 단축키]]&lt;br /&gt;
&lt;br /&gt;
==== [[:Category:서브라임|서브라임 (sublime)]] ====&lt;br /&gt;
# [[서브라임 sublime 단축키|Sublime 단축키]]&lt;br /&gt;
# [[Sublime plugin 생성 방법|Sublime 플러그인 만들기]]&lt;br /&gt;
# [[sublime emmet]]&lt;br /&gt;
==== [[Vi 명령어|vi 편집기]] ====&lt;br /&gt;
# [[Vi mode - ksh bash zsh]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 미디어위키 사용법 ==&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|아이콘이름=school&lt;br /&gt;
|내용= &#039;&#039;&#039;미디어 위키 초보자 가이드&#039;&#039;&#039; &lt;br /&gt;
# [[미디어위키 기본 사용법]] &lt;br /&gt;
# [[환경세팅 파일|환경설정(LocalSettings.php)]]&lt;br /&gt;
# [[스킨(테마) 추가/변경]]&lt;br /&gt;
# [[미디어위키 확장 기능]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== [[미디어위키 예약어 와 변수]] ===&lt;br /&gt;
==== [[미디어위키 예약어 와 변수#동작어_(Behavior_Switches)|미디어위키 동작 예약어]] ====&lt;br /&gt;
==== [[미디어위키 예약어 와 변수#변수_(Variables)|미디어위키 변수 예약어]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[미디어위키 기본 사용법]] ===&lt;br /&gt;
* [[미디어위키 기본 사용법#새글 쓰기|새글 쓰기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#대문 수정|첫페이지(대문) 수정]]&lt;br /&gt;
* [[미디어위키 기본 사용법#문단(Section)|목차 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#표(wiki table) 만들기|표 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#틀(Template) 만들기|틀 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#양식(Form) 만들기|폼 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#카테고리 만들기|카테고리 만들기]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 미디어 위키 환경설정 ===&lt;br /&gt;
* [[스킨(테마) 추가/변경]]&lt;br /&gt;
* [[미디어위키 기본 사용법#대문 수정하기|대문 타이틀 글씨 숨기기]]&lt;br /&gt;
* [[특수 명령어]]&lt;br /&gt;
* [[환경세팅 파일|환경세팅 파일(LocalSettings.php)]]&lt;br /&gt;
* [[나눔고딕 웹폰트 설정하기]]&lt;br /&gt;
* [[반응형 이미지 사이즈 조절 방법]]&lt;br /&gt;
* [[스니펫-목차에 순번 붙이기]]&lt;br /&gt;
&lt;br /&gt;
=== 미디어위키 확장 프로그램 가이드===&lt;br /&gt;
==== 페이지 관련 ====&lt;br /&gt;
* [[Syntax Highlight|문법 하이라이트]]&lt;br /&gt;
* [[목차에 순번 붙이기|목차에 순번 붙이기(확장기능 설치)]]&lt;br /&gt;
** [[스니펫-목차에 순번 붙이기 |목차에 순번 붙이기(JS/CSS 이용)]] / [https://www.mediawiki.org/wiki/Manual:Table_of_contents#Auto-numbering 원본페이지 이동]&lt;br /&gt;
* [[페이지에 탭 만들기]]&lt;br /&gt;
* [[관련글 보기 기능 추가 ]]&lt;br /&gt;
* [[방문자 수 보기 기능 추가]]&lt;br /&gt;
* [[문서 목차 불러오기 기능 추가]] &lt;br /&gt;
* [[댓글 기능 추가]]&lt;br /&gt;
* [[미디어위키 유튜브 삽입|유튜브 삽입]]&lt;br /&gt;
==== 로그인 ====&lt;br /&gt;
* [[구글인증 로그인]]&lt;br /&gt;
&lt;br /&gt;
==== 검색 관련 ====&lt;br /&gt;
* [[시맨틱 미디어위키 설치]]&lt;br /&gt;
* [[시맨틱 이용한 태그 달기]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 3D 모델링 ==&lt;br /&gt;
=== [[초보자를 위한 모델링 프로그램 추천]]===&lt;br /&gt;
==== [[Blender 초보자 따라하기 ]] ====&lt;br /&gt;
==== [[Blender로 마우스 모델링]]====&lt;br /&gt;
==== [[Blender 버티컬 마우스 모델링]]====&lt;br /&gt;
=== [[mashmixer 모델링 편집틀 사용법]]===&lt;br /&gt;
=== 3D 프린터 ===&lt;br /&gt;
==== [[3D 프린터 추천 TOP 5]] ====&lt;br /&gt;
====[[3D 프린터 필라멘트 장단점 PLA PETG|3D 프린터 필라멘트 종류 와 장단점]]====&lt;br /&gt;
&lt;br /&gt;
==== [[뱀부랩 스튜디오 튜토리얼]] ====&lt;br /&gt;
==== [[3D 프린터로 메모 프린터 만들기|3D 프린터로 라인 프린터 만들기]] ====&lt;br /&gt;
==== [[3D 프린터 출력 제품 만족도 평가|3D프린터로 만든 제품 추천]] ====&lt;br /&gt;
==== [[3D 포르쉐911 RC카 만들기]] ====&lt;br /&gt;
&lt;br /&gt;
== 재테크 ==&lt;br /&gt;
=== 부동산 ===&lt;br /&gt;
=== 주식 ===&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|내용 = &amp;lt;big&amp;gt;주식 이야기&amp;lt;/big&amp;gt;&lt;br /&gt;
# [[주식 용어]]&lt;br /&gt;
# [[주식거래시간|주식거래시간(한국/미국/중국)]]&lt;br /&gt;
# [[주식 수수료]]&lt;br /&gt;
# [[주식 금리|금리가 오를때/내릴때 주식 투자법]]&lt;br /&gt;
# [[신호등 분석법]]&lt;br /&gt;
# [[미국 배당주 추천|미국 추천 배당주 ]]&lt;br /&gt;
}}&lt;br /&gt;
* [[주식 금액별 호가 단위]]&lt;br /&gt;
* [[주식 시클리컬 가치주 투자법|시클리컬 가치주 투자법]]&lt;br /&gt;
** [[시클리컬 주요 산업별 대장주]]&lt;br /&gt;
* [[주식 테마주]]&lt;br /&gt;
==== 절세 계좌 투자 ====&lt;br /&gt;
# [[Isa 개인종합자산관리계좌|ISA 개인종합 계좌]] &lt;br /&gt;
# [[IRP 개인퇴직연금 계좌]] &lt;br /&gt;
# [[IRP 개인퇴직연금 계좌#IRP계좌 와 연금저축계좌의 차이|연금저축 계좌]]&lt;br /&gt;
# [[IRP ISA 연금저축펀트 최대 투자 금액|ISA IRP 연금저축펀드 비교 ]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 인물 ==&lt;br /&gt;
=== 근대사 ===&lt;br /&gt;
* [[노무현 명연설]]&lt;br /&gt;
* [[인간적인 국회의원 노회찬]]&lt;br /&gt;
* [[나의 스승 이어령]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 취미 ==&lt;br /&gt;
=== [[수영]] ===&lt;br /&gt;
== 휴식 ==&lt;br /&gt;
=== 휴양지 ===&lt;br /&gt;
==== [[서울_노원구_휴양림|서울_노원구_휴양림-&#039;&#039;&#039;수락 휴&#039;&#039;&#039;]]====&lt;br /&gt;
=== 물놀이/스파 ===&lt;br /&gt;
==== [[영종도 파라다이스 시티]] ====&lt;br /&gt;
==== [[경기도 계곡 추천]]====&lt;br /&gt;
==== [[가평 계곡 추천]] ====&lt;br /&gt;
==== [[전라도 계곡 추천]]====&lt;br /&gt;
==== [[경상도 계곡 추천]] ====&lt;br /&gt;
&lt;br /&gt;
=== 여행지 ===&lt;br /&gt;
=== 맛집 ===&lt;br /&gt;
==== [[인생 맛집 경기/서울]] ====&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 음악 ==&lt;br /&gt;
=== 클래식 ===&lt;br /&gt;
* [[구스타프 말러]]&lt;br /&gt;
&lt;br /&gt;
=== 레젼드 ===&lt;br /&gt;
* [[존바이즈 베스트]]&lt;br /&gt;
* [[유키 구라모토]]&lt;br /&gt;
* [[조지 윈스턴]]&lt;br /&gt;
* [[김광석 베스트]]&lt;br /&gt;
* [[신해철 베스트]]&lt;br /&gt;
* [[김건모 베스트]]&lt;br /&gt;
* [[박혜경 베스트]]&lt;br /&gt;
* [[김필 베스트]]&lt;br /&gt;
&lt;br /&gt;
=== 기타 연습 ===&lt;br /&gt;
* [[메탈리카 기타 연습]]&lt;br /&gt;
* [[캐논 락버전]]&lt;br /&gt;
* [[smells like teen spirit - nirvana|Smells Teen Split-너바나 ]]&lt;br /&gt;
* [[Lover&#039;s concerto]]&lt;br /&gt;
[[category:devcafe]] &lt;br /&gt;
[[category:python]] &lt;br /&gt;
[[category:미디어위키]]&lt;br /&gt;
[[category:autoit]]&lt;br /&gt;
[[category:인물]]&lt;br /&gt;
[[category:역사]]&lt;br /&gt;
[[category:음악]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=2363</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=2363"/>
		<updated>2026-04-02T09:14:59Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 주식 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
{{#metadata:key=데브카페}}&lt;br /&gt;
{{#metadata:key=파이썬}}&lt;br /&gt;
{{#metadata:key=프로그램개발}}&lt;br /&gt;
{{#metadata:key=autoit}}&lt;br /&gt;
{{#metadata:key=미디어위키}}&lt;br /&gt;
&lt;br /&gt;
= Welcome to DEV CAFE =&lt;br /&gt;
* {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}})  , 이세상 모든 개발에 대한 이야기 {{SERVER}}&lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://devcafe.co.kr/w/images/devcafe_main3.jpeg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:python|파이썬]]: {{PAGESINCATEGORY: python}} 건 ,  [[:Category:미디어위키]] : {{PAGESINCATEGORY: 미디어위키}} 건 ,  [[:Category:autoit|오토잇]]: {{PAGESINCATEGORY: autoit}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건, [[:Category:음악]]: {{PAGESINCATEGORY: 음악}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
== IT/기술 ==&lt;br /&gt;
=== [[파이썬|파이썬 프로그래밍]] ===&lt;br /&gt;
==== 파이썬 설치 ====&lt;br /&gt;
# [[파이썬 설치]]&lt;br /&gt;
# [[Python 가상환경|파이썬 가상화]]&lt;br /&gt;
# [[pip 이용 파이썬 라이브러리 설치|파이썬 라이브러리 설치]]&lt;br /&gt;
# [[pip 이용 파이썬 라이브러리 설치#설치 패키지 추출|설치된 파이썬 라이브러리 추출하기]]&lt;br /&gt;
# [[Python whl 설치|.whl 파일 다운로드 설치법]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 기초 ====&lt;br /&gt;
{{알림&lt;br /&gt;
|내용=&#039;&#039;&#039;[[:category:Python|Python]] Foundation&#039;&#039;&#039; &lt;br /&gt;
# [[PYTHON 데이터형|파이썬 데이터형]]&lt;br /&gt;
# [[튜플 리스트 딕셔너리 차이점,특징,사용예시|튜플 리스트 딕셔너리]]&lt;br /&gt;
# [[파이썬 명명규칙]]&lt;br /&gt;
# [[파이썬 클래스]]&lt;br /&gt;
|아이콘이름=accessibility&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 고급 ====&lt;br /&gt;
:# [[파이썬 개발 추천기능 TOP10|파이썬 고급 기능 TOP 10]]&lt;br /&gt;
:# [[Python Cheat Sheet|파이썬 치트 쉬트 ]]&lt;br /&gt;
:# [[파이썬 config parser|파이썬 .ini 환경파일에 읽고쓰기]]&lt;br /&gt;
:# [[파이썬 로깅]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 응용 ====&lt;br /&gt;
# [[PyQt|파이썬 GUI 프로그램 개발]]&lt;br /&gt;
# [[Process memory share|쉐어 메모리 접근]]&lt;br /&gt;
# [[Python Cheat Sheet#Parallel Processing|병렬처리 프로세싱]]&lt;br /&gt;
# [[멀티프로세싱]]&lt;br /&gt;
# [[파이썬 스케줄러 모듈]]&lt;br /&gt;
# [[파이썬 장고]]&lt;br /&gt;
# [[flask]]&lt;br /&gt;
# [[Json 이용 키-값 추가 수정 삭제 방법|JSON 키값 수정/삭]]&lt;br /&gt;
# [[파이썬 mybatis xml sql 파싱 방법|XML 파싱(Mybatis)]]&lt;br /&gt;
# [[스케줄러 만들기]]&lt;br /&gt;
&lt;br /&gt;
==== 판다스(Pandas) 기초 ====&lt;br /&gt;
{{알림&lt;br /&gt;
|아이콘이름=apps &lt;br /&gt;
|내용= &#039;&#039;&#039;[[:category:pandas|PANDAS]] Foundation&#039;&#039;&#039;&lt;br /&gt;
# [[pandas|판다스(pandas) 기초]]&lt;br /&gt;
# [https://pandas.pydata.org/docs/ pandas 공식문서]&lt;br /&gt;
# [https://realpython.com/pandas-dataframe/ 리얼 판다스 튜토리얼]&lt;br /&gt;
# [https://www.geeksforgeeks.org/pandas-tutorial/ 그릭스 판다스 튜토리얼]&lt;br /&gt;
# [https://towardsdatascience.com/?gi=196f5e0ef243 투왈즈 튜터리얼]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== 판다스 응용 ====&lt;br /&gt;
# [[PANDAS 와 SQL 쿼리]]&lt;br /&gt;
# [[Pandas mysql insert]]&lt;br /&gt;
# [[Pandas 엑셀]]&lt;br /&gt;
&lt;br /&gt;
==== [[:category:streamlit|스트림릿 (Streamlit)]] 기초 ====&lt;br /&gt;
# [[Streamlit 기초]]&lt;br /&gt;
# [[Streamlit 개발 사이트|Streamlit 참고 사이트]]&lt;br /&gt;
# [[Streamlit Extention]]&lt;br /&gt;
# [[Streamlit 개발|Streamlit 프로그램]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 응용프로그램 개발 ====&lt;br /&gt;
# [[주식 자동매매 프로그램 개발]]&lt;br /&gt;
## [[대신증권 API 활용]]&lt;br /&gt;
## [[키움증권 api 매수매도|키움증권 API 활용]]&lt;br /&gt;
# [[개인 PC 텍스트 검색 채봇]]&lt;br /&gt;
## [[개인 PC 텍스트 검색 챗봇 웹 화면]]&lt;br /&gt;
# [[SQL 정리 툴|SQL Formater(정리)]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[:category:java|JAVA 프로그래밍]] ===&lt;br /&gt;
==== [[Java openjdk 설치|JAVA 설치]] ====&lt;br /&gt;
==== [[JAVA 기초]] ====&lt;br /&gt;
# [[JAVA 명명 규칙]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[:category:autoit|Autotit 프로그래밍]] ===&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|아이콘이름=adb&lt;br /&gt;
|내용 = &amp;lt;big&amp;gt;오토잇 프로그래밍 설치 및 기초 문법&amp;lt;/big&amp;gt;&lt;br /&gt;
# [[Auotit 설치]]&lt;br /&gt;
# [[Autoit 기초 문법]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== [[Autoit gui|Autoit GUI]] ===&lt;br /&gt;
:# [[Autoit GUI 컨트롤 스타일|GUI 컨트롤 스타일]]&lt;br /&gt;
:# [[Handle의 class instance 값 구하는 방법|GUI 컨트롤 핸들 찾기]]&lt;br /&gt;
:# [[Autoit 멀티 윈도우 컨트롤]]&lt;br /&gt;
:# [[리스트뷰(그리드) 생성]]&lt;br /&gt;
&lt;br /&gt;
=== [[Autoit 프로그램 개발]] ===&lt;br /&gt;
:# [[Autoit sqlite|SQLite 디비 연결]]&lt;br /&gt;
:# [[Autoit oracle connect|ORACLE 디비 연결]]&lt;br /&gt;
:# [[Autoit 키보드 후킹|키보드 후킹]]&lt;br /&gt;
:# [[Autoit 자동 업그레이드 프로그램 개발|자동 업데이트 프로그램 개발]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[에디터 활용]] ===&lt;br /&gt;
==== [[:category:vscode|VS 코드(VS Code)]] ====&lt;br /&gt;
# [[Vscode 단축키]]&lt;br /&gt;
&lt;br /&gt;
==== [[:Category:서브라임|서브라임 (sublime)]] ====&lt;br /&gt;
# [[서브라임 sublime 단축키|Sublime 단축키]]&lt;br /&gt;
# [[Sublime plugin 생성 방법|Sublime 플러그인 만들기]]&lt;br /&gt;
# [[sublime emmet]]&lt;br /&gt;
==== [[Vi 명령어|vi 편집기]] ====&lt;br /&gt;
# [[Vi mode - ksh bash zsh]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 미디어위키 사용법 ==&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|아이콘이름=school&lt;br /&gt;
|내용= &#039;&#039;&#039;미디어 위키 초보자 가이드&#039;&#039;&#039; &lt;br /&gt;
# [[미디어위키 기본 사용법]] &lt;br /&gt;
# [[환경세팅 파일|환경설정(LocalSettings.php)]]&lt;br /&gt;
# [[스킨(테마) 추가/변경]]&lt;br /&gt;
# [[미디어위키 확장 기능]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== [[미디어위키 예약어 와 변수]] ===&lt;br /&gt;
==== [[미디어위키 예약어 와 변수#동작어_(Behavior_Switches)|미디어위키 동작 예약어]] ====&lt;br /&gt;
==== [[미디어위키 예약어 와 변수#변수_(Variables)|미디어위키 변수 예약어]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[미디어위키 기본 사용법]] ===&lt;br /&gt;
* [[미디어위키 기본 사용법#새글 쓰기|새글 쓰기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#대문 수정|첫페이지(대문) 수정]]&lt;br /&gt;
* [[미디어위키 기본 사용법#문단(Section)|목차 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#표(wiki table) 만들기|표 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#틀(Template) 만들기|틀 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#양식(Form) 만들기|폼 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#카테고리 만들기|카테고리 만들기]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 미디어 위키 환경설정 ===&lt;br /&gt;
* [[스킨(테마) 추가/변경]]&lt;br /&gt;
* [[미디어위키 기본 사용법#대문 수정하기|대문 타이틀 글씨 숨기기]]&lt;br /&gt;
* [[특수 명령어]]&lt;br /&gt;
* [[환경세팅 파일|환경세팅 파일(LocalSettings.php)]]&lt;br /&gt;
* [[나눔고딕 웹폰트 설정하기]]&lt;br /&gt;
* [[반응형 이미지 사이즈 조절 방법]]&lt;br /&gt;
* [[스니펫-목차에 순번 붙이기]]&lt;br /&gt;
&lt;br /&gt;
=== 미디어위키 확장 프로그램 가이드===&lt;br /&gt;
==== 페이지 관련 ====&lt;br /&gt;
* [[Syntax Highlight|문법 하이라이트]]&lt;br /&gt;
* [[목차에 순번 붙이기|목차에 순번 붙이기(확장기능 설치)]]&lt;br /&gt;
** [[스니펫-목차에 순번 붙이기 |목차에 순번 붙이기(JS/CSS 이용)]] / [https://www.mediawiki.org/wiki/Manual:Table_of_contents#Auto-numbering 원본페이지 이동]&lt;br /&gt;
* [[페이지에 탭 만들기]]&lt;br /&gt;
* [[관련글 보기 기능 추가 ]]&lt;br /&gt;
* [[방문자 수 보기 기능 추가]]&lt;br /&gt;
* [[문서 목차 불러오기 기능 추가]] &lt;br /&gt;
* [[댓글 기능 추가]]&lt;br /&gt;
* [[미디어위키 유튜브 삽입|유튜브 삽입]]&lt;br /&gt;
==== 로그인 ====&lt;br /&gt;
* [[구글인증 로그인]]&lt;br /&gt;
&lt;br /&gt;
==== 검색 관련 ====&lt;br /&gt;
* [[시맨틱 미디어위키 설치]]&lt;br /&gt;
* [[시맨틱 이용한 태그 달기]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 3D 모델링 ==&lt;br /&gt;
=== [[초보자를 위한 모델링 프로그램 추천]]===&lt;br /&gt;
==== [[Blender 초보자 따라하기 ]] ====&lt;br /&gt;
==== [[Blender로 마우스 모델링]]====&lt;br /&gt;
==== [[Blender 버티컬 마우스 모델링]]====&lt;br /&gt;
=== [[mashmixer 모델링 편집틀 사용법]]===&lt;br /&gt;
=== 3D 프린터 ===&lt;br /&gt;
==== [[3D 프린터 추천 TOP 5]] ====&lt;br /&gt;
====[[3D 프린터 필라멘트 장단점 PLA PETG|3D 프린터 필라멘트 종류 와 장단점]]====&lt;br /&gt;
&lt;br /&gt;
==== [[뱀부랩 스튜디오 튜토리얼]] ====&lt;br /&gt;
==== [[3D 프린터로 메모 프린터 만들기|3D 프린터로 라인 프린터 만들기]] ====&lt;br /&gt;
==== [[3D 프린터 출력 제품 만족도 평가|3D프린터로 만든 제품 추천]] ====&lt;br /&gt;
==== [[3D 포르쉐911 RC카 만들기]] ====&lt;br /&gt;
&lt;br /&gt;
== 재테크 ==&lt;br /&gt;
=== 부동산 ===&lt;br /&gt;
=== 주식 ===&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|내용 = &amp;lt;big&amp;gt;주식 이야기&amp;lt;/big&amp;gt;&lt;br /&gt;
# [[주식 용어]]&lt;br /&gt;
# [[주식거래시간|주식거래시간(한국/미국/중국)]]&lt;br /&gt;
# [[주식 수수료]]&lt;br /&gt;
# [[주식 금리|금리가 오를때/내릴때 주식 투자법]]&lt;br /&gt;
# [[신호등 분석법]]&lt;br /&gt;
# [[미국 배당주 추천|미국 추천 배당주 ]]&lt;br /&gt;
}}&lt;br /&gt;
* [[주식 금액별 호가 단위]]&lt;br /&gt;
* [[주식 시클리컬 가치주 투자법|시클리컬 가치주 투자법]]&lt;br /&gt;
## [[시클리컬 주요 산업별 대장주]]&lt;br /&gt;
* [[주식 테마주]]&lt;br /&gt;
==== 절세 계좌 투자 ====&lt;br /&gt;
# [[Isa 개인종합자산관리계좌|ISA 개인종합 계좌]] &lt;br /&gt;
# [[IRP 개인퇴직연금 계좌]] &lt;br /&gt;
# [[IRP 개인퇴직연금 계좌#IRP계좌 와 연금저축계좌의 차이|연금저축 계좌]]&lt;br /&gt;
# [[IRP ISA 연금저축펀트 최대 투자 금액|ISA IRP 연금저축펀드 비교 ]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 인물 ==&lt;br /&gt;
=== 근대사 ===&lt;br /&gt;
* [[노무현 명연설]]&lt;br /&gt;
* [[인간적인 국회의원 노회찬]]&lt;br /&gt;
* [[나의 스승 이어령]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 취미 ==&lt;br /&gt;
=== [[수영]] ===&lt;br /&gt;
== 휴식 ==&lt;br /&gt;
=== 휴양지 ===&lt;br /&gt;
==== [[서울_노원구_휴양림|서울_노원구_휴양림-&#039;&#039;&#039;수락 휴&#039;&#039;&#039;]]====&lt;br /&gt;
=== 물놀이/스파 ===&lt;br /&gt;
==== [[영종도 파라다이스 시티]] ====&lt;br /&gt;
==== [[경기도 계곡 추천]]====&lt;br /&gt;
==== [[가평 계곡 추천]] ====&lt;br /&gt;
==== [[전라도 계곡 추천]]====&lt;br /&gt;
==== [[경상도 계곡 추천]] ====&lt;br /&gt;
&lt;br /&gt;
=== 여행지 ===&lt;br /&gt;
=== 맛집 ===&lt;br /&gt;
==== [[인생 맛집 경기/서울]] ====&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 음악 ==&lt;br /&gt;
=== 클래식 ===&lt;br /&gt;
* [[구스타프 말러]]&lt;br /&gt;
&lt;br /&gt;
=== 레젼드 ===&lt;br /&gt;
* [[존바이즈 베스트]]&lt;br /&gt;
* [[유키 구라모토]]&lt;br /&gt;
* [[조지 윈스턴]]&lt;br /&gt;
* [[김광석 베스트]]&lt;br /&gt;
* [[신해철 베스트]]&lt;br /&gt;
* [[김건모 베스트]]&lt;br /&gt;
* [[박혜경 베스트]]&lt;br /&gt;
* [[김필 베스트]]&lt;br /&gt;
&lt;br /&gt;
=== 기타 연습 ===&lt;br /&gt;
* [[메탈리카 기타 연습]]&lt;br /&gt;
* [[캐논 락버전]]&lt;br /&gt;
* [[smells like teen spirit - nirvana|Smells Teen Split-너바나 ]]&lt;br /&gt;
* [[Lover&#039;s concerto]]&lt;br /&gt;
[[category:devcafe]] &lt;br /&gt;
[[category:python]] &lt;br /&gt;
[[category:미디어위키]]&lt;br /&gt;
[[category:autoit]]&lt;br /&gt;
[[category:인물]]&lt;br /&gt;
[[category:역사]]&lt;br /&gt;
[[category:음악]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=2362</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=2362"/>
		<updated>2026-04-02T09:14:42Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 주식 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
{{#metadata:key=데브카페}}&lt;br /&gt;
{{#metadata:key=파이썬}}&lt;br /&gt;
{{#metadata:key=프로그램개발}}&lt;br /&gt;
{{#metadata:key=autoit}}&lt;br /&gt;
{{#metadata:key=미디어위키}}&lt;br /&gt;
&lt;br /&gt;
= Welcome to DEV CAFE =&lt;br /&gt;
* {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}})  , 이세상 모든 개발에 대한 이야기 {{SERVER}}&lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://devcafe.co.kr/w/images/devcafe_main3.jpeg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:python|파이썬]]: {{PAGESINCATEGORY: python}} 건 ,  [[:Category:미디어위키]] : {{PAGESINCATEGORY: 미디어위키}} 건 ,  [[:Category:autoit|오토잇]]: {{PAGESINCATEGORY: autoit}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건, [[:Category:음악]]: {{PAGESINCATEGORY: 음악}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
== IT/기술 ==&lt;br /&gt;
=== [[파이썬|파이썬 프로그래밍]] ===&lt;br /&gt;
==== 파이썬 설치 ====&lt;br /&gt;
# [[파이썬 설치]]&lt;br /&gt;
# [[Python 가상환경|파이썬 가상화]]&lt;br /&gt;
# [[pip 이용 파이썬 라이브러리 설치|파이썬 라이브러리 설치]]&lt;br /&gt;
# [[pip 이용 파이썬 라이브러리 설치#설치 패키지 추출|설치된 파이썬 라이브러리 추출하기]]&lt;br /&gt;
# [[Python whl 설치|.whl 파일 다운로드 설치법]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 기초 ====&lt;br /&gt;
{{알림&lt;br /&gt;
|내용=&#039;&#039;&#039;[[:category:Python|Python]] Foundation&#039;&#039;&#039; &lt;br /&gt;
# [[PYTHON 데이터형|파이썬 데이터형]]&lt;br /&gt;
# [[튜플 리스트 딕셔너리 차이점,특징,사용예시|튜플 리스트 딕셔너리]]&lt;br /&gt;
# [[파이썬 명명규칙]]&lt;br /&gt;
# [[파이썬 클래스]]&lt;br /&gt;
|아이콘이름=accessibility&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 고급 ====&lt;br /&gt;
:# [[파이썬 개발 추천기능 TOP10|파이썬 고급 기능 TOP 10]]&lt;br /&gt;
:# [[Python Cheat Sheet|파이썬 치트 쉬트 ]]&lt;br /&gt;
:# [[파이썬 config parser|파이썬 .ini 환경파일에 읽고쓰기]]&lt;br /&gt;
:# [[파이썬 로깅]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 응용 ====&lt;br /&gt;
# [[PyQt|파이썬 GUI 프로그램 개발]]&lt;br /&gt;
# [[Process memory share|쉐어 메모리 접근]]&lt;br /&gt;
# [[Python Cheat Sheet#Parallel Processing|병렬처리 프로세싱]]&lt;br /&gt;
# [[멀티프로세싱]]&lt;br /&gt;
# [[파이썬 스케줄러 모듈]]&lt;br /&gt;
# [[파이썬 장고]]&lt;br /&gt;
# [[flask]]&lt;br /&gt;
# [[Json 이용 키-값 추가 수정 삭제 방법|JSON 키값 수정/삭]]&lt;br /&gt;
# [[파이썬 mybatis xml sql 파싱 방법|XML 파싱(Mybatis)]]&lt;br /&gt;
# [[스케줄러 만들기]]&lt;br /&gt;
&lt;br /&gt;
==== 판다스(Pandas) 기초 ====&lt;br /&gt;
{{알림&lt;br /&gt;
|아이콘이름=apps &lt;br /&gt;
|내용= &#039;&#039;&#039;[[:category:pandas|PANDAS]] Foundation&#039;&#039;&#039;&lt;br /&gt;
# [[pandas|판다스(pandas) 기초]]&lt;br /&gt;
# [https://pandas.pydata.org/docs/ pandas 공식문서]&lt;br /&gt;
# [https://realpython.com/pandas-dataframe/ 리얼 판다스 튜토리얼]&lt;br /&gt;
# [https://www.geeksforgeeks.org/pandas-tutorial/ 그릭스 판다스 튜토리얼]&lt;br /&gt;
# [https://towardsdatascience.com/?gi=196f5e0ef243 투왈즈 튜터리얼]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== 판다스 응용 ====&lt;br /&gt;
# [[PANDAS 와 SQL 쿼리]]&lt;br /&gt;
# [[Pandas mysql insert]]&lt;br /&gt;
# [[Pandas 엑셀]]&lt;br /&gt;
&lt;br /&gt;
==== [[:category:streamlit|스트림릿 (Streamlit)]] 기초 ====&lt;br /&gt;
# [[Streamlit 기초]]&lt;br /&gt;
# [[Streamlit 개발 사이트|Streamlit 참고 사이트]]&lt;br /&gt;
# [[Streamlit Extention]]&lt;br /&gt;
# [[Streamlit 개발|Streamlit 프로그램]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 응용프로그램 개발 ====&lt;br /&gt;
# [[주식 자동매매 프로그램 개발]]&lt;br /&gt;
## [[대신증권 API 활용]]&lt;br /&gt;
## [[키움증권 api 매수매도|키움증권 API 활용]]&lt;br /&gt;
# [[개인 PC 텍스트 검색 채봇]]&lt;br /&gt;
## [[개인 PC 텍스트 검색 챗봇 웹 화면]]&lt;br /&gt;
# [[SQL 정리 툴|SQL Formater(정리)]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[:category:java|JAVA 프로그래밍]] ===&lt;br /&gt;
==== [[Java openjdk 설치|JAVA 설치]] ====&lt;br /&gt;
==== [[JAVA 기초]] ====&lt;br /&gt;
# [[JAVA 명명 규칙]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[:category:autoit|Autotit 프로그래밍]] ===&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|아이콘이름=adb&lt;br /&gt;
|내용 = &amp;lt;big&amp;gt;오토잇 프로그래밍 설치 및 기초 문법&amp;lt;/big&amp;gt;&lt;br /&gt;
# [[Auotit 설치]]&lt;br /&gt;
# [[Autoit 기초 문법]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== [[Autoit gui|Autoit GUI]] ===&lt;br /&gt;
:# [[Autoit GUI 컨트롤 스타일|GUI 컨트롤 스타일]]&lt;br /&gt;
:# [[Handle의 class instance 값 구하는 방법|GUI 컨트롤 핸들 찾기]]&lt;br /&gt;
:# [[Autoit 멀티 윈도우 컨트롤]]&lt;br /&gt;
:# [[리스트뷰(그리드) 생성]]&lt;br /&gt;
&lt;br /&gt;
=== [[Autoit 프로그램 개발]] ===&lt;br /&gt;
:# [[Autoit sqlite|SQLite 디비 연결]]&lt;br /&gt;
:# [[Autoit oracle connect|ORACLE 디비 연결]]&lt;br /&gt;
:# [[Autoit 키보드 후킹|키보드 후킹]]&lt;br /&gt;
:# [[Autoit 자동 업그레이드 프로그램 개발|자동 업데이트 프로그램 개발]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[에디터 활용]] ===&lt;br /&gt;
==== [[:category:vscode|VS 코드(VS Code)]] ====&lt;br /&gt;
# [[Vscode 단축키]]&lt;br /&gt;
&lt;br /&gt;
==== [[:Category:서브라임|서브라임 (sublime)]] ====&lt;br /&gt;
# [[서브라임 sublime 단축키|Sublime 단축키]]&lt;br /&gt;
# [[Sublime plugin 생성 방법|Sublime 플러그인 만들기]]&lt;br /&gt;
# [[sublime emmet]]&lt;br /&gt;
==== [[Vi 명령어|vi 편집기]] ====&lt;br /&gt;
# [[Vi mode - ksh bash zsh]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 미디어위키 사용법 ==&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|아이콘이름=school&lt;br /&gt;
|내용= &#039;&#039;&#039;미디어 위키 초보자 가이드&#039;&#039;&#039; &lt;br /&gt;
# [[미디어위키 기본 사용법]] &lt;br /&gt;
# [[환경세팅 파일|환경설정(LocalSettings.php)]]&lt;br /&gt;
# [[스킨(테마) 추가/변경]]&lt;br /&gt;
# [[미디어위키 확장 기능]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== [[미디어위키 예약어 와 변수]] ===&lt;br /&gt;
==== [[미디어위키 예약어 와 변수#동작어_(Behavior_Switches)|미디어위키 동작 예약어]] ====&lt;br /&gt;
==== [[미디어위키 예약어 와 변수#변수_(Variables)|미디어위키 변수 예약어]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[미디어위키 기본 사용법]] ===&lt;br /&gt;
* [[미디어위키 기본 사용법#새글 쓰기|새글 쓰기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#대문 수정|첫페이지(대문) 수정]]&lt;br /&gt;
* [[미디어위키 기본 사용법#문단(Section)|목차 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#표(wiki table) 만들기|표 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#틀(Template) 만들기|틀 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#양식(Form) 만들기|폼 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#카테고리 만들기|카테고리 만들기]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 미디어 위키 환경설정 ===&lt;br /&gt;
* [[스킨(테마) 추가/변경]]&lt;br /&gt;
* [[미디어위키 기본 사용법#대문 수정하기|대문 타이틀 글씨 숨기기]]&lt;br /&gt;
* [[특수 명령어]]&lt;br /&gt;
* [[환경세팅 파일|환경세팅 파일(LocalSettings.php)]]&lt;br /&gt;
* [[나눔고딕 웹폰트 설정하기]]&lt;br /&gt;
* [[반응형 이미지 사이즈 조절 방법]]&lt;br /&gt;
* [[스니펫-목차에 순번 붙이기]]&lt;br /&gt;
&lt;br /&gt;
=== 미디어위키 확장 프로그램 가이드===&lt;br /&gt;
==== 페이지 관련 ====&lt;br /&gt;
* [[Syntax Highlight|문법 하이라이트]]&lt;br /&gt;
* [[목차에 순번 붙이기|목차에 순번 붙이기(확장기능 설치)]]&lt;br /&gt;
** [[스니펫-목차에 순번 붙이기 |목차에 순번 붙이기(JS/CSS 이용)]] / [https://www.mediawiki.org/wiki/Manual:Table_of_contents#Auto-numbering 원본페이지 이동]&lt;br /&gt;
* [[페이지에 탭 만들기]]&lt;br /&gt;
* [[관련글 보기 기능 추가 ]]&lt;br /&gt;
* [[방문자 수 보기 기능 추가]]&lt;br /&gt;
* [[문서 목차 불러오기 기능 추가]] &lt;br /&gt;
* [[댓글 기능 추가]]&lt;br /&gt;
* [[미디어위키 유튜브 삽입|유튜브 삽입]]&lt;br /&gt;
==== 로그인 ====&lt;br /&gt;
* [[구글인증 로그인]]&lt;br /&gt;
&lt;br /&gt;
==== 검색 관련 ====&lt;br /&gt;
* [[시맨틱 미디어위키 설치]]&lt;br /&gt;
* [[시맨틱 이용한 태그 달기]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 3D 모델링 ==&lt;br /&gt;
=== [[초보자를 위한 모델링 프로그램 추천]]===&lt;br /&gt;
==== [[Blender 초보자 따라하기 ]] ====&lt;br /&gt;
==== [[Blender로 마우스 모델링]]====&lt;br /&gt;
==== [[Blender 버티컬 마우스 모델링]]====&lt;br /&gt;
=== [[mashmixer 모델링 편집틀 사용법]]===&lt;br /&gt;
=== 3D 프린터 ===&lt;br /&gt;
==== [[3D 프린터 추천 TOP 5]] ====&lt;br /&gt;
====[[3D 프린터 필라멘트 장단점 PLA PETG|3D 프린터 필라멘트 종류 와 장단점]]====&lt;br /&gt;
&lt;br /&gt;
==== [[뱀부랩 스튜디오 튜토리얼]] ====&lt;br /&gt;
==== [[3D 프린터로 메모 프린터 만들기|3D 프린터로 라인 프린터 만들기]] ====&lt;br /&gt;
==== [[3D 프린터 출력 제품 만족도 평가|3D프린터로 만든 제품 추천]] ====&lt;br /&gt;
==== [[3D 포르쉐911 RC카 만들기]] ====&lt;br /&gt;
&lt;br /&gt;
== 재테크 ==&lt;br /&gt;
=== 부동산 ===&lt;br /&gt;
=== 주식 ===&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|내용 = &amp;lt;big&amp;gt;주식 이야기&amp;lt;/big&amp;gt;&lt;br /&gt;
# [[주식 용어]]&lt;br /&gt;
# [[주식거래시간|주식거래시간(한국/미국/중국)]]&lt;br /&gt;
# [[주식 수수료]]&lt;br /&gt;
# [[주식 금리|금리가 오를때/내릴때 주식 투자법]]&lt;br /&gt;
# [[신호등 분석법]]&lt;br /&gt;
# [[미국 배당주 추천|미국 추천 배당주 ]]&lt;br /&gt;
}}&lt;br /&gt;
* [[주식 금액별 호가 단위]]&lt;br /&gt;
* [[주식 시클리컬 가치주 투자법|시클리컬 가치주 투자법]]&lt;br /&gt;
## [[시클리컬 주요 산업별 대장주&lt;br /&gt;
]]&lt;br /&gt;
* [[주식 테마주]]&lt;br /&gt;
==== 절세 계좌 투자 ====&lt;br /&gt;
# [[Isa 개인종합자산관리계좌|ISA 개인종합 계좌]] &lt;br /&gt;
# [[IRP 개인퇴직연금 계좌]] &lt;br /&gt;
# [[IRP 개인퇴직연금 계좌#IRP계좌 와 연금저축계좌의 차이|연금저축 계좌]]&lt;br /&gt;
# [[IRP ISA 연금저축펀트 최대 투자 금액|ISA IRP 연금저축펀드 비교 ]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 인물 ==&lt;br /&gt;
=== 근대사 ===&lt;br /&gt;
* [[노무현 명연설]]&lt;br /&gt;
* [[인간적인 국회의원 노회찬]]&lt;br /&gt;
* [[나의 스승 이어령]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 취미 ==&lt;br /&gt;
=== [[수영]] ===&lt;br /&gt;
== 휴식 ==&lt;br /&gt;
=== 휴양지 ===&lt;br /&gt;
==== [[서울_노원구_휴양림|서울_노원구_휴양림-&#039;&#039;&#039;수락 휴&#039;&#039;&#039;]]====&lt;br /&gt;
=== 물놀이/스파 ===&lt;br /&gt;
==== [[영종도 파라다이스 시티]] ====&lt;br /&gt;
==== [[경기도 계곡 추천]]====&lt;br /&gt;
==== [[가평 계곡 추천]] ====&lt;br /&gt;
==== [[전라도 계곡 추천]]====&lt;br /&gt;
==== [[경상도 계곡 추천]] ====&lt;br /&gt;
&lt;br /&gt;
=== 여행지 ===&lt;br /&gt;
=== 맛집 ===&lt;br /&gt;
==== [[인생 맛집 경기/서울]] ====&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 음악 ==&lt;br /&gt;
=== 클래식 ===&lt;br /&gt;
* [[구스타프 말러]]&lt;br /&gt;
&lt;br /&gt;
=== 레젼드 ===&lt;br /&gt;
* [[존바이즈 베스트]]&lt;br /&gt;
* [[유키 구라모토]]&lt;br /&gt;
* [[조지 윈스턴]]&lt;br /&gt;
* [[김광석 베스트]]&lt;br /&gt;
* [[신해철 베스트]]&lt;br /&gt;
* [[김건모 베스트]]&lt;br /&gt;
* [[박혜경 베스트]]&lt;br /&gt;
* [[김필 베스트]]&lt;br /&gt;
&lt;br /&gt;
=== 기타 연습 ===&lt;br /&gt;
* [[메탈리카 기타 연습]]&lt;br /&gt;
* [[캐논 락버전]]&lt;br /&gt;
* [[smells like teen spirit - nirvana|Smells Teen Split-너바나 ]]&lt;br /&gt;
* [[Lover&#039;s concerto]]&lt;br /&gt;
[[category:devcafe]] &lt;br /&gt;
[[category:python]] &lt;br /&gt;
[[category:미디어위키]]&lt;br /&gt;
[[category:autoit]]&lt;br /&gt;
[[category:인물]]&lt;br /&gt;
[[category:역사]]&lt;br /&gt;
[[category:음악]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%A3%BC%EC%8B%9D_%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EA%B0%80%EC%B9%98%EC%A3%BC_%ED%88%AC%EC%9E%90%EB%B2%95&amp;diff=2361</id>
		<title>주식 시클리컬 가치주 투자법</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%A3%BC%EC%8B%9D_%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EA%B0%80%EC%B9%98%EC%A3%BC_%ED%88%AC%EC%9E%90%EB%B2%95&amp;diff=2361"/>
		<updated>2026-03-31T09:33:28Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 시클리컬 가치주 투자 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 시클리컬 가치주 투자 ==&lt;br /&gt;
&lt;br /&gt;
* - 고정비/변동비 분석 &amp;amp; PBR/PER 역산&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 1️⃣ 고정비 vs 변동비 분리 (유가증권보고서 활용)&lt;br /&gt;
&lt;br /&gt;
### 개념 &lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
매출&lt;br /&gt;
  └─ 변동비 (매출과 함께 움직임)&lt;br /&gt;
       └─ 원재료비, 외주가공비, 포장비 등&lt;br /&gt;
  └─ 고정비 (매출과 무관하게 고정)&lt;br /&gt;
       └─ 감가상각비, 인건비, 임차료, 이자비용 등&lt;br /&gt;
  └─ 영업이익&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
시클리컬 주식의 핵심은&lt;br /&gt;
&amp;gt; **&amp;quot;업황이 좋아질 때 변동비는 조금만 늘고, 고정비는 그대로라 이익이 폭발적으로 증가&amp;quot;**&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 유가증권보고서 어디서 찾나?&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
DART (dart.fss.or.kr)&lt;br /&gt;
  └─ 해당 기업 검색&lt;br /&gt;
       └─ 사업보고서&lt;br /&gt;
            ├─ [제조원가명세서] → 변동비/고정비 핵심&lt;br /&gt;
            ├─ [손익계산서]    → 매출, 영업이익&lt;br /&gt;
            └─ [주석]          → 비용의 성격별 분류&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 구체적 예시 - POSCO홀딩스 (철강 시클리컬)&lt;br /&gt;
&lt;br /&gt;
**제조원가명세서 (단순화)**&lt;br /&gt;
&lt;br /&gt;
| 항목 | 금액 | 구분 |&lt;br /&gt;
|------|------|------|&lt;br /&gt;
| 원재료비 (철광석, 석탄) | 18조원 | 🔴 변동비 |&lt;br /&gt;
| 외주가공비 | 1조원 | 🔴 변동비 |&lt;br /&gt;
| 노무비 (인건비) | 2.5조원 | 🟡 준고정비 |&lt;br /&gt;
| 감가상각비 | 1.8조원 | 🔵 고정비 |&lt;br /&gt;
| 임차료/유지보수 | 0.7조원 | 🔵 고정비 |&lt;br /&gt;
| **합계** | **24조원** | |&lt;br /&gt;
&lt;br /&gt;
이걸 분리하면:&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
변동비율 = 변동비 / 매출&lt;br /&gt;
         = 19조 / 26조 = 약 73%&lt;br /&gt;
&lt;br /&gt;
공헌이익률 = 1 - 변동비율&lt;br /&gt;
           = 27%&lt;br /&gt;
&lt;br /&gt;
고정비 총액 = 약 5조원&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 이게 왜 중요한가? - 영업레버리지 계산&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
영업레버리지 = 공헌이익 / 영업이익&lt;br /&gt;
&lt;br /&gt;
예시)&lt;br /&gt;
  매출 26조, 변동비 19조, 고정비 5조&lt;br /&gt;
  → 공헌이익 = 7조&lt;br /&gt;
  → 영업이익 = 2조&lt;br /&gt;
  → 영업레버리지 = 7조 / 2조 = 3.5배&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**의미:**&lt;br /&gt;
&amp;gt; 매출이 10% 증가하면 → 영업이익은 **35% 증가**&lt;br /&gt;
&amp;gt; 매출이 10% 감소하면 → 영업이익은 **35% 감소**&lt;br /&gt;
&lt;br /&gt;
이게 시클리컬이 업황에 따라 주가가 폭등/폭락하는 이유입니다.&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 2️⃣ 수익 증가 시 PBR/PER 역산&lt;br /&gt;
&lt;br /&gt;
### 개념&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
보통 투자:  현재 주가 → PER/PBR 계산&lt;br /&gt;
역산 투자:  &amp;quot;업황 회복 시 EPS가 얼마일까?&amp;quot; &lt;br /&gt;
            → 적정 PER 적용&lt;br /&gt;
            → 목표 주가 산출&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 구체적 예시 - POSCO홀딩스&lt;br /&gt;
&lt;br /&gt;
**현재 상황 (업황 침체기)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
현재 주가:       300,000원&lt;br /&gt;
현재 EPS:        10,000원  (불황기 이익)&lt;br /&gt;
현재 PER:        30배       (이익 적으니 PER 높아 보임)&lt;br /&gt;
현재 BPS:        500,000원&lt;br /&gt;
현재 PBR:        0.6배      (저평가 신호)&lt;br /&gt;
발행주식수:      8,500만주&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
**STEP 1 - 업황 회복 시 이익 추정**&lt;br /&gt;
&lt;br /&gt;
고정비/변동비 분석에서 구한 영업레버리지 3.5배 활용&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
시나리오: 철강 가격 15% 회복 → 매출 15% 증가&lt;br /&gt;
&lt;br /&gt;
영업이익 증가율 = 15% × 3.5배 = 52.5% 증가&lt;br /&gt;
&lt;br /&gt;
현재 영업이익: 1.5조원&lt;br /&gt;
회복 시 영업이익: 1.5조 × 1.525 = 2.3조원&lt;br /&gt;
&lt;br /&gt;
세후 순이익 (세율 25% 가정):&lt;br /&gt;
  2.3조 × 0.75 = 1.72조원&lt;br /&gt;
&lt;br /&gt;
회복 시 EPS:&lt;br /&gt;
  1.72조 / 8,500만주 = 약 20,200원&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
**STEP 2 - PER 역산으로 목표주가 산출**&lt;br /&gt;
&lt;br /&gt;
시클리컬 주식은 **업황 정점일 때 PER 낮게, 침체기에 PER 높게** 적용합니다.&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
역사적 정상 PER 범위: 8~12배 (철강 섹터 기준)&lt;br /&gt;
&lt;br /&gt;
보수적 목표주가 = 회복 EPS × 8배&lt;br /&gt;
               = 20,200 × 8 = 161,600원  ← 이미 현재가보다 낮음?&lt;br /&gt;
&lt;br /&gt;
중립 목표주가   = 20,200 × 10배&lt;br /&gt;
               = 202,000원&lt;br /&gt;
&lt;br /&gt;
낙관 목표주가   = 20,200 × 12배&lt;br /&gt;
               = 242,400원&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
**STEP 3 - PBR 역산으로 교차 검증**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
회복 시 ROE 추정:&lt;br /&gt;
  순이익 1.72조 / 자본총계 42.5조 = 4.0%&lt;br /&gt;
&lt;br /&gt;
PBR-ROE 적정 공식 (그레이엄식):&lt;br /&gt;
  적정 PBR = ROE / 기대수익률&lt;br /&gt;
           = 4.0% / 8% = 0.5배&lt;br /&gt;
&lt;br /&gt;
적정 주가 (PBR 기준):&lt;br /&gt;
  BPS 500,000원 × 0.5 = 250,000원&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 역산 결과 종합&lt;br /&gt;
&lt;br /&gt;
| 방법 | 목표주가 |&lt;br /&gt;
|------|---------|&lt;br /&gt;
| PER 역산 (보수) | 161,600원 |&lt;br /&gt;
| PER 역산 (중립) | 202,000원 |&lt;br /&gt;
| PER 역산 (낙관) | 242,400원 |&lt;br /&gt;
| PBR 역산 | 250,000원 |&lt;br /&gt;
| **현재주가** | **300,000원** |&lt;br /&gt;
&lt;br /&gt;
이 경우라면 → **아직 비싸다, 매수 시점 아님** 이라는 결론이 나옵니다.&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 📌 시클리컬 투자 핵심 요약&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
업황 바닥일 때&lt;br /&gt;
  └─ PER 높아 보임 (이익 적어서) → 겁먹고 안 삼&lt;br /&gt;
  └─ PBR 낮음 (자산 대비 주가 쌈) → 매수 신호&lt;br /&gt;
&lt;br /&gt;
업황 정점일 때&lt;br /&gt;
  └─ PER 낮아 보임 (이익 많아서) → 싸 보여서 삼&lt;br /&gt;
  └─ PBR 높음 → 실제로는 고점&lt;br /&gt;
&lt;br /&gt;
시클리컬 황금 매수 타이밍:&lt;br /&gt;
  &amp;quot;PBR 0.5 이하 + 영업레버리지 높음 + 업황 회복 징조&amp;quot;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;gt; 치치님이 스크리닝하시는 **PBR ≤ 0.5 기준**이 시클리컬에서 특히 강력하게 맞아 떨어지는 이유가 바로 이것입니다! 💡&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=2360</id>
		<title>대문</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EB%8C%80%EB%AC%B8&amp;diff=2360"/>
		<updated>2026-03-31T09:33:10Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 주식 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__notoc__&lt;br /&gt;
{{#metadata:key=데브카페}}&lt;br /&gt;
{{#metadata:key=파이썬}}&lt;br /&gt;
{{#metadata:key=프로그램개발}}&lt;br /&gt;
{{#metadata:key=autoit}}&lt;br /&gt;
{{#metadata:key=미디어위키}}&lt;br /&gt;
&lt;br /&gt;
= Welcome to DEV CAFE =&lt;br /&gt;
* {{CURRENTYEAR}}.{{CURRENTMONTHNAME}}.{{CURRENTDAY}}({{CURRENTDAYNAME}})  , 이세상 모든 개발에 대한 이야기 {{SERVER}}&lt;br /&gt;
&amp;lt;p sizes=&amp;quot;(max-width: 600px) 480px,800px&amp;quot;&amp;gt;&lt;br /&gt;
https://devcafe.co.kr/w/images/devcafe_main3.jpeg&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
{{틀:타이틀 라운드&lt;br /&gt;
|제목=[[:Category:python|파이썬]]: {{PAGESINCATEGORY: python}} 건 ,  [[:Category:미디어위키]] : {{PAGESINCATEGORY: 미디어위키}} 건 ,  [[:Category:autoit|오토잇]]: {{PAGESINCATEGORY: autoit}} 건,  [[:Category:linux|LINUX]]: {{PAGESINCATEGORY: linux}} 건, [[:Category:음악]]: {{PAGESINCATEGORY: 음악}} 건&lt;br /&gt;
|아이콘=emoji_objects&lt;br /&gt;
}}&lt;br /&gt;
== IT/기술 ==&lt;br /&gt;
=== [[파이썬|파이썬 프로그래밍]] ===&lt;br /&gt;
==== 파이썬 설치 ====&lt;br /&gt;
# [[파이썬 설치]]&lt;br /&gt;
# [[Python 가상환경|파이썬 가상화]]&lt;br /&gt;
# [[pip 이용 파이썬 라이브러리 설치|파이썬 라이브러리 설치]]&lt;br /&gt;
# [[pip 이용 파이썬 라이브러리 설치#설치 패키지 추출|설치된 파이썬 라이브러리 추출하기]]&lt;br /&gt;
# [[Python whl 설치|.whl 파일 다운로드 설치법]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 기초 ====&lt;br /&gt;
{{알림&lt;br /&gt;
|내용=&#039;&#039;&#039;[[:category:Python|Python]] Foundation&#039;&#039;&#039; &lt;br /&gt;
# [[PYTHON 데이터형|파이썬 데이터형]]&lt;br /&gt;
# [[튜플 리스트 딕셔너리 차이점,특징,사용예시|튜플 리스트 딕셔너리]]&lt;br /&gt;
# [[파이썬 명명규칙]]&lt;br /&gt;
# [[파이썬 클래스]]&lt;br /&gt;
|아이콘이름=accessibility&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 고급 ====&lt;br /&gt;
:# [[파이썬 개발 추천기능 TOP10|파이썬 고급 기능 TOP 10]]&lt;br /&gt;
:# [[Python Cheat Sheet|파이썬 치트 쉬트 ]]&lt;br /&gt;
:# [[파이썬 config parser|파이썬 .ini 환경파일에 읽고쓰기]]&lt;br /&gt;
:# [[파이썬 로깅]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 응용 ====&lt;br /&gt;
# [[PyQt|파이썬 GUI 프로그램 개발]]&lt;br /&gt;
# [[Process memory share|쉐어 메모리 접근]]&lt;br /&gt;
# [[Python Cheat Sheet#Parallel Processing|병렬처리 프로세싱]]&lt;br /&gt;
# [[멀티프로세싱]]&lt;br /&gt;
# [[파이썬 스케줄러 모듈]]&lt;br /&gt;
# [[파이썬 장고]]&lt;br /&gt;
# [[flask]]&lt;br /&gt;
# [[Json 이용 키-값 추가 수정 삭제 방법|JSON 키값 수정/삭]]&lt;br /&gt;
# [[파이썬 mybatis xml sql 파싱 방법|XML 파싱(Mybatis)]]&lt;br /&gt;
# [[스케줄러 만들기]]&lt;br /&gt;
&lt;br /&gt;
==== 판다스(Pandas) 기초 ====&lt;br /&gt;
{{알림&lt;br /&gt;
|아이콘이름=apps &lt;br /&gt;
|내용= &#039;&#039;&#039;[[:category:pandas|PANDAS]] Foundation&#039;&#039;&#039;&lt;br /&gt;
# [[pandas|판다스(pandas) 기초]]&lt;br /&gt;
# [https://pandas.pydata.org/docs/ pandas 공식문서]&lt;br /&gt;
# [https://realpython.com/pandas-dataframe/ 리얼 판다스 튜토리얼]&lt;br /&gt;
# [https://www.geeksforgeeks.org/pandas-tutorial/ 그릭스 판다스 튜토리얼]&lt;br /&gt;
# [https://towardsdatascience.com/?gi=196f5e0ef243 투왈즈 튜터리얼]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== 판다스 응용 ====&lt;br /&gt;
# [[PANDAS 와 SQL 쿼리]]&lt;br /&gt;
# [[Pandas mysql insert]]&lt;br /&gt;
# [[Pandas 엑셀]]&lt;br /&gt;
&lt;br /&gt;
==== [[:category:streamlit|스트림릿 (Streamlit)]] 기초 ====&lt;br /&gt;
# [[Streamlit 기초]]&lt;br /&gt;
# [[Streamlit 개발 사이트|Streamlit 참고 사이트]]&lt;br /&gt;
# [[Streamlit Extention]]&lt;br /&gt;
# [[Streamlit 개발|Streamlit 프로그램]]&lt;br /&gt;
&lt;br /&gt;
==== 파이썬 응용프로그램 개발 ====&lt;br /&gt;
# [[주식 자동매매 프로그램 개발]]&lt;br /&gt;
## [[대신증권 API 활용]]&lt;br /&gt;
## [[키움증권 api 매수매도|키움증권 API 활용]]&lt;br /&gt;
# [[개인 PC 텍스트 검색 채봇]]&lt;br /&gt;
## [[개인 PC 텍스트 검색 챗봇 웹 화면]]&lt;br /&gt;
# [[SQL 정리 툴|SQL Formater(정리)]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[:category:java|JAVA 프로그래밍]] ===&lt;br /&gt;
==== [[Java openjdk 설치|JAVA 설치]] ====&lt;br /&gt;
==== [[JAVA 기초]] ====&lt;br /&gt;
# [[JAVA 명명 규칙]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[:category:autoit|Autotit 프로그래밍]] ===&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|아이콘이름=adb&lt;br /&gt;
|내용 = &amp;lt;big&amp;gt;오토잇 프로그래밍 설치 및 기초 문법&amp;lt;/big&amp;gt;&lt;br /&gt;
# [[Auotit 설치]]&lt;br /&gt;
# [[Autoit 기초 문법]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== [[Autoit gui|Autoit GUI]] ===&lt;br /&gt;
:# [[Autoit GUI 컨트롤 스타일|GUI 컨트롤 스타일]]&lt;br /&gt;
:# [[Handle의 class instance 값 구하는 방법|GUI 컨트롤 핸들 찾기]]&lt;br /&gt;
:# [[Autoit 멀티 윈도우 컨트롤]]&lt;br /&gt;
:# [[리스트뷰(그리드) 생성]]&lt;br /&gt;
&lt;br /&gt;
=== [[Autoit 프로그램 개발]] ===&lt;br /&gt;
:# [[Autoit sqlite|SQLite 디비 연결]]&lt;br /&gt;
:# [[Autoit oracle connect|ORACLE 디비 연결]]&lt;br /&gt;
:# [[Autoit 키보드 후킹|키보드 후킹]]&lt;br /&gt;
:# [[Autoit 자동 업그레이드 프로그램 개발|자동 업데이트 프로그램 개발]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== [[에디터 활용]] ===&lt;br /&gt;
==== [[:category:vscode|VS 코드(VS Code)]] ====&lt;br /&gt;
# [[Vscode 단축키]]&lt;br /&gt;
&lt;br /&gt;
==== [[:Category:서브라임|서브라임 (sublime)]] ====&lt;br /&gt;
# [[서브라임 sublime 단축키|Sublime 단축키]]&lt;br /&gt;
# [[Sublime plugin 생성 방법|Sublime 플러그인 만들기]]&lt;br /&gt;
# [[sublime emmet]]&lt;br /&gt;
==== [[Vi 명령어|vi 편집기]] ====&lt;br /&gt;
# [[Vi mode - ksh bash zsh]]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 미디어위키 사용법 ==&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|아이콘이름=school&lt;br /&gt;
|내용= &#039;&#039;&#039;미디어 위키 초보자 가이드&#039;&#039;&#039; &lt;br /&gt;
# [[미디어위키 기본 사용법]] &lt;br /&gt;
# [[환경세팅 파일|환경설정(LocalSettings.php)]]&lt;br /&gt;
# [[스킨(테마) 추가/변경]]&lt;br /&gt;
# [[미디어위키 확장 기능]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== [[미디어위키 예약어 와 변수]] ===&lt;br /&gt;
==== [[미디어위키 예약어 와 변수#동작어_(Behavior_Switches)|미디어위키 동작 예약어]] ====&lt;br /&gt;
==== [[미디어위키 예약어 와 변수#변수_(Variables)|미디어위키 변수 예약어]] ====&lt;br /&gt;
&lt;br /&gt;
=== [[미디어위키 기본 사용법]] ===&lt;br /&gt;
* [[미디어위키 기본 사용법#새글 쓰기|새글 쓰기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#대문 수정|첫페이지(대문) 수정]]&lt;br /&gt;
* [[미디어위키 기본 사용법#문단(Section)|목차 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#표(wiki table) 만들기|표 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#틀(Template) 만들기|틀 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#양식(Form) 만들기|폼 만들기]]&lt;br /&gt;
* [[미디어위키 기본 사용법#카테고리 만들기|카테고리 만들기]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 미디어 위키 환경설정 ===&lt;br /&gt;
* [[스킨(테마) 추가/변경]]&lt;br /&gt;
* [[미디어위키 기본 사용법#대문 수정하기|대문 타이틀 글씨 숨기기]]&lt;br /&gt;
* [[특수 명령어]]&lt;br /&gt;
* [[환경세팅 파일|환경세팅 파일(LocalSettings.php)]]&lt;br /&gt;
* [[나눔고딕 웹폰트 설정하기]]&lt;br /&gt;
* [[반응형 이미지 사이즈 조절 방법]]&lt;br /&gt;
* [[스니펫-목차에 순번 붙이기]]&lt;br /&gt;
&lt;br /&gt;
=== 미디어위키 확장 프로그램 가이드===&lt;br /&gt;
==== 페이지 관련 ====&lt;br /&gt;
* [[Syntax Highlight|문법 하이라이트]]&lt;br /&gt;
* [[목차에 순번 붙이기|목차에 순번 붙이기(확장기능 설치)]]&lt;br /&gt;
** [[스니펫-목차에 순번 붙이기 |목차에 순번 붙이기(JS/CSS 이용)]] / [https://www.mediawiki.org/wiki/Manual:Table_of_contents#Auto-numbering 원본페이지 이동]&lt;br /&gt;
* [[페이지에 탭 만들기]]&lt;br /&gt;
* [[관련글 보기 기능 추가 ]]&lt;br /&gt;
* [[방문자 수 보기 기능 추가]]&lt;br /&gt;
* [[문서 목차 불러오기 기능 추가]] &lt;br /&gt;
* [[댓글 기능 추가]]&lt;br /&gt;
* [[미디어위키 유튜브 삽입|유튜브 삽입]]&lt;br /&gt;
==== 로그인 ====&lt;br /&gt;
* [[구글인증 로그인]]&lt;br /&gt;
&lt;br /&gt;
==== 검색 관련 ====&lt;br /&gt;
* [[시맨틱 미디어위키 설치]]&lt;br /&gt;
* [[시맨틱 이용한 태그 달기]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 3D 모델링 ==&lt;br /&gt;
=== [[초보자를 위한 모델링 프로그램 추천]]===&lt;br /&gt;
==== [[Blender 초보자 따라하기 ]] ====&lt;br /&gt;
==== [[Blender로 마우스 모델링]]====&lt;br /&gt;
==== [[Blender 버티컬 마우스 모델링]]====&lt;br /&gt;
=== [[mashmixer 모델링 편집틀 사용법]]===&lt;br /&gt;
=== 3D 프린터 ===&lt;br /&gt;
==== [[3D 프린터 추천 TOP 5]] ====&lt;br /&gt;
====[[3D 프린터 필라멘트 장단점 PLA PETG|3D 프린터 필라멘트 종류 와 장단점]]====&lt;br /&gt;
&lt;br /&gt;
==== [[뱀부랩 스튜디오 튜토리얼]] ====&lt;br /&gt;
==== [[3D 프린터로 메모 프린터 만들기|3D 프린터로 라인 프린터 만들기]] ====&lt;br /&gt;
==== [[3D 프린터 출력 제품 만족도 평가|3D프린터로 만든 제품 추천]] ====&lt;br /&gt;
==== [[3D 포르쉐911 RC카 만들기]] ====&lt;br /&gt;
&lt;br /&gt;
== 재테크 ==&lt;br /&gt;
=== 부동산 ===&lt;br /&gt;
=== 주식 ===&lt;br /&gt;
{{틀:알림&lt;br /&gt;
|내용 = &amp;lt;big&amp;gt;주식 이야기&amp;lt;/big&amp;gt;&lt;br /&gt;
# [[주식 용어]]&lt;br /&gt;
# [[주식거래시간|주식거래시간(한국/미국/중국)]]&lt;br /&gt;
# [[주식 수수료]]&lt;br /&gt;
# [[주식 금리|금리가 오를때/내릴때 주식 투자법]]&lt;br /&gt;
# [[신호등 분석법]]&lt;br /&gt;
# [[미국 배당주 추천|미국 추천 배당주 ]]&lt;br /&gt;
}}&lt;br /&gt;
* [[주식 금액별 호가 단위]]&lt;br /&gt;
* [[주식 시클리컬 가치주 투자법|시클리컬 가치주 투자법]]&lt;br /&gt;
* [[주식 테마주]]&lt;br /&gt;
==== 절세 계좌 투자 ====&lt;br /&gt;
# [[Isa 개인종합자산관리계좌|ISA 개인종합 계좌]] &lt;br /&gt;
# [[IRP 개인퇴직연금 계좌]] &lt;br /&gt;
# [[IRP 개인퇴직연금 계좌#IRP계좌 와 연금저축계좌의 차이|연금저축 계좌]]&lt;br /&gt;
# [[IRP ISA 연금저축펀트 최대 투자 금액|ISA IRP 연금저축펀드 비교 ]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 인물 ==&lt;br /&gt;
=== 근대사 ===&lt;br /&gt;
* [[노무현 명연설]]&lt;br /&gt;
* [[인간적인 국회의원 노회찬]]&lt;br /&gt;
* [[나의 스승 이어령]]&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 취미 ==&lt;br /&gt;
=== [[수영]] ===&lt;br /&gt;
== 휴식 ==&lt;br /&gt;
=== 휴양지 ===&lt;br /&gt;
==== [[서울_노원구_휴양림|서울_노원구_휴양림-&#039;&#039;&#039;수락 휴&#039;&#039;&#039;]]====&lt;br /&gt;
=== 물놀이/스파 ===&lt;br /&gt;
==== [[영종도 파라다이스 시티]] ====&lt;br /&gt;
==== [[경기도 계곡 추천]]====&lt;br /&gt;
==== [[가평 계곡 추천]] ====&lt;br /&gt;
==== [[전라도 계곡 추천]]====&lt;br /&gt;
==== [[경상도 계곡 추천]] ====&lt;br /&gt;
&lt;br /&gt;
=== 여행지 ===&lt;br /&gt;
=== 맛집 ===&lt;br /&gt;
==== [[인생 맛집 경기/서울]] ====&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== 음악 ==&lt;br /&gt;
=== 클래식 ===&lt;br /&gt;
* [[구스타프 말러]]&lt;br /&gt;
&lt;br /&gt;
=== 레젼드 ===&lt;br /&gt;
* [[존바이즈 베스트]]&lt;br /&gt;
* [[유키 구라모토]]&lt;br /&gt;
* [[조지 윈스턴]]&lt;br /&gt;
* [[김광석 베스트]]&lt;br /&gt;
* [[신해철 베스트]]&lt;br /&gt;
* [[김건모 베스트]]&lt;br /&gt;
* [[박혜경 베스트]]&lt;br /&gt;
* [[김필 베스트]]&lt;br /&gt;
&lt;br /&gt;
=== 기타 연습 ===&lt;br /&gt;
* [[메탈리카 기타 연습]]&lt;br /&gt;
* [[캐논 락버전]]&lt;br /&gt;
* [[smells like teen spirit - nirvana|Smells Teen Split-너바나 ]]&lt;br /&gt;
* [[Lover&#039;s concerto]]&lt;br /&gt;
[[category:devcafe]] &lt;br /&gt;
[[category:python]] &lt;br /&gt;
[[category:미디어위키]]&lt;br /&gt;
[[category:autoit]]&lt;br /&gt;
[[category:인물]]&lt;br /&gt;
[[category:역사]]&lt;br /&gt;
[[category:음악]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%A3%BC%EC%8B%9D_%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EA%B0%80%EC%B9%98%EC%A3%BC_%ED%88%AC%EC%9E%90%EB%B2%95&amp;diff=2359</id>
		<title>주식 시클리컬 가치주 투자법</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%A3%BC%EC%8B%9D_%EC%8B%9C%ED%81%B4%EB%A6%AC%EC%BB%AC_%EA%B0%80%EC%B9%98%EC%A3%BC_%ED%88%AC%EC%9E%90%EB%B2%95&amp;diff=2359"/>
		<updated>2026-03-31T01:57:58Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: == 시클리컬 가치주 투자 ==  * - 고정비/변동비 분석 &amp;amp; PBR/PER 역산  ---  ## 1️⃣ 고정비 vs 변동비 분리 (유가증권보고서 활용)  ### 개념 먼저  ``` 매출   └─ 변동비 (매출과 함께 움직임)        └─ 원재료비, 외주가공비, 포장비 등   └─ 고정비 (매출과 무관하게 고정)        └─ 감가상각비, 인건비, 임차료, 이자비용 등   └─ 영업이익 ```  시클리컬 주식의 핵심은 &amp;gt;...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 시클리컬 가치주 투자 ==&lt;br /&gt;
&lt;br /&gt;
* - 고정비/변동비 분석 &amp;amp; PBR/PER 역산&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 1️⃣ 고정비 vs 변동비 분리 (유가증권보고서 활용)&lt;br /&gt;
&lt;br /&gt;
### 개념 먼저&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
매출&lt;br /&gt;
  └─ 변동비 (매출과 함께 움직임)&lt;br /&gt;
       └─ 원재료비, 외주가공비, 포장비 등&lt;br /&gt;
  └─ 고정비 (매출과 무관하게 고정)&lt;br /&gt;
       └─ 감가상각비, 인건비, 임차료, 이자비용 등&lt;br /&gt;
  └─ 영업이익&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
시클리컬 주식의 핵심은&lt;br /&gt;
&amp;gt; **&amp;quot;업황이 좋아질 때 변동비는 조금만 늘고, 고정비는 그대로라 이익이 폭발적으로 증가&amp;quot;**&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 유가증권보고서 어디서 찾나?&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
DART (dart.fss.or.kr)&lt;br /&gt;
  └─ 해당 기업 검색&lt;br /&gt;
       └─ 사업보고서&lt;br /&gt;
            ├─ [제조원가명세서] → 변동비/고정비 핵심&lt;br /&gt;
            ├─ [손익계산서]    → 매출, 영업이익&lt;br /&gt;
            └─ [주석]          → 비용의 성격별 분류&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 구체적 예시 - POSCO홀딩스 (철강 시클리컬)&lt;br /&gt;
&lt;br /&gt;
**제조원가명세서 (단순화)**&lt;br /&gt;
&lt;br /&gt;
| 항목 | 금액 | 구분 |&lt;br /&gt;
|------|------|------|&lt;br /&gt;
| 원재료비 (철광석, 석탄) | 18조원 | 🔴 변동비 |&lt;br /&gt;
| 외주가공비 | 1조원 | 🔴 변동비 |&lt;br /&gt;
| 노무비 (인건비) | 2.5조원 | 🟡 준고정비 |&lt;br /&gt;
| 감가상각비 | 1.8조원 | 🔵 고정비 |&lt;br /&gt;
| 임차료/유지보수 | 0.7조원 | 🔵 고정비 |&lt;br /&gt;
| **합계** | **24조원** | |&lt;br /&gt;
&lt;br /&gt;
이걸 분리하면:&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
변동비율 = 변동비 / 매출&lt;br /&gt;
         = 19조 / 26조 = 약 73%&lt;br /&gt;
&lt;br /&gt;
공헌이익률 = 1 - 변동비율&lt;br /&gt;
           = 27%&lt;br /&gt;
&lt;br /&gt;
고정비 총액 = 약 5조원&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 이게 왜 중요한가? - 영업레버리지 계산&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
영업레버리지 = 공헌이익 / 영업이익&lt;br /&gt;
&lt;br /&gt;
예시)&lt;br /&gt;
  매출 26조, 변동비 19조, 고정비 5조&lt;br /&gt;
  → 공헌이익 = 7조&lt;br /&gt;
  → 영업이익 = 2조&lt;br /&gt;
  → 영업레버리지 = 7조 / 2조 = 3.5배&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**의미:**&lt;br /&gt;
&amp;gt; 매출이 10% 증가하면 → 영업이익은 **35% 증가**&lt;br /&gt;
&amp;gt; 매출이 10% 감소하면 → 영업이익은 **35% 감소**&lt;br /&gt;
&lt;br /&gt;
이게 시클리컬이 업황에 따라 주가가 폭등/폭락하는 이유입니다.&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 2️⃣ 수익 증가 시 PBR/PER 역산&lt;br /&gt;
&lt;br /&gt;
### 개념&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
보통 투자:  현재 주가 → PER/PBR 계산&lt;br /&gt;
역산 투자:  &amp;quot;업황 회복 시 EPS가 얼마일까?&amp;quot; &lt;br /&gt;
            → 적정 PER 적용&lt;br /&gt;
            → 목표 주가 산출&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 구체적 예시 - POSCO홀딩스&lt;br /&gt;
&lt;br /&gt;
**현재 상황 (업황 침체기)**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
현재 주가:       300,000원&lt;br /&gt;
현재 EPS:        10,000원  (불황기 이익)&lt;br /&gt;
현재 PER:        30배       (이익 적으니 PER 높아 보임)&lt;br /&gt;
현재 BPS:        500,000원&lt;br /&gt;
현재 PBR:        0.6배      (저평가 신호)&lt;br /&gt;
발행주식수:      8,500만주&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
**STEP 1 - 업황 회복 시 이익 추정**&lt;br /&gt;
&lt;br /&gt;
고정비/변동비 분석에서 구한 영업레버리지 3.5배 활용&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
시나리오: 철강 가격 15% 회복 → 매출 15% 증가&lt;br /&gt;
&lt;br /&gt;
영업이익 증가율 = 15% × 3.5배 = 52.5% 증가&lt;br /&gt;
&lt;br /&gt;
현재 영업이익: 1.5조원&lt;br /&gt;
회복 시 영업이익: 1.5조 × 1.525 = 2.3조원&lt;br /&gt;
&lt;br /&gt;
세후 순이익 (세율 25% 가정):&lt;br /&gt;
  2.3조 × 0.75 = 1.72조원&lt;br /&gt;
&lt;br /&gt;
회복 시 EPS:&lt;br /&gt;
  1.72조 / 8,500만주 = 약 20,200원&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
**STEP 2 - PER 역산으로 목표주가 산출**&lt;br /&gt;
&lt;br /&gt;
시클리컬 주식은 **업황 정점일 때 PER 낮게, 침체기에 PER 높게** 적용합니다.&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
역사적 정상 PER 범위: 8~12배 (철강 섹터 기준)&lt;br /&gt;
&lt;br /&gt;
보수적 목표주가 = 회복 EPS × 8배&lt;br /&gt;
               = 20,200 × 8 = 161,600원  ← 이미 현재가보다 낮음?&lt;br /&gt;
&lt;br /&gt;
중립 목표주가   = 20,200 × 10배&lt;br /&gt;
               = 202,000원&lt;br /&gt;
&lt;br /&gt;
낙관 목표주가   = 20,200 × 12배&lt;br /&gt;
               = 242,400원&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
**STEP 3 - PBR 역산으로 교차 검증**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
회복 시 ROE 추정:&lt;br /&gt;
  순이익 1.72조 / 자본총계 42.5조 = 4.0%&lt;br /&gt;
&lt;br /&gt;
PBR-ROE 적정 공식 (그레이엄식):&lt;br /&gt;
  적정 PBR = ROE / 기대수익률&lt;br /&gt;
           = 4.0% / 8% = 0.5배&lt;br /&gt;
&lt;br /&gt;
적정 주가 (PBR 기준):&lt;br /&gt;
  BPS 500,000원 × 0.5 = 250,000원&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 역산 결과 종합&lt;br /&gt;
&lt;br /&gt;
| 방법 | 목표주가 |&lt;br /&gt;
|------|---------|&lt;br /&gt;
| PER 역산 (보수) | 161,600원 |&lt;br /&gt;
| PER 역산 (중립) | 202,000원 |&lt;br /&gt;
| PER 역산 (낙관) | 242,400원 |&lt;br /&gt;
| PBR 역산 | 250,000원 |&lt;br /&gt;
| **현재주가** | **300,000원** |&lt;br /&gt;
&lt;br /&gt;
이 경우라면 → **아직 비싸다, 매수 시점 아님** 이라는 결론이 나옵니다.&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
### 📌 시클리컬 투자 핵심 요약&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
업황 바닥일 때&lt;br /&gt;
  └─ PER 높아 보임 (이익 적어서) → 겁먹고 안 삼&lt;br /&gt;
  └─ PBR 낮음 (자산 대비 주가 쌈) → 매수 신호&lt;br /&gt;
&lt;br /&gt;
업황 정점일 때&lt;br /&gt;
  └─ PER 낮아 보임 (이익 많아서) → 싸 보여서 삼&lt;br /&gt;
  └─ PBR 높음 → 실제로는 고점&lt;br /&gt;
&lt;br /&gt;
시클리컬 황금 매수 타이밍:&lt;br /&gt;
  &amp;quot;PBR 0.5 이하 + 영업레버리지 높음 + 업황 회복 징조&amp;quot;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&amp;gt; 치치님이 스크리닝하시는 **PBR ≤ 0.5 기준**이 시클리컬에서 특히 강력하게 맞아 떨어지는 이유가 바로 이것입니다! 💡&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%97%84_%EC%A7%80%EC%88%98&amp;diff=2358</id>
		<title>그레이엄 지수</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%97%84_%EC%A7%80%EC%88%98&amp;diff=2358"/>
		<updated>2026-03-16T15:09:31Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: 📐 그레이엄 지수란? 먼저 개념부터 정리하겠습니다.  그레이엄 지수 = PER × PBR ≤ 22.5   벤저민 그레이엄이 제시한 기준으로, PER 15 × PBR 1.5 = 22.5가 안전마진의 상한선입니다. 낮을수록 저평가입니다.  🏆 그레이엄 지수 22.5 이하 추정 종목 앞서 논의한 종목들 + 업계 대표 가치주를 기준으로 계산했습니다 (2024~2025년 재무 기준 추정치).    |종목          |PER(추정)|PBR(...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;📐 그레이엄 지수란?&lt;br /&gt;
먼저 개념부터 정리하겠습니다.&lt;br /&gt;
&lt;br /&gt;
그레이엄 지수 = PER × PBR ≤ 22.5&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
벤저민 그레이엄이 제시한 기준으로, PER 15 × PBR 1.5 = 22.5가 안전마진의 상한선입니다. 낮을수록 저평가입니다.&lt;br /&gt;
&lt;br /&gt;
🏆 그레이엄 지수 22.5 이하 추정 종목&lt;br /&gt;
앞서 논의한 종목들 + 업계 대표 가치주를 기준으로 계산했습니다 (2024~2025년 재무 기준 추정치).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|종목          |PER(추정)|PBR(추정)|**그레이엄 지수**|자기자본비율|등급 |&lt;br /&gt;
|------------|-------|-------|:---------:|------|:-:|&lt;br /&gt;
|**LX인터내셔널** |5.2    |0.30   |**1.6**    |~60%  |⭐⭐⭐|&lt;br /&gt;
|**세방**      |6.5    |0.35   |**2.3**    |~65%  |⭐⭐⭐|&lt;br /&gt;
|**한섬**      |9.0    |0.45   |**4.1**    |~67%  |⭐⭐⭐|&lt;br /&gt;
|**이노션**     |7.5    |0.65   |**4.9**    |~72%  |⭐⭐ |&lt;br /&gt;
|**현대차**     |5.5    |0.50   |**2.8**    |~40%  |⭐⭐ |&lt;br /&gt;
|**기아**      |4.5    |0.55   |**2.5**    |~45%  |⭐⭐ |&lt;br /&gt;
|**삼성전자**    |15     |1.10   |**16.5**   |~75%  |⭐  |&lt;br /&gt;
|**POSCO홀딩스**|8.0    |0.35   |**2.8**    |~54%  |⭐⭐ |&lt;br /&gt;
|**HD현대**    |7.0    |0.45   |**3.2**    |~40%  |⭐⭐ |&lt;br /&gt;
&lt;br /&gt;
📊 그레이엄 지수 구간별 해석&lt;br /&gt;
&lt;br /&gt;
0  ~ 5  : 극도의 저평가 (매우 희귀)  → LX인터내셔널, 세방&lt;br /&gt;
5  ~ 10 : 저평가 우량 구간          → 한섬, 이노션&lt;br /&gt;
10 ~ 15 : 적정 저평가              → 삼성전자(경계)&lt;br /&gt;
15 ~ 22.5: 허용 범위              → 그레이엄 상한선&lt;br /&gt;
22.5 초과 : 고평가 경고&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
🔍 핵심 3개 종목 상세&lt;br /&gt;
1️⃣ LX인터내셔널 (001120) — 그레이엄 지수 ~1.6 ⭐최우선&lt;br /&gt;
증권사들은 2025년 EPS를 7,100원으로 추정하고 있으며, PER 5.2배 수준의 밸류에이션은 동종업계 대비 저평가 구간으로 평가됩니다. 실적 개선 모멘텀과 함께 밸류에이션 재평가가 진행되면서 주가 상승 여력이 확인되고 있습니다. ￼&lt;br /&gt;
&lt;br /&gt;
그레이엄 지수 = 5.2 × 0.30 ≈ 1.6  (기준치의 1/14 수준!)&lt;br /&gt;
배당수익률   = 8.1%&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2️⃣ 세방 (004360) — 그레이엄 지수 ~2.3 ⭐&lt;br /&gt;
&lt;br /&gt;
그레이엄 지수 = 6.5 × 0.35 ≈ 2.3&lt;br /&gt;
업종      = 물류/항만하역&lt;br /&gt;
특징      = 무차입 경영에 가까운 재무 구조&lt;br /&gt;
약점      = 소형주로 거래량 적음&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3️⃣ 한섬 (020000) — 그레이엄 지수 ~4.1 ⭐&lt;br /&gt;
&lt;br /&gt;
그레이엄 지수 = 9.0 × 0.45 ≈ 4.1&lt;br /&gt;
업종      = 프리미엄 패션 (현대백화점 계열)&lt;br /&gt;
특징      = TIME, SYSTEM 등 브랜드 보유&lt;br /&gt;
약점      = 내수 소비 침체 영향&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
⚠️ 그레이엄 지수의 한계&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|한계     |설명                      |&lt;br /&gt;
|-------|------------------------|&lt;br /&gt;
|성장성 미반영|미래 성장 기업은 고PER이 정상일 수 있음|&lt;br /&gt;
|업종 차이  |금융주는 구조상 PBR이 낮아 왜곡     |&lt;br /&gt;
|일회성 이익 |PER이 일시적으로 낮아 보일 수 있음   |&lt;br /&gt;
|실적 급변  |해운·에너지 등 사이클 업종 주의      |&lt;br /&gt;
&lt;br /&gt;
💡 그레이엄 지수와 앞서 논의한 3중 필터 비교&lt;br /&gt;
&lt;br /&gt;
이전 3중 필터 (PBR≤0.5 + 자기자본비율≥60% + PER≤12)와&lt;br /&gt;
그레이엄 지수 22.5 이하를 합치면:&lt;br /&gt;
&lt;br /&gt;
PBR 0.5 × PER 12 = 그레이엄 지수 6.0&lt;br /&gt;
&lt;br /&gt;
→ 사실상 그레이엄 지수 6 이하 + 자기자본비율 60% 이상이&lt;br /&gt;
  가장 엄격한 필터!&lt;br /&gt;
&lt;br /&gt;
이 조건을 모두 만족하는 최유력 후보: LX인터내셔널, 세방, 한섬&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
⚠️ 수치는 추정치이며 투자 권유가 아닙니다. 최신 PER/PBR은 반드시 네이버 증권 또는 FnGuide에서 직접 확인하세요.​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%9E%90%EC%82%B0%EA%B0%80%EC%B9%98%EC%A3%BC_2026%EB%85%84&amp;diff=2357</id>
		<title>자산가치주 2026년</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%9E%90%EC%82%B0%EA%B0%80%EC%B9%98%EC%A3%BC_2026%EB%85%84&amp;diff=2357"/>
		<updated>2026-03-16T14:58:52Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: 📊 3중 필터: PBR ≤ 0.5 + 자기자본비율 ≥ 60% + PER ≤ 12 3가지 동시 충족은 정말 희귀한 조합입니다. 특히 PER ≤ 12 조건이 더해지면 “수익도 나는데 자산 대비 싸고 재무도 건전”한 최상위 가치주만 남습니다.  🏆 핵심 후보 종목 (2025년 재무 기준 추정)    |종목         |PBR     |PER   |자기자본비율|업종   |상태         | |-----------|--------|------|------|-----|-----------| |**LX인터...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;📊 3중 필터: PBR ≤ 0.5 + 자기자본비율 ≥ 60% + PER ≤ 12&lt;br /&gt;
3가지 동시 충족은 정말 희귀한 조합입니다. 특히 PER ≤ 12 조건이 더해지면 “수익도 나는데 자산 대비 싸고 재무도 건전”한 최상위 가치주만 남습니다.&lt;br /&gt;
&lt;br /&gt;
🏆 핵심 후보 종목 (2025년 재무 기준 추정)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|종목         |PBR     |PER   |자기자본비율|업종   |상태         |&lt;br /&gt;
|-----------|--------|------|------|-----|-----------|&lt;br /&gt;
|**LX인터내셔널**|~0.3    |~5배   |~60%  |종합상사 |✅ 3조건 부합 유력|&lt;br /&gt;
|**이노션**    |~0.6~0.7|~7~8배 |~70%+ |광고마케팅|⚠️ PBR 경계선  |&lt;br /&gt;
|**한섬**     |~0.4~0.5|~8~10배|~65%  |패션   |✅ 3조건 부합 유력|&lt;br /&gt;
|**HMM**    |~0.4~0.5|불안정   |~72%  |해운   |⚠️ 실적 변동성 큼 |&lt;br /&gt;
|**세방**     |~0.3~0.4|~6~8배 |~65%  |운송/물류|✅ 3조건 부합 유력|&lt;br /&gt;
&lt;br /&gt;
📋 종목별 상세&lt;br /&gt;
1️⃣ LX인터내셔널 (001120) ⭐ 최우선 주목&lt;br /&gt;
증권사들은 2025년 EPS를 7,100원으로 추정하고 있으며, PER 5.2배 수준의 밸류에이션은 동종업계 대비 저평가 구간으로 평가됩니다. ￼&lt;br /&gt;
2025년 주당 배당금 2,000원으로 확정되었으며 배당수익률 8.1%는 전년 대비 66.7% 증가로 시중 금리 대비 매우 매력적인 수준입니다. ￼&lt;br /&gt;
&lt;br /&gt;
PBR   : ~0.3배 (추정)&lt;br /&gt;
PER   : ~5.2배&lt;br /&gt;
자기자본비율 : ~60% 내외&lt;br /&gt;
배당수익률  : 8.1%&lt;br /&gt;
업종    : 자원/물류/무역 종합상사 (LG 계열)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
✔ 3조건 모두 충족 가능성 가장 높음&lt;br /&gt;
✔ 인도네시아 니켈 등 2차전지 핵심광물 사업 확장 중&lt;br /&gt;
&lt;br /&gt;
2️⃣ 한섬 (020000)&lt;br /&gt;
&lt;br /&gt;
PBR   : ~0.4~0.5배 (추정)&lt;br /&gt;
PER   : ~8~10배&lt;br /&gt;
자기자본비율 : ~65% (무차입 경영)&lt;br /&gt;
배당수익률  : 3~4%&lt;br /&gt;
업종    : 프리미엄 패션 (현대백화점 계열)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
✔ TIME, SYSTEM, 랑방컬렉션 등 프리미엄 브랜드 보유&lt;br /&gt;
⚠ 내수 소비 침체로 실적 회복 속도 주의&lt;br /&gt;
&lt;br /&gt;
3️⃣ 세방 (004360)&lt;br /&gt;
&lt;br /&gt;
PBR   : ~0.3~0.4배 (추정)&lt;br /&gt;
PER   : ~6~8배&lt;br /&gt;
자기자본비율 : ~65%&lt;br /&gt;
배당수익률  : 3~4%&lt;br /&gt;
업종    : 물류/항만하역&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
✔ 인지도 낮아 소외되어 있으나 재무 우량&lt;br /&gt;
⚠ 거래량 적은 소형주&lt;br /&gt;
&lt;br /&gt;
4️⃣ HMM (011200)&lt;br /&gt;
2025년 3분기 누적 전년동기 대비 연결기준 매출액은 4.2% 감소, 영업이익은 54.5% 감소, 당기순이익은 47.5% 감소했습니다. 컨테이너 부문은 트럼프 정부 관세 정책으로 성수기 물량 강세가 없었고, 선복 증가로 공급 과잉에 따른 실적 악화가 있었습니다. ￼&lt;br /&gt;
&lt;br /&gt;
PBR   : ~0.4~0.5배&lt;br /&gt;
PER   : 실적 급락으로 불안정&lt;br /&gt;
자기자본비율 : ~72%&lt;br /&gt;
현금성 자산  : 약 10조원&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
⚠ PER 조건이 실적 변동으로 충족 어려울 수 있음&lt;br /&gt;
⚠ 해운업 사이클 리스크&lt;br /&gt;
&lt;br /&gt;
🔎 직접 스크리닝 방법 (정확한 최신 데이터 확인)&lt;br /&gt;
네이버 증권 → 국내증시 → 조건검색:&lt;br /&gt;
&lt;br /&gt;
PBR  : 0 ~ 0.50&lt;br /&gt;
PER  : 0 ~ 12.00&lt;br /&gt;
부채비율 : 0 ~ 67 (자기자본비율 60% = 부채비율 약 67% 이하)&lt;br /&gt;
영업이익 : 양수 조건 추가&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
FnGuide 스크리너:&lt;br /&gt;
comp.fnguide.com → 기초검색 → 3개 조건 입력&lt;br /&gt;
&lt;br /&gt;
💡 핵심 요약&lt;br /&gt;
&lt;br /&gt;
✅ 가장 확실한 후보 : LX인터내셔널&lt;br /&gt;
✅ 재무 우량 소외주  : 세방&lt;br /&gt;
✅ 브랜드 가치주    : 한섬&lt;br /&gt;
⚠ 변동성 있는 후보  : HMM (현금은 많지만 실적 불안)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
위 수치는 추정치이며 투자 권유가 아닙니다. 반드시 네이버증권 또는 FnGuide에서 최신 수치 확인 후 판단하세요.​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2356</id>
		<title>ISA</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2356"/>
		<updated>2026-02-26T23:53:55Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* ISA 개인종합자산관리계좌 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= ISA 개인종합자산관리계좌 =&lt;br /&gt;
{{틀:고지상자&lt;br /&gt;
|제목= 개인종합자산관리계좌(Individual Savings Account)&lt;br /&gt;
|내용=* 다양한 금융상품을 한 계좌에서 통합관리하여 절세효과&lt;br /&gt;
* ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금 정산&lt;br /&gt;
* 연금저축이 아니다 =&amp;gt; 단기,목돈마련이 목적&lt;br /&gt;
* 개별 주식 투자가 가능하다.(IRP,연금저축은 개별주식 투자 불가능)&lt;br /&gt;
* 배당주 투자에 적합(종합금융소득세 절세효과)&lt;br /&gt;
** 국내상장 해외etf는 매매차익 + 배당소득세 합산하여 세금 부과&lt;br /&gt;
* 19세 이상 ,15세 이상(근로소득자) 가능&lt;br /&gt;
}}&lt;br /&gt;
== ISA의 종류 ==&lt;br /&gt;
=== 일임형 ===&lt;br /&gt;
# 고객이 전문가에게 자산운용을 일임하는 유형&lt;br /&gt;
 &lt;br /&gt;
=== 신탁형 ===&lt;br /&gt;
# 고객이 직접 운용 지시를 하여 자산운용을 하는 것&lt;br /&gt;
# 중간에 은행 등 전문가를 두고 자산을 관리하는 형식&lt;br /&gt;
&lt;br /&gt;
=== 중개형 (일반적으로 선호)===&lt;br /&gt;
# 일임형 또는 신탁형과는 다르게, 전적으로 본인이 계좌를 운용할 수 있는 계좌&lt;br /&gt;
# 포트폴리오 구성, 리밸런싱 등 모든 투자결정을 본인 재량에 따라 직접 해야함. &lt;br /&gt;
# ISA계좌를 보편적으로 이야기하는 것은 중개형입니다.&lt;br /&gt;
&lt;br /&gt;
== 세금 산정 예시 ==&lt;br /&gt;
* 국내상장 해외etf는 매매차익 과 배당금 합산하여 세금 발생&lt;br /&gt;
* 국내주식형 ETF는 매매차익이 원래 비과세라 배당금 부분에서만 세금 발생&lt;br /&gt;
&lt;br /&gt;
=== 국내상장한 해외etf (타이거 s&amp;amp;p500) ===&lt;br /&gt;
일반형 isa 계좌에서 타이거s&amp;amp;p500 etf로 매매차익 2000만원 과 배당금 1000만원 의 수익을 얻었다면 내야하는 세금은 총 얼마야?&lt;br /&gt;
&lt;br /&gt;
총 수익          3,000만원&lt;br /&gt;
비과세 한도   -    200만원&lt;br /&gt;
─────────────────────────&lt;br /&gt;
과세 대상        2,800만원&lt;br /&gt;
× 9.9% (분리과세)&lt;br /&gt;
─────────────────────────&lt;br /&gt;
납부 세금   =    277.2만원&lt;br /&gt;
&lt;br /&gt;
=== 국내 ETF(타이거 2차전지 top10) ===&lt;br /&gt;
일반형 isa 계좌에서 타이거 2차전지 top10 etf로 매매차익 2000만원 과 배당금 1000만원 의 수익을 얻었다면 내야하는 세금은 총 얼마야?&lt;br /&gt;
&lt;br /&gt;
과세대상 수익      1,000만원  (배당금만)&lt;br /&gt;
비과세 한도   -     200만원&lt;br /&gt;
─────────────────────────&lt;br /&gt;
과세 대상           800만원&lt;br /&gt;
× 9.9% (분리과세)&lt;br /&gt;
─────────────────────────&lt;br /&gt;
납부 세금   =       79.2만원&lt;br /&gt;
&lt;br /&gt;
== ISA 장단점 ==&lt;br /&gt;
=== 장점 ===&lt;br /&gt;
==== 세제 혜택 ====&lt;br /&gt;
===== 비과세 =====&lt;br /&gt;
# ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금을 정산&lt;br /&gt;
# 최종 과세대상 소득 중 200만 원까지는 비과세, 200만 원을 초과하는 금액에 대해서는 9.9%(지방소득세 포함)분리과세 적용  &lt;br /&gt;
## 최대 400만원의 비과세 혜택을 받을 수 있다.&lt;br /&gt;
## 비과세 혜택은 일반형/서민형/농어민형에 따라 다르게 적용. &lt;br /&gt;
## 총급여액이 5000만원 이하인 근로자 또는 종합소득이 3800만원 이하인 사업자는 400만원이 적용됨.&lt;br /&gt;
# 주의할 점은, 비과세 한도가 가입기간 전체에 적용되는 내용이라는 것. &lt;br /&gt;
#: - 납입한도가 연 기준으로 적용되기 때문에 많은 분들 이 비과세 한도도 연간 200만원(400만원)이라고 착각하시지만, 3년 이니 총 600만원 비과세되는 것이 아니다&lt;br /&gt;
#: * 흔한 착각 vs 실제&lt;br /&gt;
#:❌ 착각하는 사람들의 생각&lt;br /&gt;
#:“납입한도가 연 2,000만원이니까, 비과세도 매년 200만원씩 적용되겠지?”&lt;br /&gt;
#:→ 3년 가입 시 총 600만원 비과세?&lt;br /&gt;
#:✅ 실제&lt;br /&gt;
#:3년을 가입하든, 5년을 가입하든 비과세는 딱 200만원(서민형 400만원) 한 번만&lt;br /&gt;
&lt;br /&gt;
===== 분리과세 =====&lt;br /&gt;
# 비과세 혜택을 초과하는 금액은 9.9%로 분리과세(종합과세되지 않은것도 엄청남 혜택임)&lt;br /&gt;
# 금융소득에 대한 소득세율이 15.4%인 점을 고려 하면 비과세 금액을 초과(9.9%)하더라도 ISA 계좌가 훨씬 유리. &lt;br /&gt;
# 금융상품에서 발생한 소득이 분리과세 됨으로써 종합금융과세 될 확률이 줄어든다는 장점&lt;br /&gt;
===== 연금계좌로 전환시 추가 공제 =====&lt;br /&gt;
# 만기에 계좌에 보유한 자금을 연금계좌로 전환할 경우에는 전환금액의 10% 만큼의 세금을 300만원을 추가로 공제.&lt;br /&gt;
&lt;br /&gt;
==== 손익 통산 ====&lt;br /&gt;
# 일반 계좌에서 A금융상품에 대해 200만원의 손실을 봤고, B금융상품에 대해 100만원의 이익을 봤으면 A의 손실에 대해서는 과세하지 않고 B의 이익에 대해서는 과세 하게 됨.(즉  계좌가 개별로 처리됨)&lt;br /&gt;
#: =&amp;gt; 결과적으로 100만원의 손실을 봤음에 도 불구하고 또 세금을 내야됨.&lt;br /&gt;
# ISA 계좌를 통해서 A와 B에 투자한 경우, 각각에서 발생한 손익을 통산하여 총 이익을 -100만원 으로 보아 세금을 매기지 않습니다. &lt;br /&gt;
#: =&amp;gt; 이를 이용해서  연말에 손실을 본 주식을 매각하는 등 세율구간 조정도 가능&lt;br /&gt;
==== IRP/연금저축계좌로 이관시 혜택 ====&lt;br /&gt;
# ISA 만기자금을 연금계좌로 이체할 경우 ISA에서의 이체 금액만큼 연금계좌의 납입 한도를 추가로 늘려준다.&lt;br /&gt;
## 원래 연금계좌는 연금저축과 IRP를 합쳐서 연간 1800만원까지만 납입이 가능하다.&lt;br /&gt;
## 이미 연금계좌 납입 한도(연금저축의 경우 연 1800만원, IRP의 경우도 연 1800만원까지만 납입 가능하다.)를 다 채운 사람이라도 ISA 만기 자금을 연금계좌로 이체하는데 아무런 제한이 없다는 것이다.&lt;br /&gt;
# 자금 이체는 ISA 계약기간이 만료된 날로부터 60일 이내에 해야 한다. &lt;br /&gt;
# 만기자금의 일부만 이체하는 것도 가능하다.&lt;br /&gt;
===== 추가 세액 공제 =====&lt;br /&gt;
# 연금계좌로 이체한 만기자금 중 10%, 최대 300만원 세액공제&lt;br /&gt;
# 연소득이 4천만원(근로소득만 있으면 총급여 5천5백만원) 이하면 세액공제율은 16.5%, 이보다 많으면 13.2% 공제. &lt;br /&gt;
## 예시) isa 만기 자금 3천만원을 연금 계좌에 이체할 경우&lt;br /&gt;
### 세액공제 기준이 되는 금액은 10%인 300만원&lt;br /&gt;
### 기준보다 소득이 적은 사람은 300만원×16.5%=49만5천원 &lt;br /&gt;
### 기준보다 소득이 많은 사람은 300만원×13.2%=39만6천원의 세금 환급&lt;br /&gt;
# ISA 보다 더 적은 세금 적용&lt;br /&gt;
## ISA 만기자금을  연금계좌로 이체한 자금은 추후(55세 이후) 연금을 수령할 때 ISA계좌보다 더 낮은 세율인 3.3~5.5%의 연금소득세가 부과&lt;br /&gt;
&lt;br /&gt;
=== 단점 ===&lt;br /&gt;
* 이 모든 혜택에도 불구하고, 개인종합자산관리계좌 를 이용할 때 반드시 고려해야 할 사항.&lt;br /&gt;
==== 해외주식 직접 투자 불가능 (ETF만 거래 가능) ====&lt;br /&gt;
# 일반계좌에서는 국내주식도, 해외주식도 모두 직접 투자할 수 있는데 반해서 ISA 계좌에서는 해외주식 직접투자가 불가능함.&lt;br /&gt;
#: =&amp;gt; 그렇다면 국내주식만 비과세 혜택 등을 얻을수 있는것인지? 꼭 그런것은 아니다.&lt;br /&gt;
&lt;br /&gt;
==== 납입한도와 의무가입기간 ====&lt;br /&gt;
# 연 최대 2000만원 &lt;br /&gt;
# 의무가입기간 3년&lt;br /&gt;
# 최대 5년까지 만기를 연장 신청할 수 있음.&lt;br /&gt;
# 즉, 1인당 계좌에 최대 1억원을 한도로 납입할 수 있음. &lt;br /&gt;
# 만약 해당연도에 최대 금액을 납입하지 않는다면 다음 해로 이월됨&lt;br /&gt;
# 가입대상-금융소득 종합과세 대상자 가입불가 (2024년 개편안, 금융소득종합과세 대상자 가입 허용_국내주식,국내주식형 펀드투자만 가능)&lt;br /&gt;
&lt;br /&gt;
==== 중도해지/중도인출 ====&lt;br /&gt;
# 의무가입기간이 있기 때문에 가입중에 긴급하게 인출이 필요할 경우에 ISA 계좌는 중도해지할 경우 지금까지 받은 세금 혜택을 모두 반납 함&lt;br /&gt;
#: 사실상 세제 혜택을 받지 못하는 최악의 상황 발생 &lt;br /&gt;
# 추천 하는 방법은 바로 중도인출을 적절히 활용하는 것. &lt;br /&gt;
#: =&amp;gt; 중도해지는 안되지만, 납부한 원금에 한해서 중도인출은 되기 때문.&lt;br /&gt;
&lt;br /&gt;
=== 문제점 === &lt;br /&gt;
==== 건강보험료가 인상된다는데? ====&lt;br /&gt;
# 검색해보면 ISA 계좌를 이용하면 건강보험료가 인 상된다는 내용이 많음. 그런데 알고 보면 이것은 계좌 가입 여부와 무관하게 &amp;quot;금융소득이 많이 발생하면 소득이 늘기 때문에 건강보험료가 인상된다”는 취지 임. 가입만으로 건강보험료가 인상되는 일은 없음.&lt;br /&gt;
&lt;br /&gt;
==== 건강보험료 피부양자인경우 자격 박탈 ====&lt;br /&gt;
# 만기수령시에 분리과세되는 금융소득이 한꺼번에 생기게되는데 문제는 이 금융소득이 연 천만원을 넘어가게 되면 ISA가입자가 건강보험료 피부양자인 경우 피부양자 자격요건에서 박탈되거나, &lt;br /&gt;
# ISA 가입자가 지역건강보험 가입자인 경우에는 소득으로 합산되는 문제가 발생할 수 있다는 점이다. &lt;br /&gt;
## 예컨대 5년 동안 매년 2,000만원씩 ISA에 납입한 가입자가 연 평균 10%의 이자 수익을 올렸다고 가정하면 &lt;br /&gt;
## 이 가입자는 매해 200만원의 이자소득을 얻은 것이다. &lt;br /&gt;
## 그러나 세금을 부과할 때에는 만기 인출한 해에 5년치 이자 수익을 일시에 올린 것으로 산정한다.&lt;br /&gt;
[[category:재테크]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2355</id>
		<title>ISA</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2355"/>
		<updated>2026-02-26T23:53:09Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* ISA 개인종합자산관리계좌 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= ISA 개인종합자산관리계좌 =&lt;br /&gt;
{{틀:고지상자&lt;br /&gt;
|제목= 개인종합자산관리계좌(Individual Savings Account)&lt;br /&gt;
|내용=* 다양한 금융상품을 한 계좌에서 통합관리하여 절세효과&lt;br /&gt;
* ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금 정산&lt;br /&gt;
* 연금저축이 아니다 =&amp;gt; 단기,목돈마련이 목적&lt;br /&gt;
* 개별 주식 투자가 가능하다.(IRP,연금저축은 개별주식 투자 불가능)&lt;br /&gt;
* 배당주 투자에 적합(종합금융소득세 절세효과)&lt;br /&gt;
** 국내상장 해외etf는 매매차익 + 배당소득세 합산하여 새금 부과&lt;br /&gt;
* 19세 이상 ,15세 이상(근로소득자) 가능&lt;br /&gt;
}}&lt;br /&gt;
== ISA의 종류 ==&lt;br /&gt;
=== 일임형 ===&lt;br /&gt;
# 고객이 전문가에게 자산운용을 일임하는 유형&lt;br /&gt;
 &lt;br /&gt;
=== 신탁형 ===&lt;br /&gt;
# 고객이 직접 운용 지시를 하여 자산운용을 하는 것&lt;br /&gt;
# 중간에 은행 등 전문가를 두고 자산을 관리하는 형식&lt;br /&gt;
&lt;br /&gt;
=== 중개형 (일반적으로 선호)===&lt;br /&gt;
# 일임형 또는 신탁형과는 다르게, 전적으로 본인이 계좌를 운용할 수 있는 계좌&lt;br /&gt;
# 포트폴리오 구성, 리밸런싱 등 모든 투자결정을 본인 재량에 따라 직접 해야함. &lt;br /&gt;
# ISA계좌를 보편적으로 이야기하는 것은 중개형입니다.&lt;br /&gt;
&lt;br /&gt;
== 세금 산정 예시 ==&lt;br /&gt;
* 국내상장 해외etf는 매매차익 과 배당금 합산하여 세금 발생&lt;br /&gt;
* 국내주식형 ETF는 매매차익이 원래 비과세라 배당금 부분에서만 세금 발생&lt;br /&gt;
&lt;br /&gt;
=== 국내상장한 해외etf (타이거 s&amp;amp;p500) ===&lt;br /&gt;
일반형 isa 계좌에서 타이거s&amp;amp;p500 etf로 매매차익 2000만원 과 배당금 1000만원 의 수익을 얻었다면 내야하는 세금은 총 얼마야?&lt;br /&gt;
&lt;br /&gt;
총 수익          3,000만원&lt;br /&gt;
비과세 한도   -    200만원&lt;br /&gt;
─────────────────────────&lt;br /&gt;
과세 대상        2,800만원&lt;br /&gt;
× 9.9% (분리과세)&lt;br /&gt;
─────────────────────────&lt;br /&gt;
납부 세금   =    277.2만원&lt;br /&gt;
&lt;br /&gt;
=== 국내 ETF(타이거 2차전지 top10) ===&lt;br /&gt;
일반형 isa 계좌에서 타이거 2차전지 top10 etf로 매매차익 2000만원 과 배당금 1000만원 의 수익을 얻었다면 내야하는 세금은 총 얼마야?&lt;br /&gt;
&lt;br /&gt;
과세대상 수익      1,000만원  (배당금만)&lt;br /&gt;
비과세 한도   -     200만원&lt;br /&gt;
─────────────────────────&lt;br /&gt;
과세 대상           800만원&lt;br /&gt;
× 9.9% (분리과세)&lt;br /&gt;
─────────────────────────&lt;br /&gt;
납부 세금   =       79.2만원&lt;br /&gt;
&lt;br /&gt;
== ISA 장단점 ==&lt;br /&gt;
=== 장점 ===&lt;br /&gt;
==== 세제 혜택 ====&lt;br /&gt;
===== 비과세 =====&lt;br /&gt;
# ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금을 정산&lt;br /&gt;
# 최종 과세대상 소득 중 200만 원까지는 비과세, 200만 원을 초과하는 금액에 대해서는 9.9%(지방소득세 포함)분리과세 적용  &lt;br /&gt;
## 최대 400만원의 비과세 혜택을 받을 수 있다.&lt;br /&gt;
## 비과세 혜택은 일반형/서민형/농어민형에 따라 다르게 적용. &lt;br /&gt;
## 총급여액이 5000만원 이하인 근로자 또는 종합소득이 3800만원 이하인 사업자는 400만원이 적용됨.&lt;br /&gt;
# 주의할 점은, 비과세 한도가 가입기간 전체에 적용되는 내용이라는 것. &lt;br /&gt;
#: - 납입한도가 연 기준으로 적용되기 때문에 많은 분들 이 비과세 한도도 연간 200만원(400만원)이라고 착각하시지만, 3년 이니 총 600만원 비과세되는 것이 아니다&lt;br /&gt;
#: * 흔한 착각 vs 실제&lt;br /&gt;
#:❌ 착각하는 사람들의 생각&lt;br /&gt;
#:“납입한도가 연 2,000만원이니까, 비과세도 매년 200만원씩 적용되겠지?”&lt;br /&gt;
#:→ 3년 가입 시 총 600만원 비과세?&lt;br /&gt;
#:✅ 실제&lt;br /&gt;
#:3년을 가입하든, 5년을 가입하든 비과세는 딱 200만원(서민형 400만원) 한 번만&lt;br /&gt;
&lt;br /&gt;
===== 분리과세 =====&lt;br /&gt;
# 비과세 혜택을 초과하는 금액은 9.9%로 분리과세(종합과세되지 않은것도 엄청남 혜택임)&lt;br /&gt;
# 금융소득에 대한 소득세율이 15.4%인 점을 고려 하면 비과세 금액을 초과(9.9%)하더라도 ISA 계좌가 훨씬 유리. &lt;br /&gt;
# 금융상품에서 발생한 소득이 분리과세 됨으로써 종합금융과세 될 확률이 줄어든다는 장점&lt;br /&gt;
===== 연금계좌로 전환시 추가 공제 =====&lt;br /&gt;
# 만기에 계좌에 보유한 자금을 연금계좌로 전환할 경우에는 전환금액의 10% 만큼의 세금을 300만원을 추가로 공제.&lt;br /&gt;
&lt;br /&gt;
==== 손익 통산 ====&lt;br /&gt;
# 일반 계좌에서 A금융상품에 대해 200만원의 손실을 봤고, B금융상품에 대해 100만원의 이익을 봤으면 A의 손실에 대해서는 과세하지 않고 B의 이익에 대해서는 과세 하게 됨.(즉  계좌가 개별로 처리됨)&lt;br /&gt;
#: =&amp;gt; 결과적으로 100만원의 손실을 봤음에 도 불구하고 또 세금을 내야됨.&lt;br /&gt;
# ISA 계좌를 통해서 A와 B에 투자한 경우, 각각에서 발생한 손익을 통산하여 총 이익을 -100만원 으로 보아 세금을 매기지 않습니다. &lt;br /&gt;
#: =&amp;gt; 이를 이용해서  연말에 손실을 본 주식을 매각하는 등 세율구간 조정도 가능&lt;br /&gt;
==== IRP/연금저축계좌로 이관시 혜택 ====&lt;br /&gt;
# ISA 만기자금을 연금계좌로 이체할 경우 ISA에서의 이체 금액만큼 연금계좌의 납입 한도를 추가로 늘려준다.&lt;br /&gt;
## 원래 연금계좌는 연금저축과 IRP를 합쳐서 연간 1800만원까지만 납입이 가능하다.&lt;br /&gt;
## 이미 연금계좌 납입 한도(연금저축의 경우 연 1800만원, IRP의 경우도 연 1800만원까지만 납입 가능하다.)를 다 채운 사람이라도 ISA 만기 자금을 연금계좌로 이체하는데 아무런 제한이 없다는 것이다.&lt;br /&gt;
# 자금 이체는 ISA 계약기간이 만료된 날로부터 60일 이내에 해야 한다. &lt;br /&gt;
# 만기자금의 일부만 이체하는 것도 가능하다.&lt;br /&gt;
===== 추가 세액 공제 =====&lt;br /&gt;
# 연금계좌로 이체한 만기자금 중 10%, 최대 300만원 세액공제&lt;br /&gt;
# 연소득이 4천만원(근로소득만 있으면 총급여 5천5백만원) 이하면 세액공제율은 16.5%, 이보다 많으면 13.2% 공제. &lt;br /&gt;
## 예시) isa 만기 자금 3천만원을 연금 계좌에 이체할 경우&lt;br /&gt;
### 세액공제 기준이 되는 금액은 10%인 300만원&lt;br /&gt;
### 기준보다 소득이 적은 사람은 300만원×16.5%=49만5천원 &lt;br /&gt;
### 기준보다 소득이 많은 사람은 300만원×13.2%=39만6천원의 세금 환급&lt;br /&gt;
# ISA 보다 더 적은 세금 적용&lt;br /&gt;
## ISA 만기자금을  연금계좌로 이체한 자금은 추후(55세 이후) 연금을 수령할 때 ISA계좌보다 더 낮은 세율인 3.3~5.5%의 연금소득세가 부과&lt;br /&gt;
&lt;br /&gt;
=== 단점 ===&lt;br /&gt;
* 이 모든 혜택에도 불구하고, 개인종합자산관리계좌 를 이용할 때 반드시 고려해야 할 사항.&lt;br /&gt;
==== 해외주식 직접 투자 불가능 (ETF만 거래 가능) ====&lt;br /&gt;
# 일반계좌에서는 국내주식도, 해외주식도 모두 직접 투자할 수 있는데 반해서 ISA 계좌에서는 해외주식 직접투자가 불가능함.&lt;br /&gt;
#: =&amp;gt; 그렇다면 국내주식만 비과세 혜택 등을 얻을수 있는것인지? 꼭 그런것은 아니다.&lt;br /&gt;
&lt;br /&gt;
==== 납입한도와 의무가입기간 ====&lt;br /&gt;
# 연 최대 2000만원 &lt;br /&gt;
# 의무가입기간 3년&lt;br /&gt;
# 최대 5년까지 만기를 연장 신청할 수 있음.&lt;br /&gt;
# 즉, 1인당 계좌에 최대 1억원을 한도로 납입할 수 있음. &lt;br /&gt;
# 만약 해당연도에 최대 금액을 납입하지 않는다면 다음 해로 이월됨&lt;br /&gt;
# 가입대상-금융소득 종합과세 대상자 가입불가 (2024년 개편안, 금융소득종합과세 대상자 가입 허용_국내주식,국내주식형 펀드투자만 가능)&lt;br /&gt;
&lt;br /&gt;
==== 중도해지/중도인출 ====&lt;br /&gt;
# 의무가입기간이 있기 때문에 가입중에 긴급하게 인출이 필요할 경우에 ISA 계좌는 중도해지할 경우 지금까지 받은 세금 혜택을 모두 반납 함&lt;br /&gt;
#: 사실상 세제 혜택을 받지 못하는 최악의 상황 발생 &lt;br /&gt;
# 추천 하는 방법은 바로 중도인출을 적절히 활용하는 것. &lt;br /&gt;
#: =&amp;gt; 중도해지는 안되지만, 납부한 원금에 한해서 중도인출은 되기 때문.&lt;br /&gt;
&lt;br /&gt;
=== 문제점 === &lt;br /&gt;
==== 건강보험료가 인상된다는데? ====&lt;br /&gt;
# 검색해보면 ISA 계좌를 이용하면 건강보험료가 인 상된다는 내용이 많음. 그런데 알고 보면 이것은 계좌 가입 여부와 무관하게 &amp;quot;금융소득이 많이 발생하면 소득이 늘기 때문에 건강보험료가 인상된다”는 취지 임. 가입만으로 건강보험료가 인상되는 일은 없음.&lt;br /&gt;
&lt;br /&gt;
==== 건강보험료 피부양자인경우 자격 박탈 ====&lt;br /&gt;
# 만기수령시에 분리과세되는 금융소득이 한꺼번에 생기게되는데 문제는 이 금융소득이 연 천만원을 넘어가게 되면 ISA가입자가 건강보험료 피부양자인 경우 피부양자 자격요건에서 박탈되거나, &lt;br /&gt;
# ISA 가입자가 지역건강보험 가입자인 경우에는 소득으로 합산되는 문제가 발생할 수 있다는 점이다. &lt;br /&gt;
## 예컨대 5년 동안 매년 2,000만원씩 ISA에 납입한 가입자가 연 평균 10%의 이자 수익을 올렸다고 가정하면 &lt;br /&gt;
## 이 가입자는 매해 200만원의 이자소득을 얻은 것이다. &lt;br /&gt;
## 그러나 세금을 부과할 때에는 만기 인출한 해에 5년치 이자 수익을 일시에 올린 것으로 산정한다.&lt;br /&gt;
[[category:재테크]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2354</id>
		<title>ISA</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2354"/>
		<updated>2026-02-26T23:50:08Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 세금 산정 예시 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= ISA 개인종합자산관리계좌 =&lt;br /&gt;
{{틀:고지상자&lt;br /&gt;
|제목= 개인종합자산관리계좌(Individual Savings Account)&lt;br /&gt;
|내용=* 다양한 금융상품을 한 계좌에서 통합관리하여 절세효과&lt;br /&gt;
* ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금 정산&lt;br /&gt;
* 연금저축이 아니다 =&amp;gt; 단기,목돈마련이 목적&lt;br /&gt;
* 개별 주식 투자가 가능하다.(IRP,연금저축은 개별주식 투자 불가능)&lt;br /&gt;
* 배당주 투자에 적합(종합금융소득세 절세효과)&lt;br /&gt;
* 19세 이상 ,15세 이상(근로소득자) 가능&lt;br /&gt;
}}&lt;br /&gt;
== ISA의 종류 ==&lt;br /&gt;
=== 일임형 ===&lt;br /&gt;
# 고객이 전문가에게 자산운용을 일임하는 유형&lt;br /&gt;
 &lt;br /&gt;
=== 신탁형 ===&lt;br /&gt;
# 고객이 직접 운용 지시를 하여 자산운용을 하는 것&lt;br /&gt;
# 중간에 은행 등 전문가를 두고 자산을 관리하는 형식&lt;br /&gt;
&lt;br /&gt;
=== 중개형 (일반적으로 선호)===&lt;br /&gt;
# 일임형 또는 신탁형과는 다르게, 전적으로 본인이 계좌를 운용할 수 있는 계좌&lt;br /&gt;
# 포트폴리오 구성, 리밸런싱 등 모든 투자결정을 본인 재량에 따라 직접 해야함. &lt;br /&gt;
# ISA계좌를 보편적으로 이야기하는 것은 중개형입니다.&lt;br /&gt;
&lt;br /&gt;
== 세금 산정 예시 ==&lt;br /&gt;
* 국내상장 해외etf는 매매차익 과 배당금 합산하여 세금 발생&lt;br /&gt;
* 국내주식형 ETF는 매매차익이 원래 비과세라 배당금 부분에서만 세금 발생&lt;br /&gt;
&lt;br /&gt;
=== 국내상장한 해외etf (타이거 s&amp;amp;p500) ===&lt;br /&gt;
일반형 isa 계좌에서 타이거s&amp;amp;p500 etf로 매매차익 2000만원 과 배당금 1000만원 의 수익을 얻었다면 내야하는 세금은 총 얼마야?&lt;br /&gt;
&lt;br /&gt;
총 수익          3,000만원&lt;br /&gt;
비과세 한도   -    200만원&lt;br /&gt;
─────────────────────────&lt;br /&gt;
과세 대상        2,800만원&lt;br /&gt;
× 9.9% (분리과세)&lt;br /&gt;
─────────────────────────&lt;br /&gt;
납부 세금   =    277.2만원&lt;br /&gt;
&lt;br /&gt;
=== 국내 ETF(타이거 2차전지 top10) ===&lt;br /&gt;
일반형 isa 계좌에서 타이거 2차전지 top10 etf로 매매차익 2000만원 과 배당금 1000만원 의 수익을 얻었다면 내야하는 세금은 총 얼마야?&lt;br /&gt;
&lt;br /&gt;
과세대상 수익      1,000만원  (배당금만)&lt;br /&gt;
비과세 한도   -     200만원&lt;br /&gt;
─────────────────────────&lt;br /&gt;
과세 대상           800만원&lt;br /&gt;
× 9.9% (분리과세)&lt;br /&gt;
─────────────────────────&lt;br /&gt;
납부 세금   =       79.2만원&lt;br /&gt;
&lt;br /&gt;
== ISA 장단점 ==&lt;br /&gt;
=== 장점 ===&lt;br /&gt;
==== 세제 혜택 ====&lt;br /&gt;
===== 비과세 =====&lt;br /&gt;
# ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금을 정산&lt;br /&gt;
# 최종 과세대상 소득 중 200만 원까지는 비과세, 200만 원을 초과하는 금액에 대해서는 9.9%(지방소득세 포함)분리과세 적용  &lt;br /&gt;
## 최대 400만원의 비과세 혜택을 받을 수 있다.&lt;br /&gt;
## 비과세 혜택은 일반형/서민형/농어민형에 따라 다르게 적용. &lt;br /&gt;
## 총급여액이 5000만원 이하인 근로자 또는 종합소득이 3800만원 이하인 사업자는 400만원이 적용됨.&lt;br /&gt;
# 주의할 점은, 비과세 한도가 가입기간 전체에 적용되는 내용이라는 것. &lt;br /&gt;
#: - 납입한도가 연 기준으로 적용되기 때문에 많은 분들 이 비과세 한도도 연간 200만원(400만원)이라고 착각하시지만, 3년 이니 총 600만원 비과세되는 것이 아니다&lt;br /&gt;
#: * 흔한 착각 vs 실제&lt;br /&gt;
#:❌ 착각하는 사람들의 생각&lt;br /&gt;
#:“납입한도가 연 2,000만원이니까, 비과세도 매년 200만원씩 적용되겠지?”&lt;br /&gt;
#:→ 3년 가입 시 총 600만원 비과세?&lt;br /&gt;
#:✅ 실제&lt;br /&gt;
#:3년을 가입하든, 5년을 가입하든 비과세는 딱 200만원(서민형 400만원) 한 번만&lt;br /&gt;
&lt;br /&gt;
===== 분리과세 =====&lt;br /&gt;
# 비과세 혜택을 초과하는 금액은 9.9%로 분리과세(종합과세되지 않은것도 엄청남 혜택임)&lt;br /&gt;
# 금융소득에 대한 소득세율이 15.4%인 점을 고려 하면 비과세 금액을 초과(9.9%)하더라도 ISA 계좌가 훨씬 유리. &lt;br /&gt;
# 금융상품에서 발생한 소득이 분리과세 됨으로써 종합금융과세 될 확률이 줄어든다는 장점&lt;br /&gt;
===== 연금계좌로 전환시 추가 공제 =====&lt;br /&gt;
# 만기에 계좌에 보유한 자금을 연금계좌로 전환할 경우에는 전환금액의 10% 만큼의 세금을 300만원을 추가로 공제.&lt;br /&gt;
&lt;br /&gt;
==== 손익 통산 ====&lt;br /&gt;
# 일반 계좌에서 A금융상품에 대해 200만원의 손실을 봤고, B금융상품에 대해 100만원의 이익을 봤으면 A의 손실에 대해서는 과세하지 않고 B의 이익에 대해서는 과세 하게 됨.(즉  계좌가 개별로 처리됨)&lt;br /&gt;
#: =&amp;gt; 결과적으로 100만원의 손실을 봤음에 도 불구하고 또 세금을 내야됨.&lt;br /&gt;
# ISA 계좌를 통해서 A와 B에 투자한 경우, 각각에서 발생한 손익을 통산하여 총 이익을 -100만원 으로 보아 세금을 매기지 않습니다. &lt;br /&gt;
#: =&amp;gt; 이를 이용해서  연말에 손실을 본 주식을 매각하는 등 세율구간 조정도 가능&lt;br /&gt;
==== IRP/연금저축계좌로 이관시 혜택 ====&lt;br /&gt;
# ISA 만기자금을 연금계좌로 이체할 경우 ISA에서의 이체 금액만큼 연금계좌의 납입 한도를 추가로 늘려준다.&lt;br /&gt;
## 원래 연금계좌는 연금저축과 IRP를 합쳐서 연간 1800만원까지만 납입이 가능하다.&lt;br /&gt;
## 이미 연금계좌 납입 한도(연금저축의 경우 연 1800만원, IRP의 경우도 연 1800만원까지만 납입 가능하다.)를 다 채운 사람이라도 ISA 만기 자금을 연금계좌로 이체하는데 아무런 제한이 없다는 것이다.&lt;br /&gt;
# 자금 이체는 ISA 계약기간이 만료된 날로부터 60일 이내에 해야 한다. &lt;br /&gt;
# 만기자금의 일부만 이체하는 것도 가능하다.&lt;br /&gt;
===== 추가 세액 공제 =====&lt;br /&gt;
# 연금계좌로 이체한 만기자금 중 10%, 최대 300만원 세액공제&lt;br /&gt;
# 연소득이 4천만원(근로소득만 있으면 총급여 5천5백만원) 이하면 세액공제율은 16.5%, 이보다 많으면 13.2% 공제. &lt;br /&gt;
## 예시) isa 만기 자금 3천만원을 연금 계좌에 이체할 경우&lt;br /&gt;
### 세액공제 기준이 되는 금액은 10%인 300만원&lt;br /&gt;
### 기준보다 소득이 적은 사람은 300만원×16.5%=49만5천원 &lt;br /&gt;
### 기준보다 소득이 많은 사람은 300만원×13.2%=39만6천원의 세금 환급&lt;br /&gt;
# ISA 보다 더 적은 세금 적용&lt;br /&gt;
## ISA 만기자금을  연금계좌로 이체한 자금은 추후(55세 이후) 연금을 수령할 때 ISA계좌보다 더 낮은 세율인 3.3~5.5%의 연금소득세가 부과&lt;br /&gt;
&lt;br /&gt;
=== 단점 ===&lt;br /&gt;
* 이 모든 혜택에도 불구하고, 개인종합자산관리계좌 를 이용할 때 반드시 고려해야 할 사항.&lt;br /&gt;
==== 해외주식 직접 투자 불가능 (ETF만 거래 가능) ====&lt;br /&gt;
# 일반계좌에서는 국내주식도, 해외주식도 모두 직접 투자할 수 있는데 반해서 ISA 계좌에서는 해외주식 직접투자가 불가능함.&lt;br /&gt;
#: =&amp;gt; 그렇다면 국내주식만 비과세 혜택 등을 얻을수 있는것인지? 꼭 그런것은 아니다.&lt;br /&gt;
&lt;br /&gt;
==== 납입한도와 의무가입기간 ====&lt;br /&gt;
# 연 최대 2000만원 &lt;br /&gt;
# 의무가입기간 3년&lt;br /&gt;
# 최대 5년까지 만기를 연장 신청할 수 있음.&lt;br /&gt;
# 즉, 1인당 계좌에 최대 1억원을 한도로 납입할 수 있음. &lt;br /&gt;
# 만약 해당연도에 최대 금액을 납입하지 않는다면 다음 해로 이월됨&lt;br /&gt;
# 가입대상-금융소득 종합과세 대상자 가입불가 (2024년 개편안, 금융소득종합과세 대상자 가입 허용_국내주식,국내주식형 펀드투자만 가능)&lt;br /&gt;
&lt;br /&gt;
==== 중도해지/중도인출 ====&lt;br /&gt;
# 의무가입기간이 있기 때문에 가입중에 긴급하게 인출이 필요할 경우에 ISA 계좌는 중도해지할 경우 지금까지 받은 세금 혜택을 모두 반납 함&lt;br /&gt;
#: 사실상 세제 혜택을 받지 못하는 최악의 상황 발생 &lt;br /&gt;
# 추천 하는 방법은 바로 중도인출을 적절히 활용하는 것. &lt;br /&gt;
#: =&amp;gt; 중도해지는 안되지만, 납부한 원금에 한해서 중도인출은 되기 때문.&lt;br /&gt;
&lt;br /&gt;
=== 문제점 === &lt;br /&gt;
==== 건강보험료가 인상된다는데? ====&lt;br /&gt;
# 검색해보면 ISA 계좌를 이용하면 건강보험료가 인 상된다는 내용이 많음. 그런데 알고 보면 이것은 계좌 가입 여부와 무관하게 &amp;quot;금융소득이 많이 발생하면 소득이 늘기 때문에 건강보험료가 인상된다”는 취지 임. 가입만으로 건강보험료가 인상되는 일은 없음.&lt;br /&gt;
&lt;br /&gt;
==== 건강보험료 피부양자인경우 자격 박탈 ====&lt;br /&gt;
# 만기수령시에 분리과세되는 금융소득이 한꺼번에 생기게되는데 문제는 이 금융소득이 연 천만원을 넘어가게 되면 ISA가입자가 건강보험료 피부양자인 경우 피부양자 자격요건에서 박탈되거나, &lt;br /&gt;
# ISA 가입자가 지역건강보험 가입자인 경우에는 소득으로 합산되는 문제가 발생할 수 있다는 점이다. &lt;br /&gt;
## 예컨대 5년 동안 매년 2,000만원씩 ISA에 납입한 가입자가 연 평균 10%의 이자 수익을 올렸다고 가정하면 &lt;br /&gt;
## 이 가입자는 매해 200만원의 이자소득을 얻은 것이다. &lt;br /&gt;
## 그러나 세금을 부과할 때에는 만기 인출한 해에 5년치 이자 수익을 일시에 올린 것으로 산정한다.&lt;br /&gt;
[[category:재테크]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2353</id>
		<title>ISA</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2353"/>
		<updated>2026-02-26T23:46:35Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 세금 산정 예시 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= ISA 개인종합자산관리계좌 =&lt;br /&gt;
{{틀:고지상자&lt;br /&gt;
|제목= 개인종합자산관리계좌(Individual Savings Account)&lt;br /&gt;
|내용=* 다양한 금융상품을 한 계좌에서 통합관리하여 절세효과&lt;br /&gt;
* ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금 정산&lt;br /&gt;
* 연금저축이 아니다 =&amp;gt; 단기,목돈마련이 목적&lt;br /&gt;
* 개별 주식 투자가 가능하다.(IRP,연금저축은 개별주식 투자 불가능)&lt;br /&gt;
* 배당주 투자에 적합(종합금융소득세 절세효과)&lt;br /&gt;
* 19세 이상 ,15세 이상(근로소득자) 가능&lt;br /&gt;
}}&lt;br /&gt;
== ISA의 종류 ==&lt;br /&gt;
=== 일임형 ===&lt;br /&gt;
# 고객이 전문가에게 자산운용을 일임하는 유형&lt;br /&gt;
 &lt;br /&gt;
=== 신탁형 ===&lt;br /&gt;
# 고객이 직접 운용 지시를 하여 자산운용을 하는 것&lt;br /&gt;
# 중간에 은행 등 전문가를 두고 자산을 관리하는 형식&lt;br /&gt;
&lt;br /&gt;
=== 중개형 (일반적으로 선호)===&lt;br /&gt;
# 일임형 또는 신탁형과는 다르게, 전적으로 본인이 계좌를 운용할 수 있는 계좌&lt;br /&gt;
# 포트폴리오 구성, 리밸런싱 등 모든 투자결정을 본인 재량에 따라 직접 해야함. &lt;br /&gt;
# ISA계좌를 보편적으로 이야기하는 것은 중개형입니다.&lt;br /&gt;
&lt;br /&gt;
== 세금 산정 예시 ==&lt;br /&gt;
=== 국내상장한 해외etf (타이거 s&amp;amp;p500) ===&lt;br /&gt;
일반형 isa 계좌에서 타이거s&amp;amp;p500 etf로 매매차익 2000만원 과 배당금 1000만원 의 수익을 얻었다면 내야하는 세금은 총 얼마야?&lt;br /&gt;
&lt;br /&gt;
총 수익          3,000만원&lt;br /&gt;
비과세 한도   -    200만원&lt;br /&gt;
─────────────────────────&lt;br /&gt;
과세 대상        2,800만원&lt;br /&gt;
× 9.9% (분리과세)&lt;br /&gt;
─────────────────────────&lt;br /&gt;
납부 세금   =    277.2만원&lt;br /&gt;
&lt;br /&gt;
=== 국내 ETF(타이거 2차전지 top10) ===&lt;br /&gt;
일반형 isa 계좌에서 타이거 2차전지 top10 etf로 매매차익 2000만원 과 배당금 1000만원 의 수익을 얻었다면 내야하는 세금은 총 얼마야?&lt;br /&gt;
&lt;br /&gt;
과세대상 수익      1,000만원  (배당금만)&lt;br /&gt;
비과세 한도   -     200만원&lt;br /&gt;
─────────────────────────&lt;br /&gt;
과세 대상           800만원&lt;br /&gt;
× 9.9% (분리과세)&lt;br /&gt;
─────────────────────────&lt;br /&gt;
납부 세금   =       79.2만원&lt;br /&gt;
&lt;br /&gt;
== ISA 장단점 ==&lt;br /&gt;
=== 장점 ===&lt;br /&gt;
==== 세제 혜택 ====&lt;br /&gt;
===== 비과세 =====&lt;br /&gt;
# ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금을 정산&lt;br /&gt;
# 최종 과세대상 소득 중 200만 원까지는 비과세, 200만 원을 초과하는 금액에 대해서는 9.9%(지방소득세 포함)분리과세 적용  &lt;br /&gt;
## 최대 400만원의 비과세 혜택을 받을 수 있다.&lt;br /&gt;
## 비과세 혜택은 일반형/서민형/농어민형에 따라 다르게 적용. &lt;br /&gt;
## 총급여액이 5000만원 이하인 근로자 또는 종합소득이 3800만원 이하인 사업자는 400만원이 적용됨.&lt;br /&gt;
# 주의할 점은, 비과세 한도가 가입기간 전체에 적용되는 내용이라는 것. &lt;br /&gt;
#: - 납입한도가 연 기준으로 적용되기 때문에 많은 분들 이 비과세 한도도 연간 200만원(400만원)이라고 착각하시지만, 3년 이니 총 600만원 비과세되는 것이 아니다&lt;br /&gt;
#: * 흔한 착각 vs 실제&lt;br /&gt;
#:❌ 착각하는 사람들의 생각&lt;br /&gt;
#:“납입한도가 연 2,000만원이니까, 비과세도 매년 200만원씩 적용되겠지?”&lt;br /&gt;
#:→ 3년 가입 시 총 600만원 비과세?&lt;br /&gt;
#:✅ 실제&lt;br /&gt;
#:3년을 가입하든, 5년을 가입하든 비과세는 딱 200만원(서민형 400만원) 한 번만&lt;br /&gt;
&lt;br /&gt;
===== 분리과세 =====&lt;br /&gt;
# 비과세 혜택을 초과하는 금액은 9.9%로 분리과세(종합과세되지 않은것도 엄청남 혜택임)&lt;br /&gt;
# 금융소득에 대한 소득세율이 15.4%인 점을 고려 하면 비과세 금액을 초과(9.9%)하더라도 ISA 계좌가 훨씬 유리. &lt;br /&gt;
# 금융상품에서 발생한 소득이 분리과세 됨으로써 종합금융과세 될 확률이 줄어든다는 장점&lt;br /&gt;
===== 연금계좌로 전환시 추가 공제 =====&lt;br /&gt;
# 만기에 계좌에 보유한 자금을 연금계좌로 전환할 경우에는 전환금액의 10% 만큼의 세금을 300만원을 추가로 공제.&lt;br /&gt;
&lt;br /&gt;
==== 손익 통산 ====&lt;br /&gt;
# 일반 계좌에서 A금융상품에 대해 200만원의 손실을 봤고, B금융상품에 대해 100만원의 이익을 봤으면 A의 손실에 대해서는 과세하지 않고 B의 이익에 대해서는 과세 하게 됨.(즉  계좌가 개별로 처리됨)&lt;br /&gt;
#: =&amp;gt; 결과적으로 100만원의 손실을 봤음에 도 불구하고 또 세금을 내야됨.&lt;br /&gt;
# ISA 계좌를 통해서 A와 B에 투자한 경우, 각각에서 발생한 손익을 통산하여 총 이익을 -100만원 으로 보아 세금을 매기지 않습니다. &lt;br /&gt;
#: =&amp;gt; 이를 이용해서  연말에 손실을 본 주식을 매각하는 등 세율구간 조정도 가능&lt;br /&gt;
==== IRP/연금저축계좌로 이관시 혜택 ====&lt;br /&gt;
# ISA 만기자금을 연금계좌로 이체할 경우 ISA에서의 이체 금액만큼 연금계좌의 납입 한도를 추가로 늘려준다.&lt;br /&gt;
## 원래 연금계좌는 연금저축과 IRP를 합쳐서 연간 1800만원까지만 납입이 가능하다.&lt;br /&gt;
## 이미 연금계좌 납입 한도(연금저축의 경우 연 1800만원, IRP의 경우도 연 1800만원까지만 납입 가능하다.)를 다 채운 사람이라도 ISA 만기 자금을 연금계좌로 이체하는데 아무런 제한이 없다는 것이다.&lt;br /&gt;
# 자금 이체는 ISA 계약기간이 만료된 날로부터 60일 이내에 해야 한다. &lt;br /&gt;
# 만기자금의 일부만 이체하는 것도 가능하다.&lt;br /&gt;
===== 추가 세액 공제 =====&lt;br /&gt;
# 연금계좌로 이체한 만기자금 중 10%, 최대 300만원 세액공제&lt;br /&gt;
# 연소득이 4천만원(근로소득만 있으면 총급여 5천5백만원) 이하면 세액공제율은 16.5%, 이보다 많으면 13.2% 공제. &lt;br /&gt;
## 예시) isa 만기 자금 3천만원을 연금 계좌에 이체할 경우&lt;br /&gt;
### 세액공제 기준이 되는 금액은 10%인 300만원&lt;br /&gt;
### 기준보다 소득이 적은 사람은 300만원×16.5%=49만5천원 &lt;br /&gt;
### 기준보다 소득이 많은 사람은 300만원×13.2%=39만6천원의 세금 환급&lt;br /&gt;
# ISA 보다 더 적은 세금 적용&lt;br /&gt;
## ISA 만기자금을  연금계좌로 이체한 자금은 추후(55세 이후) 연금을 수령할 때 ISA계좌보다 더 낮은 세율인 3.3~5.5%의 연금소득세가 부과&lt;br /&gt;
&lt;br /&gt;
=== 단점 ===&lt;br /&gt;
* 이 모든 혜택에도 불구하고, 개인종합자산관리계좌 를 이용할 때 반드시 고려해야 할 사항.&lt;br /&gt;
==== 해외주식 직접 투자 불가능 (ETF만 거래 가능) ====&lt;br /&gt;
# 일반계좌에서는 국내주식도, 해외주식도 모두 직접 투자할 수 있는데 반해서 ISA 계좌에서는 해외주식 직접투자가 불가능함.&lt;br /&gt;
#: =&amp;gt; 그렇다면 국내주식만 비과세 혜택 등을 얻을수 있는것인지? 꼭 그런것은 아니다.&lt;br /&gt;
&lt;br /&gt;
==== 납입한도와 의무가입기간 ====&lt;br /&gt;
# 연 최대 2000만원 &lt;br /&gt;
# 의무가입기간 3년&lt;br /&gt;
# 최대 5년까지 만기를 연장 신청할 수 있음.&lt;br /&gt;
# 즉, 1인당 계좌에 최대 1억원을 한도로 납입할 수 있음. &lt;br /&gt;
# 만약 해당연도에 최대 금액을 납입하지 않는다면 다음 해로 이월됨&lt;br /&gt;
# 가입대상-금융소득 종합과세 대상자 가입불가 (2024년 개편안, 금융소득종합과세 대상자 가입 허용_국내주식,국내주식형 펀드투자만 가능)&lt;br /&gt;
&lt;br /&gt;
==== 중도해지/중도인출 ====&lt;br /&gt;
# 의무가입기간이 있기 때문에 가입중에 긴급하게 인출이 필요할 경우에 ISA 계좌는 중도해지할 경우 지금까지 받은 세금 혜택을 모두 반납 함&lt;br /&gt;
#: 사실상 세제 혜택을 받지 못하는 최악의 상황 발생 &lt;br /&gt;
# 추천 하는 방법은 바로 중도인출을 적절히 활용하는 것. &lt;br /&gt;
#: =&amp;gt; 중도해지는 안되지만, 납부한 원금에 한해서 중도인출은 되기 때문.&lt;br /&gt;
&lt;br /&gt;
=== 문제점 === &lt;br /&gt;
==== 건강보험료가 인상된다는데? ====&lt;br /&gt;
# 검색해보면 ISA 계좌를 이용하면 건강보험료가 인 상된다는 내용이 많음. 그런데 알고 보면 이것은 계좌 가입 여부와 무관하게 &amp;quot;금융소득이 많이 발생하면 소득이 늘기 때문에 건강보험료가 인상된다”는 취지 임. 가입만으로 건강보험료가 인상되는 일은 없음.&lt;br /&gt;
&lt;br /&gt;
==== 건강보험료 피부양자인경우 자격 박탈 ====&lt;br /&gt;
# 만기수령시에 분리과세되는 금융소득이 한꺼번에 생기게되는데 문제는 이 금융소득이 연 천만원을 넘어가게 되면 ISA가입자가 건강보험료 피부양자인 경우 피부양자 자격요건에서 박탈되거나, &lt;br /&gt;
# ISA 가입자가 지역건강보험 가입자인 경우에는 소득으로 합산되는 문제가 발생할 수 있다는 점이다. &lt;br /&gt;
## 예컨대 5년 동안 매년 2,000만원씩 ISA에 납입한 가입자가 연 평균 10%의 이자 수익을 올렸다고 가정하면 &lt;br /&gt;
## 이 가입자는 매해 200만원의 이자소득을 얻은 것이다. &lt;br /&gt;
## 그러나 세금을 부과할 때에는 만기 인출한 해에 5년치 이자 수익을 일시에 올린 것으로 산정한다.&lt;br /&gt;
[[category:재테크]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2352</id>
		<title>ISA</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2352"/>
		<updated>2026-02-26T23:33:58Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* ISA의 종류 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= ISA 개인종합자산관리계좌 =&lt;br /&gt;
{{틀:고지상자&lt;br /&gt;
|제목= 개인종합자산관리계좌(Individual Savings Account)&lt;br /&gt;
|내용=* 다양한 금융상품을 한 계좌에서 통합관리하여 절세효과&lt;br /&gt;
* ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금 정산&lt;br /&gt;
* 연금저축이 아니다 =&amp;gt; 단기,목돈마련이 목적&lt;br /&gt;
* 개별 주식 투자가 가능하다.(IRP,연금저축은 개별주식 투자 불가능)&lt;br /&gt;
* 배당주 투자에 적합(종합금융소득세 절세효과)&lt;br /&gt;
* 19세 이상 ,15세 이상(근로소득자) 가능&lt;br /&gt;
}}&lt;br /&gt;
== ISA의 종류 ==&lt;br /&gt;
=== 일임형 ===&lt;br /&gt;
# 고객이 전문가에게 자산운용을 일임하는 유형&lt;br /&gt;
 &lt;br /&gt;
=== 신탁형 ===&lt;br /&gt;
# 고객이 직접 운용 지시를 하여 자산운용을 하는 것&lt;br /&gt;
# 중간에 은행 등 전문가를 두고 자산을 관리하는 형식&lt;br /&gt;
&lt;br /&gt;
=== 중개형 (일반적으로 선호)===&lt;br /&gt;
# 일임형 또는 신탁형과는 다르게, 전적으로 본인이 계좌를 운용할 수 있는 계좌&lt;br /&gt;
# 포트폴리오 구성, 리밸런싱 등 모든 투자결정을 본인 재량에 따라 직접 해야함. &lt;br /&gt;
# ISA계좌를 보편적으로 이야기하는 것은 중개형입니다.&lt;br /&gt;
&lt;br /&gt;
== 세금 산정 예시 ==&lt;br /&gt;
=== 국내상장한 해외etf (타이거 s&amp;amp;p500) ===&lt;br /&gt;
일반형 isa 계좌에서 타이거s&amp;amp;p500 etf로 매매차익 2000만원 과 배당금 1000만원 의 수익을 얻었다면 내야하는 세금은 총 얼마야?&lt;br /&gt;
&lt;br /&gt;
총 수익          3,000만원&lt;br /&gt;
비과세 한도   -    200만원&lt;br /&gt;
─────────────────────────&lt;br /&gt;
과세 대상        2,800만원&lt;br /&gt;
× 9.9% (분리과세)&lt;br /&gt;
─────────────────────────&lt;br /&gt;
납부 세금   =    277.2만원&lt;br /&gt;
&lt;br /&gt;
== ISA 장단점 ==&lt;br /&gt;
=== 장점 ===&lt;br /&gt;
==== 세제 혜택 ====&lt;br /&gt;
===== 비과세 =====&lt;br /&gt;
# ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금을 정산&lt;br /&gt;
# 최종 과세대상 소득 중 200만 원까지는 비과세, 200만 원을 초과하는 금액에 대해서는 9.9%(지방소득세 포함)분리과세 적용  &lt;br /&gt;
## 최대 400만원의 비과세 혜택을 받을 수 있다.&lt;br /&gt;
## 비과세 혜택은 일반형/서민형/농어민형에 따라 다르게 적용. &lt;br /&gt;
## 총급여액이 5000만원 이하인 근로자 또는 종합소득이 3800만원 이하인 사업자는 400만원이 적용됨.&lt;br /&gt;
# 주의할 점은, 비과세 한도가 가입기간 전체에 적용되는 내용이라는 것. &lt;br /&gt;
#: - 납입한도가 연 기준으로 적용되기 때문에 많은 분들 이 비과세 한도도 연간 200만원(400만원)이라고 착각하시지만, 3년 이니 총 600만원 비과세되는 것이 아니다&lt;br /&gt;
#: * 흔한 착각 vs 실제&lt;br /&gt;
#:❌ 착각하는 사람들의 생각&lt;br /&gt;
#:“납입한도가 연 2,000만원이니까, 비과세도 매년 200만원씩 적용되겠지?”&lt;br /&gt;
#:→ 3년 가입 시 총 600만원 비과세?&lt;br /&gt;
#:✅ 실제&lt;br /&gt;
#:3년을 가입하든, 5년을 가입하든 비과세는 딱 200만원(서민형 400만원) 한 번만&lt;br /&gt;
&lt;br /&gt;
===== 분리과세 =====&lt;br /&gt;
# 비과세 혜택을 초과하는 금액은 9.9%로 분리과세(종합과세되지 않은것도 엄청남 혜택임)&lt;br /&gt;
# 금융소득에 대한 소득세율이 15.4%인 점을 고려 하면 비과세 금액을 초과(9.9%)하더라도 ISA 계좌가 훨씬 유리. &lt;br /&gt;
# 금융상품에서 발생한 소득이 분리과세 됨으로써 종합금융과세 될 확률이 줄어든다는 장점&lt;br /&gt;
===== 연금계좌로 전환시 추가 공제 =====&lt;br /&gt;
# 만기에 계좌에 보유한 자금을 연금계좌로 전환할 경우에는 전환금액의 10% 만큼의 세금을 300만원을 추가로 공제.&lt;br /&gt;
&lt;br /&gt;
==== 손익 통산 ====&lt;br /&gt;
# 일반 계좌에서 A금융상품에 대해 200만원의 손실을 봤고, B금융상품에 대해 100만원의 이익을 봤으면 A의 손실에 대해서는 과세하지 않고 B의 이익에 대해서는 과세 하게 됨.(즉  계좌가 개별로 처리됨)&lt;br /&gt;
#: =&amp;gt; 결과적으로 100만원의 손실을 봤음에 도 불구하고 또 세금을 내야됨.&lt;br /&gt;
# ISA 계좌를 통해서 A와 B에 투자한 경우, 각각에서 발생한 손익을 통산하여 총 이익을 -100만원 으로 보아 세금을 매기지 않습니다. &lt;br /&gt;
#: =&amp;gt; 이를 이용해서  연말에 손실을 본 주식을 매각하는 등 세율구간 조정도 가능&lt;br /&gt;
==== IRP/연금저축계좌로 이관시 혜택 ====&lt;br /&gt;
# ISA 만기자금을 연금계좌로 이체할 경우 ISA에서의 이체 금액만큼 연금계좌의 납입 한도를 추가로 늘려준다.&lt;br /&gt;
## 원래 연금계좌는 연금저축과 IRP를 합쳐서 연간 1800만원까지만 납입이 가능하다.&lt;br /&gt;
## 이미 연금계좌 납입 한도(연금저축의 경우 연 1800만원, IRP의 경우도 연 1800만원까지만 납입 가능하다.)를 다 채운 사람이라도 ISA 만기 자금을 연금계좌로 이체하는데 아무런 제한이 없다는 것이다.&lt;br /&gt;
# 자금 이체는 ISA 계약기간이 만료된 날로부터 60일 이내에 해야 한다. &lt;br /&gt;
# 만기자금의 일부만 이체하는 것도 가능하다.&lt;br /&gt;
===== 추가 세액 공제 =====&lt;br /&gt;
# 연금계좌로 이체한 만기자금 중 10%, 최대 300만원 세액공제&lt;br /&gt;
# 연소득이 4천만원(근로소득만 있으면 총급여 5천5백만원) 이하면 세액공제율은 16.5%, 이보다 많으면 13.2% 공제. &lt;br /&gt;
## 예시) isa 만기 자금 3천만원을 연금 계좌에 이체할 경우&lt;br /&gt;
### 세액공제 기준이 되는 금액은 10%인 300만원&lt;br /&gt;
### 기준보다 소득이 적은 사람은 300만원×16.5%=49만5천원 &lt;br /&gt;
### 기준보다 소득이 많은 사람은 300만원×13.2%=39만6천원의 세금 환급&lt;br /&gt;
# ISA 보다 더 적은 세금 적용&lt;br /&gt;
## ISA 만기자금을  연금계좌로 이체한 자금은 추후(55세 이후) 연금을 수령할 때 ISA계좌보다 더 낮은 세율인 3.3~5.5%의 연금소득세가 부과&lt;br /&gt;
&lt;br /&gt;
=== 단점 ===&lt;br /&gt;
* 이 모든 혜택에도 불구하고, 개인종합자산관리계좌 를 이용할 때 반드시 고려해야 할 사항.&lt;br /&gt;
==== 해외주식 직접 투자 불가능 (ETF만 거래 가능) ====&lt;br /&gt;
# 일반계좌에서는 국내주식도, 해외주식도 모두 직접 투자할 수 있는데 반해서 ISA 계좌에서는 해외주식 직접투자가 불가능함.&lt;br /&gt;
#: =&amp;gt; 그렇다면 국내주식만 비과세 혜택 등을 얻을수 있는것인지? 꼭 그런것은 아니다.&lt;br /&gt;
&lt;br /&gt;
==== 납입한도와 의무가입기간 ====&lt;br /&gt;
# 연 최대 2000만원 &lt;br /&gt;
# 의무가입기간 3년&lt;br /&gt;
# 최대 5년까지 만기를 연장 신청할 수 있음.&lt;br /&gt;
# 즉, 1인당 계좌에 최대 1억원을 한도로 납입할 수 있음. &lt;br /&gt;
# 만약 해당연도에 최대 금액을 납입하지 않는다면 다음 해로 이월됨&lt;br /&gt;
# 가입대상-금융소득 종합과세 대상자 가입불가 (2024년 개편안, 금융소득종합과세 대상자 가입 허용_국내주식,국내주식형 펀드투자만 가능)&lt;br /&gt;
&lt;br /&gt;
==== 중도해지/중도인출 ====&lt;br /&gt;
# 의무가입기간이 있기 때문에 가입중에 긴급하게 인출이 필요할 경우에 ISA 계좌는 중도해지할 경우 지금까지 받은 세금 혜택을 모두 반납 함&lt;br /&gt;
#: 사실상 세제 혜택을 받지 못하는 최악의 상황 발생 &lt;br /&gt;
# 추천 하는 방법은 바로 중도인출을 적절히 활용하는 것. &lt;br /&gt;
#: =&amp;gt; 중도해지는 안되지만, 납부한 원금에 한해서 중도인출은 되기 때문.&lt;br /&gt;
&lt;br /&gt;
=== 문제점 === &lt;br /&gt;
==== 건강보험료가 인상된다는데? ====&lt;br /&gt;
# 검색해보면 ISA 계좌를 이용하면 건강보험료가 인 상된다는 내용이 많음. 그런데 알고 보면 이것은 계좌 가입 여부와 무관하게 &amp;quot;금융소득이 많이 발생하면 소득이 늘기 때문에 건강보험료가 인상된다”는 취지 임. 가입만으로 건강보험료가 인상되는 일은 없음.&lt;br /&gt;
&lt;br /&gt;
==== 건강보험료 피부양자인경우 자격 박탈 ====&lt;br /&gt;
# 만기수령시에 분리과세되는 금융소득이 한꺼번에 생기게되는데 문제는 이 금융소득이 연 천만원을 넘어가게 되면 ISA가입자가 건강보험료 피부양자인 경우 피부양자 자격요건에서 박탈되거나, &lt;br /&gt;
# ISA 가입자가 지역건강보험 가입자인 경우에는 소득으로 합산되는 문제가 발생할 수 있다는 점이다. &lt;br /&gt;
## 예컨대 5년 동안 매년 2,000만원씩 ISA에 납입한 가입자가 연 평균 10%의 이자 수익을 올렸다고 가정하면 &lt;br /&gt;
## 이 가입자는 매해 200만원의 이자소득을 얻은 것이다. &lt;br /&gt;
## 그러나 세금을 부과할 때에는 만기 인출한 해에 5년치 이자 수익을 일시에 올린 것으로 산정한다.&lt;br /&gt;
[[category:재테크]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2351</id>
		<title>ISA</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=ISA&amp;diff=2351"/>
		<updated>2026-02-26T23:24:14Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 비과세 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= ISA 개인종합자산관리계좌 =&lt;br /&gt;
{{틀:고지상자&lt;br /&gt;
|제목= 개인종합자산관리계좌(Individual Savings Account)&lt;br /&gt;
|내용=* 다양한 금융상품을 한 계좌에서 통합관리하여 절세효과&lt;br /&gt;
* ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금 정산&lt;br /&gt;
* 연금저축이 아니다 =&amp;gt; 단기,목돈마련이 목적&lt;br /&gt;
* 개별 주식 투자가 가능하다.(IRP,연금저축은 개별주식 투자 불가능)&lt;br /&gt;
* 배당주 투자에 적합(종합금융소득세 절세효과)&lt;br /&gt;
* 19세 이상 ,15세 이상(근로소득자) 가능&lt;br /&gt;
}}&lt;br /&gt;
== ISA의 종류 ==&lt;br /&gt;
=== 일임형 ===&lt;br /&gt;
# 고객이 전문가에게 자산운용을 일임하는 유형&lt;br /&gt;
 &lt;br /&gt;
=== 신탁형 ===&lt;br /&gt;
# 고객이 직접 운용 지시를 하여 자산운용을 하는 것&lt;br /&gt;
# 중간에 은행 등 전문가를 두고 자산을 관리하는 형식&lt;br /&gt;
&lt;br /&gt;
=== 중개형 (일반적으로 선호)===&lt;br /&gt;
# 일임형 또는 신탁형과는 다르게, 전적으로 본인이 계좌를 운용할 수 있는 계좌&lt;br /&gt;
# 포트폴리오 구성, 리밸런싱 등 모든 투자결정을 본인 재량에 따라 직접 해야함. &lt;br /&gt;
# ISA계좌를 보편적으로 이야기하는 것은 중개형입니다.&lt;br /&gt;
&lt;br /&gt;
== ISA 장단점 ==&lt;br /&gt;
=== 장점 ===&lt;br /&gt;
==== 세제 혜택 ====&lt;br /&gt;
===== 비과세 =====&lt;br /&gt;
# ISA계좌로 투자하는 동안 과세이연에 만기시점(=의무가입기간 3년)에 한꺼번에 최종적인 세금을 정산&lt;br /&gt;
# 최종 과세대상 소득 중 200만 원까지는 비과세, 200만 원을 초과하는 금액에 대해서는 9.9%(지방소득세 포함)분리과세 적용  &lt;br /&gt;
## 최대 400만원의 비과세 혜택을 받을 수 있다.&lt;br /&gt;
## 비과세 혜택은 일반형/서민형/농어민형에 따라 다르게 적용. &lt;br /&gt;
## 총급여액이 5000만원 이하인 근로자 또는 종합소득이 3800만원 이하인 사업자는 400만원이 적용됨.&lt;br /&gt;
# 주의할 점은, 비과세 한도가 가입기간 전체에 적용되는 내용이라는 것. &lt;br /&gt;
#: - 납입한도가 연 기준으로 적용되기 때문에 많은 분들 이 비과세 한도도 연간 200만원(400만원)이라고 착각하시지만, 3년 이니 총 600만원 비과세되는 것이 아니다&lt;br /&gt;
#: * 흔한 착각 vs 실제&lt;br /&gt;
#:❌ 착각하는 사람들의 생각&lt;br /&gt;
#:“납입한도가 연 2,000만원이니까, 비과세도 매년 200만원씩 적용되겠지?”&lt;br /&gt;
#:→ 3년 가입 시 총 600만원 비과세?&lt;br /&gt;
#:✅ 실제&lt;br /&gt;
#:3년을 가입하든, 5년을 가입하든 비과세는 딱 200만원(서민형 400만원) 한 번만&lt;br /&gt;
&lt;br /&gt;
===== 분리과세 =====&lt;br /&gt;
# 비과세 혜택을 초과하는 금액은 9.9%로 분리과세(종합과세되지 않은것도 엄청남 혜택임)&lt;br /&gt;
# 금융소득에 대한 소득세율이 15.4%인 점을 고려 하면 비과세 금액을 초과(9.9%)하더라도 ISA 계좌가 훨씬 유리. &lt;br /&gt;
# 금융상품에서 발생한 소득이 분리과세 됨으로써 종합금융과세 될 확률이 줄어든다는 장점&lt;br /&gt;
===== 연금계좌로 전환시 추가 공제 =====&lt;br /&gt;
# 만기에 계좌에 보유한 자금을 연금계좌로 전환할 경우에는 전환금액의 10% 만큼의 세금을 300만원을 추가로 공제.&lt;br /&gt;
&lt;br /&gt;
==== 손익 통산 ====&lt;br /&gt;
# 일반 계좌에서 A금융상품에 대해 200만원의 손실을 봤고, B금융상품에 대해 100만원의 이익을 봤으면 A의 손실에 대해서는 과세하지 않고 B의 이익에 대해서는 과세 하게 됨.(즉  계좌가 개별로 처리됨)&lt;br /&gt;
#: =&amp;gt; 결과적으로 100만원의 손실을 봤음에 도 불구하고 또 세금을 내야됨.&lt;br /&gt;
# ISA 계좌를 통해서 A와 B에 투자한 경우, 각각에서 발생한 손익을 통산하여 총 이익을 -100만원 으로 보아 세금을 매기지 않습니다. &lt;br /&gt;
#: =&amp;gt; 이를 이용해서  연말에 손실을 본 주식을 매각하는 등 세율구간 조정도 가능&lt;br /&gt;
==== IRP/연금저축계좌로 이관시 혜택 ====&lt;br /&gt;
# ISA 만기자금을 연금계좌로 이체할 경우 ISA에서의 이체 금액만큼 연금계좌의 납입 한도를 추가로 늘려준다.&lt;br /&gt;
## 원래 연금계좌는 연금저축과 IRP를 합쳐서 연간 1800만원까지만 납입이 가능하다.&lt;br /&gt;
## 이미 연금계좌 납입 한도(연금저축의 경우 연 1800만원, IRP의 경우도 연 1800만원까지만 납입 가능하다.)를 다 채운 사람이라도 ISA 만기 자금을 연금계좌로 이체하는데 아무런 제한이 없다는 것이다.&lt;br /&gt;
# 자금 이체는 ISA 계약기간이 만료된 날로부터 60일 이내에 해야 한다. &lt;br /&gt;
# 만기자금의 일부만 이체하는 것도 가능하다.&lt;br /&gt;
===== 추가 세액 공제 =====&lt;br /&gt;
# 연금계좌로 이체한 만기자금 중 10%, 최대 300만원 세액공제&lt;br /&gt;
# 연소득이 4천만원(근로소득만 있으면 총급여 5천5백만원) 이하면 세액공제율은 16.5%, 이보다 많으면 13.2% 공제. &lt;br /&gt;
## 예시) isa 만기 자금 3천만원을 연금 계좌에 이체할 경우&lt;br /&gt;
### 세액공제 기준이 되는 금액은 10%인 300만원&lt;br /&gt;
### 기준보다 소득이 적은 사람은 300만원×16.5%=49만5천원 &lt;br /&gt;
### 기준보다 소득이 많은 사람은 300만원×13.2%=39만6천원의 세금 환급&lt;br /&gt;
# ISA 보다 더 적은 세금 적용&lt;br /&gt;
## ISA 만기자금을  연금계좌로 이체한 자금은 추후(55세 이후) 연금을 수령할 때 ISA계좌보다 더 낮은 세율인 3.3~5.5%의 연금소득세가 부과&lt;br /&gt;
&lt;br /&gt;
=== 단점 ===&lt;br /&gt;
* 이 모든 혜택에도 불구하고, 개인종합자산관리계좌 를 이용할 때 반드시 고려해야 할 사항.&lt;br /&gt;
==== 해외주식 직접 투자 불가능 (ETF만 거래 가능) ====&lt;br /&gt;
# 일반계좌에서는 국내주식도, 해외주식도 모두 직접 투자할 수 있는데 반해서 ISA 계좌에서는 해외주식 직접투자가 불가능함.&lt;br /&gt;
#: =&amp;gt; 그렇다면 국내주식만 비과세 혜택 등을 얻을수 있는것인지? 꼭 그런것은 아니다.&lt;br /&gt;
&lt;br /&gt;
==== 납입한도와 의무가입기간 ====&lt;br /&gt;
# 연 최대 2000만원 &lt;br /&gt;
# 의무가입기간 3년&lt;br /&gt;
# 최대 5년까지 만기를 연장 신청할 수 있음.&lt;br /&gt;
# 즉, 1인당 계좌에 최대 1억원을 한도로 납입할 수 있음. &lt;br /&gt;
# 만약 해당연도에 최대 금액을 납입하지 않는다면 다음 해로 이월됨&lt;br /&gt;
# 가입대상-금융소득 종합과세 대상자 가입불가 (2024년 개편안, 금융소득종합과세 대상자 가입 허용_국내주식,국내주식형 펀드투자만 가능)&lt;br /&gt;
&lt;br /&gt;
==== 중도해지/중도인출 ====&lt;br /&gt;
# 의무가입기간이 있기 때문에 가입중에 긴급하게 인출이 필요할 경우에 ISA 계좌는 중도해지할 경우 지금까지 받은 세금 혜택을 모두 반납 함&lt;br /&gt;
#: 사실상 세제 혜택을 받지 못하는 최악의 상황 발생 &lt;br /&gt;
# 추천 하는 방법은 바로 중도인출을 적절히 활용하는 것. &lt;br /&gt;
#: =&amp;gt; 중도해지는 안되지만, 납부한 원금에 한해서 중도인출은 되기 때문.&lt;br /&gt;
&lt;br /&gt;
=== 문제점 === &lt;br /&gt;
==== 건강보험료가 인상된다는데? ====&lt;br /&gt;
# 검색해보면 ISA 계좌를 이용하면 건강보험료가 인 상된다는 내용이 많음. 그런데 알고 보면 이것은 계좌 가입 여부와 무관하게 &amp;quot;금융소득이 많이 발생하면 소득이 늘기 때문에 건강보험료가 인상된다”는 취지 임. 가입만으로 건강보험료가 인상되는 일은 없음.&lt;br /&gt;
&lt;br /&gt;
==== 건강보험료 피부양자인경우 자격 박탈 ====&lt;br /&gt;
# 만기수령시에 분리과세되는 금융소득이 한꺼번에 생기게되는데 문제는 이 금융소득이 연 천만원을 넘어가게 되면 ISA가입자가 건강보험료 피부양자인 경우 피부양자 자격요건에서 박탈되거나, &lt;br /&gt;
# ISA 가입자가 지역건강보험 가입자인 경우에는 소득으로 합산되는 문제가 발생할 수 있다는 점이다. &lt;br /&gt;
## 예컨대 5년 동안 매년 2,000만원씩 ISA에 납입한 가입자가 연 평균 10%의 이자 수익을 올렸다고 가정하면 &lt;br /&gt;
## 이 가입자는 매해 200만원의 이자소득을 얻은 것이다. &lt;br /&gt;
## 그러나 세금을 부과할 때에는 만기 인출한 해에 5년치 이자 수익을 일시에 올린 것으로 산정한다.&lt;br /&gt;
[[category:재테크]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=Autoit_%EC%95%84%EC%9D%B4%EC%BD%98_%EB%B3%80%EA%B2%BD&amp;diff=2350</id>
		<title>Autoit 아이콘 변경</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=Autoit_%EC%95%84%EC%9D%B4%EC%BD%98_%EB%B3%80%EA%B2%BD&amp;diff=2350"/>
		<updated>2026-02-24T07:42:51Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: AutoIt - Ctrl 키 누름 감지 → 트레이 아이콘 변경 Ctrl 키 상태를 주기적으로 체크해서 아이콘을 바꾸는 방식입니다.  핵심 함수 	∙	_IsPressed(&amp;quot;11&amp;quot;) → Ctrl 키 상태 감지 (0x11 = VK_CONTROL) 	∙	TraySetIcon() → 트레이 아이콘 변경  구현 코드 &amp;lt;source lang=autoit&amp;gt;  #include &amp;lt;Misc.au3&amp;gt; #include &amp;lt;TrayConstants.au3&amp;gt;  Opt(&amp;quot;TrayMenuMode&amp;quot;, 0)  ; 아이콘 경로 설정 Global $sIconNormal = @SystemDir &amp;amp; &amp;quot;\shell32.dll&amp;quot;   ; 기본...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;AutoIt - Ctrl 키 누름 감지 → 트레이 아이콘 변경&lt;br /&gt;
Ctrl 키 상태를 주기적으로 체크해서 아이콘을 바꾸는 방식입니다.&lt;br /&gt;
&lt;br /&gt;
핵심 함수&lt;br /&gt;
	∙	_IsPressed(&amp;quot;11&amp;quot;) → Ctrl 키 상태 감지 (0x11 = VK_CONTROL)&lt;br /&gt;
	∙	TraySetIcon() → 트레이 아이콘 변경&lt;br /&gt;
&lt;br /&gt;
구현 코드&lt;br /&gt;
&amp;lt;source lang=autoit&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;Misc.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;TrayConstants.au3&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Opt(&amp;quot;TrayMenuMode&amp;quot;, 0)&lt;br /&gt;
&lt;br /&gt;
; 아이콘 경로 설정&lt;br /&gt;
Global $sIconNormal = @SystemDir &amp;amp; &amp;quot;\shell32.dll&amp;quot;   ; 기본 아이콘&lt;br /&gt;
Global $sIconCtrl   = @SystemDir &amp;amp; &amp;quot;\shell32.dll&amp;quot;   ; Ctrl 눌렸을 때 아이콘&lt;br /&gt;
Global $iIconNormal = 1    ; shell32.dll 내 아이콘 인덱스&lt;br /&gt;
Global $iIconCtrl   = 21   ; 다른 인덱스 (원하는 번호로 변경)&lt;br /&gt;
&lt;br /&gt;
Global $bCtrlPressed = False&lt;br /&gt;
&lt;br /&gt;
; 트레이 메뉴&lt;br /&gt;
$idExit = TrayCreateItem(&amp;quot;종료&amp;quot;)&lt;br /&gt;
TrayItemSetOnEvent($idExit, &amp;quot;_DoExit&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
; 초기 아이콘 설정&lt;br /&gt;
TraySetIcon($sIconNormal, $iIconNormal)&lt;br /&gt;
TraySetState($TRAY_ICONSTATE_SHOW)&lt;br /&gt;
&lt;br /&gt;
; Ctrl 키 감지를 AdlibRegister로 등록 (100ms 주기)&lt;br /&gt;
AdlibRegister(&amp;quot;_CheckCtrlKey&amp;quot;, 100)&lt;br /&gt;
&lt;br /&gt;
While 1&lt;br /&gt;
    Sleep(50)&lt;br /&gt;
WEnd&lt;br /&gt;
&lt;br /&gt;
Func _CheckCtrlKey()&lt;br /&gt;
    Local $bNowPressed = _IsPressed(&amp;quot;11&amp;quot;)  ; 0x11 = VK_CONTROL&lt;br /&gt;
&lt;br /&gt;
    If $bNowPressed And Not $bCtrlPressed Then&lt;br /&gt;
        ; Ctrl 키 눌림 → 아이콘 변경&lt;br /&gt;
        $bCtrlPressed = True&lt;br /&gt;
        TraySetIcon($sIconCtrl, $iIconCtrl)&lt;br /&gt;
        TraySetToolTip(&amp;quot;Ctrl 키 누름!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    ElseIf Not $bNowPressed And $bCtrlPressed Then&lt;br /&gt;
        ; Ctrl 키 해제 → 원래 아이콘 복원&lt;br /&gt;
        $bCtrlPressed = False&lt;br /&gt;
        TraySetIcon($sIconNormal, $iIconNormal)&lt;br /&gt;
        TraySetToolTip(&amp;quot;대기중&amp;quot;)&lt;br /&gt;
    EndIf&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func _DoExit()&lt;br /&gt;
    Exit&lt;br /&gt;
EndFunc&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
커스텀 .ico 파일 사용시&lt;br /&gt;
&amp;lt;source lang=autoit&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; .ico 파일 직접 사용&lt;br /&gt;
Global $sIconNormal = @ScriptDir &amp;amp; &amp;quot;\normal.ico&amp;quot;&lt;br /&gt;
Global $sIconCtrl   = @ScriptDir &amp;amp; &amp;quot;\ctrl_mode.ico&amp;quot;&lt;br /&gt;
&lt;br /&gt;
; 인덱스는 0 또는 1&lt;br /&gt;
TraySetIcon($sIconNormal, 0)&lt;br /&gt;
&lt;br /&gt;
; Ctrl 눌렸을 때&lt;br /&gt;
TraySetIcon($sIconCtrl, 0)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
shell32.dll 주요 아이콘 인덱스 참고&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|인덱스|모양    |&lt;br /&gt;
|---|------|&lt;br /&gt;
|1  |일반 파일 |&lt;br /&gt;
|4  |폴더    |&lt;br /&gt;
|15 |검색    |&lt;br /&gt;
|21 |정보 (i)|&lt;br /&gt;
|44 |경고    |&lt;br /&gt;
|109|네트워크  |&lt;br /&gt;
|238|자물쇠   |&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
포인트 정리&lt;br /&gt;
	∙	AdlibRegister(&amp;quot;_CheckCtrlKey&amp;quot;, 100) : 100ms마다 자동 호출, 메인 루프 방해 없음&lt;br /&gt;
	∙	_IsPressed(&amp;quot;11&amp;quot;) : 16진수 문자열로 전달 (“11” = 0x11)&lt;br /&gt;
	∙	상태 변화 시에만 TraySetIcon() 호출 → 불필요한 깜빡임 방지&lt;br /&gt;
	∙	Left Ctrl만 감지하려면 &amp;quot;A2&amp;quot; (VK_LCONTROL) 사용&lt;br /&gt;
&lt;br /&gt;
[[category:autoit]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=Oracle_%EC%A0%91%EC%86%8D%EC%9C%A0%EC%A7%80_%EC%BB%A4%EB%84%A5%EC%85%98%ED%92%80&amp;diff=2349</id>
		<title>Oracle 접속유지 커넥션풀</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=Oracle_%EC%A0%91%EC%86%8D%EC%9C%A0%EC%A7%80_%EC%BB%A4%EB%84%A5%EC%85%98%ED%92%80&amp;diff=2349"/>
		<updated>2026-01-22T23:47:57Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: == Oracle DB 접속 유지 및 종료 처리 방법 == 1. 접속 유지 방법 (Connection Pool 사용)  from sqlalchemy import create_engine, event from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import QueuePool import cx_Oracle  # Oracle 접속 정보 username = &amp;quot;your_user&amp;quot; password = &amp;quot;your_password&amp;quot; dsn = &amp;quot;hostname:1521/service_name&amp;quot;  # Connection Pool 설정으로 Engine 생성 engine = create_engine(     f&amp;#039;oracle+cx_oracle://{username}:{password}@{dsn}&amp;#039;,     p...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Oracle DB 접속 유지 및 종료 처리 방법 ==&lt;br /&gt;
1. 접속 유지 방법 (Connection Pool 사용)&lt;br /&gt;
&lt;br /&gt;
from sqlalchemy import create_engine, event&lt;br /&gt;
from sqlalchemy.orm import sessionmaker&lt;br /&gt;
from sqlalchemy.pool import QueuePool&lt;br /&gt;
import cx_Oracle&lt;br /&gt;
&lt;br /&gt;
# Oracle 접속 정보&lt;br /&gt;
username = &amp;quot;your_user&amp;quot;&lt;br /&gt;
password = &amp;quot;your_password&amp;quot;&lt;br /&gt;
dsn = &amp;quot;hostname:1521/service_name&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Connection Pool 설정으로 Engine 생성&lt;br /&gt;
engine = create_engine(&lt;br /&gt;
    f&#039;oracle+cx_oracle://{username}:{password}@{dsn}&#039;,&lt;br /&gt;
    poolclass=QueuePool,&lt;br /&gt;
    pool_size=5,              # 유지할 커넥션 수&lt;br /&gt;
    max_overflow=10,          # 추가로 생성 가능한 커넥션 수&lt;br /&gt;
    pool_recycle=3600,        # 1시간마다 커넥션 재생성 (초단위)&lt;br /&gt;
    pool_pre_ping=True,       # 커넥션 사용 전 유효성 체크&lt;br /&gt;
    echo=False&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
# Session 생성&lt;br /&gt;
Session = sessionmaker(bind=engine)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. Keep-Alive 이벤트 리스너 추가&lt;br /&gt;
&lt;br /&gt;
# 커넥션 체크아웃 시 유효성 검증&lt;br /&gt;
@event.listens_for(engine, &amp;quot;connect&amp;quot;)&lt;br /&gt;
def receive_connect(dbapi_conn, connection_record):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;커넥션 생성 시 실행&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    print(&amp;quot;Database connected&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
@event.listens_for(engine, &amp;quot;checkout&amp;quot;)&lt;br /&gt;
def receive_checkout(dbapi_conn, connection_record, connection_proxy):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;커넥션 풀에서 꺼낼 때 ping 테스트&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    cursor = dbapi_conn.cursor()&lt;br /&gt;
    try:&lt;br /&gt;
        cursor.execute(&amp;quot;SELECT 1 FROM DUAL&amp;quot;)&lt;br /&gt;
        cursor.close()&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        # 커넥션이 끊어진 경우 재생성&lt;br /&gt;
        raise exc.DisconnectionError()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. Context Manager로 안전한 세션 관리&lt;br /&gt;
&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
&lt;br /&gt;
@contextmanager&lt;br /&gt;
def get_db_session():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;안전한 세션 관리&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    session = Session()&lt;br /&gt;
    try:&lt;br /&gt;
        yield session&lt;br /&gt;
        session.commit()&lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        session.rollback()&lt;br /&gt;
        print(f&amp;quot;Error: {e}&amp;quot;)&lt;br /&gt;
        raise&lt;br /&gt;
    finally:&lt;br /&gt;
        session.close()&lt;br /&gt;
&lt;br /&gt;
# 사용 예시&lt;br /&gt;
def query_example():&lt;br /&gt;
    with get_db_session() as session:&lt;br /&gt;
        result = session.execute(&amp;quot;SELECT * FROM your_table&amp;quot;)&lt;br /&gt;
        for row in result:&lt;br /&gt;
            print(row)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. 프로그램 종료 시 접속 종료&lt;br /&gt;
&lt;br /&gt;
import atexit&lt;br /&gt;
import signal&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
def cleanup_connections():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;모든 커넥션 정리&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    print(&amp;quot;Closing all database connections...&amp;quot;)&lt;br /&gt;
    engine.dispose()  # 모든 커넥션 풀 정리&lt;br /&gt;
    print(&amp;quot;Database connections closed&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# 정상 종료 시&lt;br /&gt;
atexit.register(cleanup_connections)&lt;br /&gt;
&lt;br /&gt;
# 강제 종료 시그널 처리&lt;br /&gt;
def signal_handler(signum, frame):&lt;br /&gt;
    print(f&amp;quot;\nReceived signal {signum}&amp;quot;)&lt;br /&gt;
    cleanup_connections()&lt;br /&gt;
    sys.exit(0)&lt;br /&gt;
&lt;br /&gt;
signal.signal(signal.SIGINT, signal_handler)   # Ctrl+C&lt;br /&gt;
signal.signal(signal.SIGTERM, signal_handler)  # kill 명령&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
5. 완전한 예제&lt;br /&gt;
&lt;br /&gt;
from sqlalchemy import create_engine, event, exc&lt;br /&gt;
from sqlalchemy.orm import sessionmaker&lt;br /&gt;
from sqlalchemy.pool import QueuePool&lt;br /&gt;
from contextlib import contextmanager&lt;br /&gt;
import atexit&lt;br /&gt;
import signal&lt;br /&gt;
import sys&lt;br /&gt;
&lt;br /&gt;
class OracleDBManager:&lt;br /&gt;
    def __init__(self, username, password, dsn):&lt;br /&gt;
        self.engine = create_engine(&lt;br /&gt;
            f&#039;oracle+cx_oracle://{username}:{password}@{dsn}&#039;,&lt;br /&gt;
            poolclass=QueuePool,&lt;br /&gt;
            pool_size=5,&lt;br /&gt;
            max_overflow=10,&lt;br /&gt;
            pool_recycle=3600,&lt;br /&gt;
            pool_pre_ping=True,&lt;br /&gt;
            echo=False&lt;br /&gt;
        )&lt;br /&gt;
        &lt;br /&gt;
        # 이벤트 리스너 등록&lt;br /&gt;
        event.listen(self.engine, &amp;quot;connect&amp;quot;, self._on_connect)&lt;br /&gt;
        event.listen(self.engine, &amp;quot;checkout&amp;quot;, self._on_checkout)&lt;br /&gt;
        &lt;br /&gt;
        self.Session = sessionmaker(bind=self.engine)&lt;br /&gt;
        &lt;br /&gt;
        # 종료 핸들러 등록&lt;br /&gt;
        atexit.register(self.cleanup)&lt;br /&gt;
        signal.signal(signal.SIGINT, self._signal_handler)&lt;br /&gt;
        signal.signal(signal.SIGTERM, self._signal_handler)&lt;br /&gt;
    &lt;br /&gt;
    def _on_connect(self, dbapi_conn, connection_record):&lt;br /&gt;
        print(&amp;quot;New database connection established&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    def _on_checkout(self, dbapi_conn, connection_record, connection_proxy):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;커넥션 사용 전 유효성 체크&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        cursor = dbapi_conn.cursor()&lt;br /&gt;
        try:&lt;br /&gt;
            cursor.execute(&amp;quot;SELECT 1 FROM DUAL&amp;quot;)&lt;br /&gt;
            cursor.close()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            raise exc.DisconnectionError()&lt;br /&gt;
    &lt;br /&gt;
    @contextmanager&lt;br /&gt;
    def get_session(self):&lt;br /&gt;
        session = self.Session()&lt;br /&gt;
        try:&lt;br /&gt;
            yield session&lt;br /&gt;
            session.commit()&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            session.rollback()&lt;br /&gt;
            raise&lt;br /&gt;
        finally:&lt;br /&gt;
            session.close()&lt;br /&gt;
    &lt;br /&gt;
    def cleanup(self):&lt;br /&gt;
        print(&amp;quot;\nCleaning up database connections...&amp;quot;)&lt;br /&gt;
        self.engine.dispose()&lt;br /&gt;
        print(&amp;quot;All connections closed&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    def _signal_handler(self, signum, frame):&lt;br /&gt;
        print(f&amp;quot;\nReceived termination signal {signum}&amp;quot;)&lt;br /&gt;
        self.cleanup()&lt;br /&gt;
        sys.exit(0)&lt;br /&gt;
&lt;br /&gt;
# 사용 예시&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    db = OracleDBManager(&amp;quot;scott&amp;quot;, &amp;quot;tiger&amp;quot;, &amp;quot;localhost:1521/ORCL&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    # 쿼리 실행&lt;br /&gt;
    with db.get_session() as session:&lt;br /&gt;
        result = session.execute(&amp;quot;SELECT sysdate FROM dual&amp;quot;)&lt;br /&gt;
        print(f&amp;quot;Current time: {result.fetchone()[0]}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    # 프로그램이 종료되면 자동으로 cleanup() 호출됨&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
6. 추가 옵션 - 주기적 Keep-Alive&lt;br /&gt;
&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
&lt;br /&gt;
class KeepAliveThread(threading.Thread):&lt;br /&gt;
    def __init__(self, db_manager, interval=300):&lt;br /&gt;
        super().__init__(daemon=True)&lt;br /&gt;
        self.db_manager = db_manager&lt;br /&gt;
        self.interval = interval  # 5분마다&lt;br /&gt;
        self.running = True&lt;br /&gt;
    &lt;br /&gt;
    def run(self):&lt;br /&gt;
        while self.running:&lt;br /&gt;
            try:&lt;br /&gt;
                with self.db_manager.get_session() as session:&lt;br /&gt;
                    session.execute(&amp;quot;SELECT 1 FROM DUAL&amp;quot;)&lt;br /&gt;
                print(&amp;quot;Keep-alive ping successful&amp;quot;)&lt;br /&gt;
            except Exception as e:&lt;br /&gt;
                print(f&amp;quot;Keep-alive error: {e}&amp;quot;)&lt;br /&gt;
            time.sleep(self.interval)&lt;br /&gt;
    &lt;br /&gt;
    def stop(self):&lt;br /&gt;
        self.running = False&lt;br /&gt;
&lt;br /&gt;
# 사용&lt;br /&gt;
db = OracleDBManager(&amp;quot;scott&amp;quot;, &amp;quot;tiger&amp;quot;, &amp;quot;localhost:1521/ORCL&amp;quot;)&lt;br /&gt;
keep_alive = KeepAliveThread(db, interval=300)&lt;br /&gt;
keep_alive.start()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
주요 포인트:&lt;br /&gt;
	∙	pool_pre_ping=True: 커넥션 사용 전 자동 검증&lt;br /&gt;
	∙	pool_recycle=3600: 1시간마다 커넥션 재생성으로 stale connection 방지&lt;br /&gt;
	∙	atexit.register(): 정상 종료 시 자동 cleanup&lt;br /&gt;
	∙	signal 핸들러: Ctrl+C나 kill 명령어로 강제 종료 시에도 안전하게 종료&lt;br /&gt;
	∙	Context manager: 예외 발생 시에도 세션 정리 보장&lt;br /&gt;
이 방식으로 안정적인 DB 접속 관리가 가능합니다.​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=S%26p500_%EA%B3%BC_%EB%82%98%EC%8A%A4%EB%8B%A5_%EB%B9%84%EA%B5%90_(%EC%B5%9C%EA%B7%BC_20%EB%85%84%EA%B0%84)&amp;diff=2348</id>
		<title>S&amp;p500 과 나스닥 비교 (최근 20년간)</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=S%26p500_%EA%B3%BC_%EB%82%98%EC%8A%A4%EB%8B%A5_%EB%B9%84%EA%B5%90_(%EC%B5%9C%EA%B7%BC_20%EB%85%84%EA%B0%84)&amp;diff=2348"/>
		<updated>2026-01-13T10:32:59Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* 최근 20년간(2005-2024년) S&amp;amp;P 500, 나스닥 종합지수, 나스닥 100의 연간 수익률 비교&lt;br /&gt;
&lt;br /&gt;
===연도별 수익률 비교 (2005-2024) ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 연도 !! S&amp;amp;P 500 !! 나스닥 종합 !! 나스닥 100 !! 최고 성과 지수&lt;br /&gt;
|-&lt;br /&gt;
| 2024 || 25.02% || 28.64% || 24.88% || 나스닥 종합&lt;br /&gt;
|-&lt;br /&gt;
| 2023 || 26.29% || 43.42% || 53.81% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2022 || -18.11% || -33.10% || -32.97% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2021 || 28.71% || 21.39% || 26.63% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2020 || 18.40% || 43.64% || 47.58% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2019 || 31.49% || 35.23% || 37.96% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2018 || -4.38% || -3.88% || -1.04% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2017 || 21.83% || 28.24% || 31.52% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2016 || 11.96% || 7.50% || 5.89% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2015 || 1.38% || 5.73% || 8.43% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2014 || 13.69% || 13.40% || 17.94% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2013 || 32.39% || 38.32% || 34.99% || 나스닥 종합&lt;br /&gt;
|-&lt;br /&gt;
| 2012 || 16.00% || 15.91% || 16.82% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2011 || 2.11% || -1.80% || 2.70% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2010 || 15.06% || 16.91% || 19.22% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2009 || 26.46% || 43.89% || 53.54% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2008 || -37.00% || -40.54% || -41.89% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2007 || 5.49% || 9.81% || 18.67% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2006 || 15.79% || 9.52% || 6.79% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2005 || 4.91% || 1.37% || 1.49% || S&amp;amp;P 500&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 20년간 주요 통계 ===&lt;br /&gt;
&lt;br /&gt;
==== 연평균 수익률 (단순 평균) ====&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 약 11.8%&lt;br /&gt;
- 나스닥 종합: 약 13.7%&lt;br /&gt;
- 나스닥 100: 약 16.0%&lt;br /&gt;
&lt;br /&gt;
==== 최고 수익률 연도 ====&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 2019년 (31.49%)&lt;br /&gt;
- 나스닥 종합: 2023년 (43.42%)&lt;br /&gt;
- 나스닥 100: 2023년 (53.81%)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== 최저 수익률 연도 (가장 큰 손실) ====&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 2008년 (-37.00%)&lt;br /&gt;
- 나스닥 종합: 2008년 (-40.54%)&lt;br /&gt;
- 나스닥 100: 2008년 (-41.89%)&lt;br /&gt;
&lt;br /&gt;
==== 각 지수가 최고 성과를 보인 연도 수 ====&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 5회&lt;br /&gt;
- 나스닥 종합: 3회&lt;br /&gt;
- 나스닥 100: 12회&lt;br /&gt;
&lt;br /&gt;
* 주요 특징 &lt;br /&gt;
# 나스닥 100의 장기 우위: 20년간 12회로 가장 많이 1위를 차지하며 평균 수익률도 가장 높음&lt;br /&gt;
# 2008 금융위기: 세 지수 모두 역대급 하락, 기술주 중심 지수가 더 큰 타격&lt;br /&gt;
# 2020-2023 기술주 랠리: 나스닥 지수들이 압도적 성과, 특히 2023년 나스닥 100은 53.81% 상승&lt;br /&gt;
# 2022년 조정: 금리 인상으로 나스닥 지수들이 -33% 내외 급락, S&amp;amp;P 500은 상대적으로 양호&lt;br /&gt;
# 변동성 : 나스닥 지수들은 상승장에서 더 높은 수익, 하락장에서 더 큰 손실​​​​​​​​​​​​​​​​&lt;br /&gt;
&lt;br /&gt;
=== 1000만원 투자시 시나리오 ===&lt;br /&gt;
* 2005년 초에 각 지수에 1,000만원씩 투자했을 경우의 현재(2024년 말 기준) 평가액을 계산&lt;br /&gt;
&lt;br /&gt;
==== 복리 수익률 계산 ====&lt;br /&gt;
* S&amp;amp;P 500 (1,000만원 투자)&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1,000만원 × 1.0491 × 1.1579 × 1.0549 × 0.6300 × 1.2646 × 1.1506 × 1.0211 × 1.1600 × 1.3239 × 1.1369 × 1.0138 × 1.1196 × 1.2183 × 0.9562 × 1.3149 × 1.1840 × 1.3149 × 0.8189 × 1.2871 × 1.2871 × 1.2629 × 1.2502&lt;br /&gt;
= **약 5,893만원**&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
* 나스닥 종합 (1,000만원 투자)&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1,000만원 × 1.0137 × 1.0952 × 1.0981 × 0.5946 × 1.4389 × 1.1691 × 0.9820 × 1.1591 × 1.3832 × 1.1340 × 1.0573 × 1.0750 × 1.2824 × 0.9612 × 1.3523 × 1.4364 × 1.2139 × 0.6690 × 1.4342 × 1.2864&lt;br /&gt;
= **약 6,846만원**&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
* 나스닥 100 (1,000만원 투자)&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1,000만원 × 1.0149 × 1.0679 × 1.1867 × 0.5811 × 1.5354 × 1.1922 × 1.0270 × 1.1682 × 1.3499 × 1.1794 × 1.0843 × 1.0589 × 1.3152 × 0.9896 × 1.3796 × 1.4758 × 1.2663 × 0.6703 × 1.5381 × 1.2488&lt;br /&gt;
= **약 7,735만원**&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 최종 결과 비교&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!지수         !!초기 투자  !!2024년 말 평가액!!총 수익률     !!연평균 복리수익률&lt;br /&gt;
|-&lt;br /&gt;
|**S&amp;amp;P 500**|1,000만원|**5,893만원**|**489.3%**|**9.3%** |&lt;br /&gt;
|-&lt;br /&gt;
|**나스닥 종합** |1,000만원|**6,846만원**|**584.6%**|**10.3%**|&lt;br /&gt;
|-&lt;br /&gt;
|**나스닥 100**|1,000만원|**7,735만원**|**673.5%**|**11.0%**|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
## 투자 성과 분석&lt;br /&gt;
&lt;br /&gt;
**절대 수익 차이:**&lt;br /&gt;
&lt;br /&gt;
- 나스닥 100이 S&amp;amp;P 500 대비 **1,842만원 더 많은 수익**&lt;br /&gt;
- 나스닥 100이 나스닥 종합 대비 **889만원 더 많은 수익**&lt;br /&gt;
&lt;br /&gt;
**핵심 인사이트:**&lt;br /&gt;
&lt;br /&gt;
1. **장기 복리 효과**: 연평균 수익률 1.7%p 차이가 20년간 약 1,800만원의 차이 발생&lt;br /&gt;
1. **2008년 금융위기 극복**: 세 지수 모두 40% 가까이 하락했지만, 이후 회복하여 5~7배 성장&lt;br /&gt;
1. **최근 5년의 영향**: 2020-2023년 기술주 랠리가 나스닥 지수들의 격차를 크게 벌림&lt;br /&gt;
1. **변동성 vs 수익률**: 나스닥 100이 가장 높은 변동성을 보였지만, 장기적으로는 가장 높은 수익률 달성&lt;br /&gt;
&lt;br /&gt;
**실제 투자 시 고려사항:**&lt;br /&gt;
&lt;br /&gt;
- 배당 재투자를 포함하면 실제 수익률은 더 높을 수 있음&lt;br /&gt;
- 세금, 수수료는 고려하지 않은 수치&lt;br /&gt;
- 환율 변동(원/달러)도 실제 수익률에 영향​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=S%26p500_%EA%B3%BC_%EB%82%98%EC%8A%A4%EB%8B%A5_%EB%B9%84%EA%B5%90_(%EC%B5%9C%EA%B7%BC_20%EB%85%84%EA%B0%84)&amp;diff=2347</id>
		<title>S&amp;p500 과 나스닥 비교 (최근 20년간)</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=S%26p500_%EA%B3%BC_%EB%82%98%EC%8A%A4%EB%8B%A5_%EB%B9%84%EA%B5%90_(%EC%B5%9C%EA%B7%BC_20%EB%85%84%EA%B0%84)&amp;diff=2347"/>
		<updated>2026-01-13T10:27:49Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;최근 20년간(2005-2024년) S&amp;amp;P 500, 나스닥 종합지수, 나스닥 100의 연간 수익률 비교표입니다.&lt;br /&gt;
&lt;br /&gt;
===연도별 수익률 비교 (2005-2024) ===&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 연도 !! S&amp;amp;P 500 !! 나스닥 종합 !! 나스닥 100 !! 최고 성과 지수&lt;br /&gt;
|-&lt;br /&gt;
| 2024 || 25.02% || 28.64% || 24.88% || 나스닥 종합&lt;br /&gt;
|-&lt;br /&gt;
| 2023 || 26.29% || 43.42% || 53.81% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2022 || -18.11% || -33.10% || -32.97% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2021 || 28.71% || 21.39% || 26.63% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2020 || 18.40% || 43.64% || 47.58% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2019 || 31.49% || 35.23% || 37.96% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2018 || -4.38% || -3.88% || -1.04% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2017 || 21.83% || 28.24% || 31.52% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2016 || 11.96% || 7.50% || 5.89% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2015 || 1.38% || 5.73% || 8.43% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2014 || 13.69% || 13.40% || 17.94% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2013 || 32.39% || 38.32% || 34.99% || 나스닥 종합&lt;br /&gt;
|-&lt;br /&gt;
| 2012 || 16.00% || 15.91% || 16.82% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2011 || 2.11% || -1.80% || 2.70% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2010 || 15.06% || 16.91% || 19.22% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2009 || 26.46% || 43.89% || 53.54% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2008 || -37.00% || -40.54% || -41.89% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2007 || 5.49% || 9.81% || 18.67% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2006 || 15.79% || 9.52% || 6.79% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2005 || 4.91% || 1.37% || 1.49% || S&amp;amp;P 500&lt;br /&gt;
|}&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 20년간 주요 통계 ===&lt;br /&gt;
&lt;br /&gt;
==== 연평균 수익률 (단순 평균) ====&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 약 11.8%&lt;br /&gt;
- 나스닥 종합: 약 13.7%&lt;br /&gt;
- 나스닥 100: 약 16.0%&lt;br /&gt;
&lt;br /&gt;
==== 최고 수익률 연도 ====&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 2019년 (31.49%)&lt;br /&gt;
- 나스닥 종합: 2023년 (43.42%)&lt;br /&gt;
- 나스닥 100: 2023년 (53.81%)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== 최저 수익률 연도 (가장 큰 손실) ====&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 2008년 (-37.00%)&lt;br /&gt;
- 나스닥 종합: 2008년 (-40.54%)&lt;br /&gt;
- 나스닥 100: 2008년 (-41.89%)&lt;br /&gt;
&lt;br /&gt;
==== 각 지수가 최고 성과를 보인 연도 수 ====&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 5회&lt;br /&gt;
- 나스닥 종합: 3회&lt;br /&gt;
- 나스닥 100: 12회&lt;br /&gt;
&lt;br /&gt;
* 주요 특징 &lt;br /&gt;
# 나스닥 100의 장기 우위: 20년간 12회로 가장 많이 1위를 차지하며 평균 수익률도 가장 높음&lt;br /&gt;
# 2008 금융위기: 세 지수 모두 역대급 하락, 기술주 중심 지수가 더 큰 타격&lt;br /&gt;
# 2020-2023 기술주 랠리: 나스닥 지수들이 압도적 성과, 특히 2023년 나스닥 100은 53.81% 상승&lt;br /&gt;
# 2022년 조정: 금리 인상으로 나스닥 지수들이 -33% 내외 급락, S&amp;amp;P 500은 상대적으로 양호&lt;br /&gt;
# 변동성 : 나스닥 지수들은 상승장에서 더 높은 수익, 하락장에서 더 큰 손실​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=S%26p500_%EA%B3%BC_%EB%82%98%EC%8A%A4%EB%8B%A5_%EB%B9%84%EA%B5%90_(%EC%B5%9C%EA%B7%BC_20%EB%85%84%EA%B0%84)&amp;diff=2346</id>
		<title>S&amp;p500 과 나스닥 비교 (최근 20년간)</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=S%26p500_%EA%B3%BC_%EB%82%98%EC%8A%A4%EB%8B%A5_%EB%B9%84%EA%B5%90_(%EC%B5%9C%EA%B7%BC_20%EB%85%84%EA%B0%84)&amp;diff=2346"/>
		<updated>2026-01-13T10:24:43Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: 최근 20년간(2005-2024년) S&amp;amp;P 500, 나스닥 종합지수, 나스닥 100의 연간 수익률 비교표입니다.  ## 연도별 수익률 비교 (2005-2024) 미디어위키 표 양식으로 변환해드리겠습니다.  ```wiki {| class=&amp;quot;wikitable&amp;quot; |- ! 연도 !! S&amp;amp;P 500 !! 나스닥 종합 !! 나스닥 100 !! 최고 성과 지수 |- | 2024 || 25.02% || 28.64% || 24.88% || 나스닥 종합 |- | 2023 || 26.29% || 43.42% || 53.81% || 나스닥 100 |- | 2022 || -18.11% || -33.10...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;최근 20년간(2005-2024년) S&amp;amp;P 500, 나스닥 종합지수, 나스닥 100의 연간 수익률 비교표입니다.&lt;br /&gt;
&lt;br /&gt;
## 연도별 수익률 비교 (2005-2024)&lt;br /&gt;
미디어위키 표 양식으로 변환해드리겠습니다.&lt;br /&gt;
&lt;br /&gt;
```wiki&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! 연도 !! S&amp;amp;P 500 !! 나스닥 종합 !! 나스닥 100 !! 최고 성과 지수&lt;br /&gt;
|-&lt;br /&gt;
| 2024 || 25.02% || 28.64% || 24.88% || 나스닥 종합&lt;br /&gt;
|-&lt;br /&gt;
| 2023 || 26.29% || 43.42% || 53.81% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2022 || -18.11% || -33.10% || -32.97% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2021 || 28.71% || 21.39% || 26.63% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2020 || 18.40% || 43.64% || 47.58% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2019 || 31.49% || 35.23% || 37.96% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2018 || -4.38% || -3.88% || -1.04% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2017 || 21.83% || 28.24% || 31.52% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2016 || 11.96% || 7.50% || 5.89% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2015 || 1.38% || 5.73% || 8.43% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2014 || 13.69% || 13.40% || 17.94% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2013 || 32.39% || 38.32% || 34.99% || 나스닥 종합&lt;br /&gt;
|-&lt;br /&gt;
| 2012 || 16.00% || 15.91% || 16.82% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2011 || 2.11% || -1.80% || 2.70% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2010 || 15.06% || 16.91% || 19.22% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2009 || 26.46% || 43.89% || 53.54% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2008 || -37.00% || -40.54% || -41.89% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2007 || 5.49% || 9.81% || 18.67% || 나스닥 100&lt;br /&gt;
|-&lt;br /&gt;
| 2006 || 15.79% || 9.52% || 6.79% || S&amp;amp;P 500&lt;br /&gt;
|-&lt;br /&gt;
| 2005 || 4.91% || 1.37% || 1.49% || S&amp;amp;P 500&lt;br /&gt;
|}&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
## 20년간 주요 통계&lt;br /&gt;
&lt;br /&gt;
**연평균 수익률 (단순 평균):**&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 약 11.8%&lt;br /&gt;
- 나스닥 종합: 약 13.7%&lt;br /&gt;
- 나스닥 100: 약 16.0%&lt;br /&gt;
&lt;br /&gt;
**최고 수익률 연도:**&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 2019년 (31.49%)&lt;br /&gt;
- 나스닥 종합: 2023년 (43.42%)&lt;br /&gt;
- 나스닥 100: 2023년 (53.81%)&lt;br /&gt;
&lt;br /&gt;
**최저 수익률 연도 (가장 큰 손실):**&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 2008년 (-37.00%)&lt;br /&gt;
- 나스닥 종합: 2008년 (-40.54%)&lt;br /&gt;
- 나스닥 100: 2008년 (-41.89%)&lt;br /&gt;
&lt;br /&gt;
**각 지수가 최고 성과를 보인 연도 수:**&lt;br /&gt;
&lt;br /&gt;
- S&amp;amp;P 500: 5회&lt;br /&gt;
- 나스닥 종합: 3회&lt;br /&gt;
- 나스닥 100: 12회&lt;br /&gt;
&lt;br /&gt;
**주요 특징:**&lt;br /&gt;
&lt;br /&gt;
1. **나스닥 100의 장기 우위**: 20년간 12회로 가장 많이 1위를 차지하며 평균 수익률도 가장 높음&lt;br /&gt;
1. **2008 금융위기**: 세 지수 모두 역대급 하락, 기술주 중심 지수가 더 큰 타격&lt;br /&gt;
1. **2020-2023 기술주 랠리**: 나스닥 지수들이 압도적 성과, 특히 2023년 나스닥 100은 53.81% 상승&lt;br /&gt;
1. **2022년 조정**: 금리 인상으로 나스닥 지수들이 -33% 내외 급락, S&amp;amp;P 500은 상대적으로 양호&lt;br /&gt;
1. **변동성**: 나스닥 지수들은 상승장에서 더 높은 수익, 하락장에서 더 큰 손실​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EA%B8%B0%EC%96%B5%EB%A0%A5_%ED%96%A5%EC%83%81_%EB%B0%A9%EB%B2%95&amp;diff=2345</id>
		<title>기억력 향상 방법</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EA%B8%B0%EC%96%B5%EB%A0%A5_%ED%96%A5%EC%83%81_%EB%B0%A9%EB%B2%95&amp;diff=2345"/>
		<updated>2026-01-12T13:54:27Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: == 기억력 향상을 위한 가장 효율적인 방법 3가지 == === 간격 반복 학습 (Spaced Repetition) === 정보를 처음 학습한 후 점차 간격을 늘려가며 반복하는 방법입니다. 예를 들어 1일 후, 3일 후, 7일 후, 14일 후 식으로 복습하면 장기 기억으로 효과적으로 전환됩니다. Anki 같은 앱을 활용하거나, Oracle 관련 개념들을 주기적으로 복습하는 스케줄을 만들어보세요.  === 능동적 회...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 기억력 향상을 위한 가장 효율적인 방법 3가지 ==&lt;br /&gt;
=== 간격 반복 학습 (Spaced Repetition) ===&lt;br /&gt;
정보를 처음 학습한 후 점차 간격을 늘려가며 반복하는 방법입니다. 예를 들어 1일 후, 3일 후, 7일 후, 14일 후 식으로 복습하면 장기 기억으로 효과적으로 전환됩니다. Anki 같은 앱을 활용하거나, Oracle 관련 개념들을 주기적으로 복습하는 스케줄을 만들어보세요.&lt;br /&gt;
&lt;br /&gt;
=== 능동적 회상 (Active Recall) ===&lt;br /&gt;
단순히 자료를 다시 읽는 것이 아니라, 책을 덮고 스스로 기억해내려고 노력하는 방법입니다. “이 SQL 튜닝 기법의 핵심은 뭐였지?“라고 스스로 질문하고 답하는 연습이 수동적 복습보다 훨씬 효과적입니다. 코딩할 때도 문서를 바로 보지 않고 먼저 기억해내려 시도하는 습관이 도움됩니다.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 충분한 수면 ===&lt;br /&gt;
수면 중 뇌는 단기 기억을 장기 기억으로 공고화하는 작업을 합니다. 특히 깊은 수면 단계에서 이 과정이 활발하게 일어나므로, 7-8시간의 양질의 수면을 유지하는 것이 학습 효과를 극대화합니다. 새로운 Oracle 기능을 공부했다면, 그날 밤 충분히 자는 것이 기억 정착에 핵심입니다.&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%8F%AC%EB%A5%B4%ED%88%AC%EA%B0%88_%EB%A6%AC%EC%8A%A4%EB%B3%B8_%ED%8F%AC%EB%A5%B4%ED%88%AC_%EB%93%9C%EB%A1%A0_%EC%B4%AC%EC%98%81%EA%B8%B0&amp;diff=2344</id>
		<title>포르투갈 리스본 포르투 드론 촬영기</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%8F%AC%EB%A5%B4%ED%88%AC%EA%B0%88_%EB%A6%AC%EC%8A%A4%EB%B3%B8_%ED%8F%AC%EB%A5%B4%ED%88%AC_%EB%93%9C%EB%A1%A0_%EC%B4%AC%EC%98%81%EA%B8%B0&amp;diff=2344"/>
		<updated>2026-01-04T06:30:10Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: # 포르투갈 드론 촬영 규정 (250g 이하) 🚁  ## ⚠️ 중요 공지  **포르투갈은 EU 드론 규정을 따르며, 250g 이하 드론도 규제 대상입니다.**  ---  ## 🇪🇺 EU 드론 규정 (2024년 기준)  ### 드론 분류 체계  포르투갈을 포함한 EU는 **무게가 아닌 위험도**로 분류:  | 클래스 | 무게 | 자격증 | 등록 | 비고 | |--------|------|--------|------|------| | **C0** | &amp;lt;250g | ❌ 불필요 | ⚠️ **조건부** |...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;# 포르투갈 드론 촬영 규정 (250g 이하) 🚁&lt;br /&gt;
&lt;br /&gt;
## ⚠️ 중요 공지&lt;br /&gt;
&lt;br /&gt;
**포르투갈은 EU 드론 규정을 따르며, 250g 이하 드론도 규제 대상입니다.**&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 🇪🇺 EU 드론 규정 (2024년 기준)&lt;br /&gt;
&lt;br /&gt;
### 드론 분류 체계&lt;br /&gt;
&lt;br /&gt;
포르투갈을 포함한 EU는 **무게가 아닌 위험도**로 분류:&lt;br /&gt;
&lt;br /&gt;
| 클래스 | 무게 | 자격증 | 등록 | 비고 |&lt;br /&gt;
|--------|------|--------|------|------|&lt;br /&gt;
| **C0** | &amp;lt;250g | ❌ 불필요 | ⚠️ **조건부** | 카메라 있으면 등록 필요 |&lt;br /&gt;
| **C1** | &amp;lt;900g | ✅ A1/A3 | ✅ 필수 | 온라인 시험 |&lt;br /&gt;
| **C2** | &amp;lt;4kg | ✅ A2 | ✅ 필수 | 이론+실기 |&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 🎯 250g 이하 드론의 경우&lt;br /&gt;
&lt;br /&gt;
### ✅ 자격증: **불필요**&lt;br /&gt;
&lt;br /&gt;
**하지만 조건이 있습니다:**&lt;br /&gt;
&lt;br /&gt;
### ⚠️ 등록 필요 여부&lt;br /&gt;
&lt;br /&gt;
#### 📷 **카메라가 있는 경우** (DJI Mini 시리즈 등)&lt;br /&gt;
```&lt;br /&gt;
✅ 운영자 등록 필수!&lt;br /&gt;
- 개인정보 수집 가능한 카메라 탑재&lt;br /&gt;
- EU 규정상 등록 의무&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### 🎮 **카메라 없는 경우** (완구용 드론)&lt;br /&gt;
```&lt;br /&gt;
❌ 등록 불필요&lt;br /&gt;
- 순수 레저용&lt;br /&gt;
- 개인정보 수집 불가&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 📋 250g 이하 드론 사용 절차&lt;br /&gt;
&lt;br /&gt;
### Step 1: 드론 운영자 등록 (필수 - 카메라 있는 경우)&lt;br /&gt;
&lt;br /&gt;
**등록 기관:**&lt;br /&gt;
- **ANAC** (Autoridade Nacional da Aviação Civil)&lt;br /&gt;
- 포르투갈 민간항공청&lt;br /&gt;
&lt;br /&gt;
**등록 방법:**&lt;br /&gt;
&lt;br /&gt;
#### 온라인 등록&lt;br /&gt;
```&lt;br /&gt;
웹사이트: https://www.anac.pt&lt;br /&gt;
또는&lt;br /&gt;
EU 통합 시스템 사용 가능&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**필요 정보:**&lt;br /&gt;
- 이름, 주소&lt;br /&gt;
- 이메일&lt;br /&gt;
- 여권 번호&lt;br /&gt;
- 드론 모델/시리얼 번호&lt;br /&gt;
&lt;br /&gt;
**비용:**&lt;br /&gt;
- 약 €5-10&lt;br /&gt;
- 유효기간: 1-3년&lt;br /&gt;
&lt;br /&gt;
**등록 후:**&lt;br /&gt;
```&lt;br /&gt;
✅ 운영자 번호(Operator ID) 발급&lt;br /&gt;
✅ 드론에 번호 부착 (스티커/마커)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### Step 2: 온라인 교육 이수 (권장)&lt;br /&gt;
&lt;br /&gt;
**A1/A3 Open Category 온라인 교육:**&lt;br /&gt;
```&lt;br /&gt;
웹사이트: EU 드론 교육 플랫폼&lt;br /&gt;
- 무료 온라인 강의 (약 1시간)&lt;br /&gt;
- 온라인 시험 (40문항, 75% 이상 합격)&lt;br /&gt;
- 자격증 발급 (PDF 다운로드)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**필수는 아니지만:**&lt;br /&gt;
- 250g 이하도 교육 이수 **강력 권장**&lt;br /&gt;
- 법규 위반 시 면책 가능&lt;br /&gt;
- 일부 관광지에서 요구할 수 있음&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 📜 포르투갈 드론 비행 규칙 (250g 이하)&lt;br /&gt;
&lt;br /&gt;
### ✅ 허용 사항&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
✅ 시야 내 비행 (VLOS - Visual Line of Sight)&lt;br /&gt;
✅ 낮 시간 비행 (일출-일몰)&lt;br /&gt;
✅ 최대 고도 120m&lt;br /&gt;
✅ 해변, 공원, 시골 지역 (제한 없는 곳)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### ❌ 금지 사항&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
❌ 사람 군중 위 비행 (축제, 행사)&lt;br /&gt;
❌ 공항 반경 5km 이내&lt;br /&gt;
❌ 군사 시설 근처&lt;br /&gt;
❌ 국립공원/보호구역 (허가 필요)&lt;br /&gt;
❌ 도심 중심가 (리스본, 포르투)&lt;br /&gt;
❌ 프라이버시 침해 (주택가 창문 촬영 등)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### ⚠️ 특별 제한 구역&lt;br /&gt;
&lt;br /&gt;
#### 리스본 (Lisbon)&lt;br /&gt;
```&lt;br /&gt;
❌ 상 조르즈 성 (Castelo de São Jorge)&lt;br /&gt;
❌ 벨렝 타워 (Torre de Belém)&lt;br /&gt;
❌ 제로니무스 수도원&lt;br /&gt;
❌ 알파마 구시가지&lt;br /&gt;
⚠️ 테주강변 일부 구역만 가능&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### 포르투 (Porto)&lt;br /&gt;
```&lt;br /&gt;
❌ 도무 루이스 다리 (Ponte Dom Luís I)&lt;br /&gt;
❌ 리베이라 광장 (Ribeira)&lt;br /&gt;
❌ 클레리구스 탑 (Torre dos Clérigos)&lt;br /&gt;
⚠️ 도루강 일부 구역만 가능&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### 신트라 (Sintra)&lt;br /&gt;
```&lt;br /&gt;
❌ 페나 궁전 (Palácio da Pena) 내부&lt;br /&gt;
❌ 킨타 다 레갈레이라&lt;br /&gt;
❌ 무어인의 성&lt;br /&gt;
⚠️ 신트라 산맥은 자연보호구역&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### 해변&lt;br /&gt;
```&lt;br /&gt;
✅ 대부분 허용 (11월-3월)&lt;br /&gt;
⚠️ 여름(6-9월)은 인파로 제한적&lt;br /&gt;
❌ 사람 위로 날리면 안 됨&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 📱 필수 앱 다운로드&lt;br /&gt;
&lt;br /&gt;
### 1. **AirMap** 또는 **ANAC Drones App**&lt;br /&gt;
```&lt;br /&gt;
기능:&lt;br /&gt;
- 비행 금지 구역 실시간 표시&lt;br /&gt;
- GPS 기반 위치 확인&lt;br /&gt;
- 비행 허가 여부 체크&lt;br /&gt;
&lt;br /&gt;
다운로드:&lt;br /&gt;
- iOS: App Store&lt;br /&gt;
- Android: Google Play&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 2. 사용 방법&lt;br /&gt;
```&lt;br /&gt;
1. 앱 실행&lt;br /&gt;
2. 현재 위치 확인&lt;br /&gt;
3. &amp;quot;Can I fly here?&amp;quot; 체크&lt;br /&gt;
4. 빨간색 = 금지, 노란색 = 제한, 초록색 = 허용&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 🎬 촬영 팁 (합법적으로)&lt;br /&gt;
&lt;br /&gt;
### ✅ 추천 촬영 장소&lt;br /&gt;
&lt;br /&gt;
#### 1. **카스카이스 (Cascais) 해변**&lt;br /&gt;
```&lt;br /&gt;
✅ 관광객 적음 (2월)&lt;br /&gt;
✅ 바위 해안선 멋짐&lt;br /&gt;
✅ 리스본에서 기차 30분&lt;br /&gt;
✅ 드론 비행 허용 구역 많음&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### 2. **호카 곶 (Cabo da Roca)**&lt;br /&gt;
```&lt;br /&gt;
✅ 유라시아 최서단&lt;br /&gt;
✅ 절벽 풍경&lt;br /&gt;
✅ 사람 적음&lt;br /&gt;
⚠️ 바람 강함 주의&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### 3. **아로카 비치 (Praia da Ursa)**&lt;br /&gt;
```&lt;br /&gt;
✅ 숨은 해변&lt;br /&gt;
✅ 비경&lt;br /&gt;
✅ 접근 어려워 사람 없음&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### 4. **도루 계곡 (Douro Valley)**&lt;br /&gt;
```&lt;br /&gt;
✅ 포도밭 풍경&lt;br /&gt;
✅ 시골 지역&lt;br /&gt;
✅ 제한 거의 없음&lt;br /&gt;
⚠️ 포르투에서 차량 1시간&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### 5. **나자레 (Nazaré)**&lt;br /&gt;
```&lt;br /&gt;
✅ 거대 파도로 유명&lt;br /&gt;
✅ 해변 드론 가능&lt;br /&gt;
⚠️ 여름 피하기&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### ❌ 피해야 할 장소&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
❌ 리스본 알파마 좁은 골목&lt;br /&gt;
❌ 포르투 리베이라 광장&lt;br /&gt;
❌ 신트라 궁전 내부&lt;br /&gt;
❌ 공항 근처 (리스본 공항, 포르투 공항)&lt;br /&gt;
❌ 벨렝 지구 주요 건축물&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 📝 현지에서 확인 방법&lt;br /&gt;
&lt;br /&gt;
### 관광지 도착 시:&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 안내판 확인&lt;br /&gt;
   &amp;quot;No Drones&amp;quot; 표지판&lt;br /&gt;
&lt;br /&gt;
2. 관리 사무소 문의&lt;br /&gt;
   &amp;quot;Posso usar um drone aqui?&amp;quot;&lt;br /&gt;
   (여기서 드론 써도 되나요?)&lt;br /&gt;
&lt;br /&gt;
3. 경찰/경비원에게 물어보기&lt;br /&gt;
   예의 바르게 허가 요청&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## ⚖️ 위반 시 벌금&lt;br /&gt;
&lt;br /&gt;
### 포르투갈 드론 법규 위반:&lt;br /&gt;
&lt;br /&gt;
| 위반 사항 | 벌금 |&lt;br /&gt;
|----------|------|&lt;br /&gt;
| 등록 없이 비행 | €300-600 |&lt;br /&gt;
| 금지 구역 침입 | €500-2,000 |&lt;br /&gt;
| 공항 근처 비행 | €2,000-10,000 |&lt;br /&gt;
| 프라이버시 침해 | €1,000-3,000 |&lt;br /&gt;
| 사고 발생 | 형사 처벌 가능 |&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 🛡️ 보험&lt;br /&gt;
&lt;br /&gt;
### 권장 사항:&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
✅ 드론 보험 가입 (선택)&lt;br /&gt;
- 제3자 책임 보험&lt;br /&gt;
- 분실/파손 보험&lt;br /&gt;
- 비용: €50-100/연&lt;br /&gt;
&lt;br /&gt;
또는&lt;br /&gt;
&lt;br /&gt;
✅ 여행자 보험에 드론 포함 확인&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 📋 체크리스트 (출국 전)&lt;br /&gt;
&lt;br /&gt;
### 한국 출발 전:&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
□ 드론 250g 이하 확인&lt;br /&gt;
□ 배터리 개수 확인 (기내 반입 100Wh 이하)&lt;br /&gt;
□ ANAC 웹사이트 방문&lt;br /&gt;
□ 운영자 등록 (온라인)&lt;br /&gt;
□ 운영자 번호 드론에 부착&lt;br /&gt;
□ AirMap 앱 다운로드&lt;br /&gt;
□ A1/A3 온라인 교육 이수 (권장)&lt;br /&gt;
□ 드론 보험 확인 (선택)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 포르투갈 도착 후:&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
□ 촬영 예정지 앱으로 확인&lt;br /&gt;
□ 금지 구역 피하기&lt;br /&gt;
□ 날씨 확인 (바람)&lt;br /&gt;
□ 현지 안내판 확인&lt;br /&gt;
□ 의심되면 관리자에게 물어보기&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 🌐 공식 자료 링크&lt;br /&gt;
&lt;br /&gt;
### 포르투갈 민간항공청 (ANAC)&lt;br /&gt;
```&lt;br /&gt;
https://www.anac.pt/&lt;br /&gt;
→ &amp;quot;Drones&amp;quot; 섹션&lt;br /&gt;
→ 영어 지원&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### EU 드론 규정&lt;br /&gt;
```&lt;br /&gt;
https://www.easa.europa.eu/drones&lt;br /&gt;
→ EU 통합 규정&lt;br /&gt;
→ 온라인 교육 링크&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 비행 금지 구역 지도&lt;br /&gt;
```&lt;br /&gt;
https://www.anac.pt/vPT/Generico/&lt;br /&gt;
→ &amp;quot;Drone Zone Map&amp;quot; 다운로드&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 💡 추가 팁&lt;br /&gt;
&lt;br /&gt;
### 1. 언어 준비&lt;br /&gt;
```&lt;br /&gt;
포르투갈어 문구:&lt;br /&gt;
&amp;quot;Posso usar um drone aqui?&amp;quot;&lt;br /&gt;
(포수 우자르 움 드론 아키?)&lt;br /&gt;
= 여기서 드론 써도 되나요?&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Tenho registo de operador&amp;quot;&lt;br /&gt;
(테뉴 헤지스투 드 오페라도르)&lt;br /&gt;
= 운영자 등록했습니다&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 2. 촬영 시간대&lt;br /&gt;
```&lt;br /&gt;
✅ 아침 일찍 (07:00-09:00)&lt;br /&gt;
   - 사람 적음&lt;br /&gt;
   - 빛 좋음&lt;br /&gt;
   &lt;br /&gt;
✅ 일몰 전 (17:00-19:00)&lt;br /&gt;
   - 골든 아워&lt;br /&gt;
   - 관광객 줄어듦&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 3. 날씨 주의&lt;br /&gt;
```&lt;br /&gt;
⚠️ 2월 포르투갈:&lt;br /&gt;
- 바람 강함 (특히 해안)&lt;br /&gt;
- 비 가능성 (리스본 40%, 포르투 50%)&lt;br /&gt;
- 바람 15km/h 이상이면 비행 자제&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 4. 배터리 관리&lt;br /&gt;
```&lt;br /&gt;
✅ 예비 배터리 2-3개&lt;br /&gt;
✅ 호텔에서 충전&lt;br /&gt;
✅ 저온에서 배터리 수명 짧아짐 주의&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## 🎯 최종 답변 요약&lt;br /&gt;
&lt;br /&gt;
### Q: 250g 이하 드론, 자격증 필요한가요?&lt;br /&gt;
**A: ❌ 자격증 불필요**&lt;br /&gt;
&lt;br /&gt;
### Q: 등록은 필요한가요?&lt;br /&gt;
**A: ⚠️ 카메라 있으면 등록 필수 (€5-10)**&lt;br /&gt;
&lt;br /&gt;
### Q: 온라인 교육은?&lt;br /&gt;
**A: 선택사항이지만 강력 권장**&lt;br /&gt;
&lt;br /&gt;
### Q: 리스본/포르투 시내 촬영 가능?&lt;br /&gt;
**A: ❌ 대부분 금지. 주요 관광지는 불가**&lt;br /&gt;
&lt;br /&gt;
### Q: 해변 촬영은?&lt;br /&gt;
**A: ✅ 대부분 가능 (2월은 사람 적어서 OK)**&lt;br /&gt;
&lt;br /&gt;
### Q: 어디서 확인하나요?&lt;br /&gt;
**A: AirMap 앱 또는 ANAC 웹사이트**&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
## ⚠️ 최종 경고&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
❗ 무등록 비행 적발 시 최소 €300 벌금&lt;br /&gt;
❗ 금지 구역 비행 시 최대 €10,000 벌금&lt;br /&gt;
❗ 등록 안 하면 현장에서 압수 가능&lt;br /&gt;
❗ 프라이버시 침해는 형사 처벌 가능&lt;br /&gt;
&lt;br /&gt;
→ 꼭 등록하고 앱으로 확인하세요!&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
---&lt;br /&gt;
&lt;br /&gt;
**안전하고 합법적인 드론 촬영 되세요!** 🚁✨&lt;br /&gt;
&lt;br /&gt;
더 궁금한 점 있으면 언제든 물어보세요! 😊&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=Python_whl_%EC%84%A4%EC%B9%98&amp;diff=2343</id>
		<title>Python whl 설치</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=Python_whl_%EC%84%A4%EC%B9%98&amp;diff=2343"/>
		<updated>2025-12-24T08:13:13Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 1. Python whl 파일 설치 방법 ==&lt;br /&gt;
&lt;br /&gt;
1) 설치하고자 하는 whl 파일을 다운로드 받는다.&lt;br /&gt;
   https://pypi.org/&lt;br /&gt;
&lt;br /&gt;
2) python -m pip install whl파일명&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 2: Using pip download (Recommended) ==&lt;br /&gt;
&lt;br /&gt;
This automatically grabs all required dependencies for you. &lt;br /&gt;
On the internet-connected machine:&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
pip download PyQt5 -d ./pyqt5_offline&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This downloads the PyQt5 wheel and all its dependencies into the pyqt5_offline folder.&lt;br /&gt;
On the offline machine:&lt;br /&gt;
Transfer the folder and run:&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
pip install --no-index --find-links=./pyqt5_offline PyQt5 &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:python]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EB%82%B4%EC%BB%B4%ED%93%A8%ED%84%B0_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EB%82%B4%EC%9A%A9_%EA%B2%80%EC%83%89%EA%B8%B0&amp;diff=2342</id>
		<title>내컴퓨터 텍스트 내용 검색기</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EB%82%B4%EC%BB%B4%ED%93%A8%ED%84%B0_%ED%85%8D%EC%8A%A4%ED%8A%B8_%EB%82%B4%EC%9A%A9_%EA%B2%80%EC%83%89%EA%B8%B0&amp;diff=2342"/>
		<updated>2025-12-24T01:09:48Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서:  == 내컴퓨터 파일내용 검색기 ==  ===주요 기능=== ==== 검색 기능====  텍스트 검색: 일반 텍스트 또는 정규표현식 SQL 객체 검색: 테이블, 프로시저, 함수, 패키지 등 컨텍스트 표시: 검색 결과 전후 라인 표시 대소문자 구분: 선택 가능 ripgrep 지원: 초고속 검색 (설치된 경우) 멀티스레딩으로 병렬 검색 다양한 SQL 확장자 지원 (.sql, .pls, .pck 등) ====고급 기능====  Whoosh 인덱...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
== 내컴퓨터 파일내용 검색기 ==&lt;br /&gt;
&lt;br /&gt;
===주요 기능===&lt;br /&gt;
==== 검색 기능====&lt;br /&gt;
&lt;br /&gt;
텍스트 검색: 일반 텍스트 또는 정규표현식&lt;br /&gt;
SQL 객체 검색: 테이블, 프로시저, 함수, 패키지 등&lt;br /&gt;
컨텍스트 표시: 검색 결과 전후 라인 표시&lt;br /&gt;
대소문자 구분: 선택 가능&lt;br /&gt;
ripgrep 지원: 초고속 검색 (설치된 경우)&lt;br /&gt;
멀티스레딩으로 병렬 검색&lt;br /&gt;
다양한 SQL 확장자 지원 (.sql, .pls, .pck 등)&lt;br /&gt;
====고급 기능====&lt;br /&gt;
&lt;br /&gt;
Whoosh 인덱싱: 전문 검색 엔진으로 빠른 검색&lt;br /&gt;
다중 스레드: 병렬 처리로 성능 향상&lt;br /&gt;
진행률 표시: 실시간 검색 진행 상황&lt;br /&gt;
&lt;br /&gt;
==== 결과 관리====&lt;br /&gt;
&lt;br /&gt;
테이블 뷰: 정렬 가능한 결과 목록&lt;br /&gt;
상세 뷰: 컨텍스트와 함께 코드 표시&lt;br /&gt;
내보내기: HTML, CSV, TXT 형식 지원&lt;br /&gt;
파일 열기: 결과에서 바로 파일 실행&lt;br /&gt;
&lt;br /&gt;
==== 설정====&lt;br /&gt;
&lt;br /&gt;
검색 디렉토리: 여러 디렉토리 동시 검색&lt;br /&gt;
파일 확장자: 커스텀 확장자 지원&lt;br /&gt;
성능 튜닝: 스레드 수 조절&lt;br /&gt;
YAML 설정: 설정 파일로 관리&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 실전 활용 시나리오===&lt;br /&gt;
&lt;br /&gt;
# 시나리오 1: 특정 테이블을 DELETE하는 모든 스크립트 찾기&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
results = searcher.search(r&amp;quot;\bDELETE\s+FROM\s+EMP\b&amp;quot;, use_regex=True)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
# 시나리오 2: DBMS_OUTPUT.PUT_LINE 사용하는 곳 찾기&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
results = searcher.search(&amp;quot;DBMS_OUTPUT.PUT_LINE&amp;quot;, case_sensitive=False)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
# 시나리오 3: 특정 스키마의 객체 참조 찾기&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
results = searcher.search(r&amp;quot;\bHR\.\w+&amp;quot;, use_regex=True)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
# 시나리오 4: 커밋/롤백 문 찾기&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
results = searcher.search(r&amp;quot;\b(COMMIT|ROLLBACK)\b&amp;quot;, use_regex=True)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 프로그래밍 ===&lt;br /&gt;
&lt;br /&gt;
==== requirements.txt ====&lt;br /&gt;
* 프로젝트 디렉토리에 requirements.txt 파일을 만들면 편리합니다:&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
PyQt5==5.15.10&lt;br /&gt;
textPyQt5==5.15.10&lt;br /&gt;
tqdm==4.66.1&lt;br /&gt;
whoosh==2.7.4&lt;br /&gt;
PyYAML==6.0.1&lt;br /&gt;
ripgrepy==2.0.0&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== config.yaml (설정 파일)====&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
# SQL Script Searcher 설정 파일&lt;br /&gt;
search_directories:&lt;br /&gt;
  - &amp;quot;C:/oracle/scripts&amp;quot;&lt;br /&gt;
  - &amp;quot;C:/work/sql&amp;quot;&lt;br /&gt;
  - &amp;quot;D:/projects/database&amp;quot;&lt;br /&gt;
&lt;br /&gt;
sql_extensions:&lt;br /&gt;
  - &amp;quot;.sql&amp;quot;&lt;br /&gt;
  - &amp;quot;.pls&amp;quot;&lt;br /&gt;
  - &amp;quot;.pck&amp;quot;&lt;br /&gt;
  - &amp;quot;.pkb&amp;quot;&lt;br /&gt;
  - &amp;quot;.pks&amp;quot;&lt;br /&gt;
  - &amp;quot;.trg&amp;quot;&lt;br /&gt;
  - &amp;quot;.fnc&amp;quot;&lt;br /&gt;
  - &amp;quot;.prc&amp;quot;&lt;br /&gt;
&lt;br /&gt;
search_settings:&lt;br /&gt;
  max_workers: 4&lt;br /&gt;
  default_context_lines: 2&lt;br /&gt;
  case_sensitive: false&lt;br /&gt;
  use_regex: false&lt;br /&gt;
&lt;br /&gt;
ui_settings:&lt;br /&gt;
  window_width: 1200&lt;br /&gt;
  window_height: 800&lt;br /&gt;
  recent_searches_count: 10&lt;br /&gt;
&lt;br /&gt;
index_settings:&lt;br /&gt;
  enable_indexing: true&lt;br /&gt;
  index_directory: &amp;quot;./search_index&amp;quot;&lt;br /&gt;
  auto_update_index: false&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== sql_searcher_core.py (검색 엔진 코어)====&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import re&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
from concurrent.futures import ThreadPoolExecutor&lt;br /&gt;
from typing import List, Dict, Tuple, Optional&lt;br /&gt;
from dataclasses import dataclass&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from tqdm import tqdm&lt;br /&gt;
import yaml&lt;br /&gt;
&lt;br /&gt;
try:&lt;br /&gt;
    from ripgrepy import Ripgrepy&lt;br /&gt;
    RIPGREP_AVAILABLE = True&lt;br /&gt;
except ImportError:&lt;br /&gt;
    RIPGREP_AVAILABLE = False&lt;br /&gt;
&lt;br /&gt;
try:&lt;br /&gt;
    from whoosh.index import create_in, open_dir, exists_in&lt;br /&gt;
    from whoosh.fields import Schema, TEXT, ID, DATETIME, NUMERIC&lt;br /&gt;
    from whoosh.qparser import QueryParser, MultifieldParser&lt;br /&gt;
    from whoosh import highlight&lt;br /&gt;
    WHOOSH_AVAILABLE = True&lt;br /&gt;
except ImportError:&lt;br /&gt;
    WHOOSH_AVAILABLE = False&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
@dataclass&lt;br /&gt;
class SearchResult:&lt;br /&gt;
    filepath: str&lt;br /&gt;
    line_number: int&lt;br /&gt;
    line_content: str&lt;br /&gt;
    context_before: List[str]&lt;br /&gt;
    context_after: List[str]&lt;br /&gt;
    file_size: int&lt;br /&gt;
    modified_date: datetime&lt;br /&gt;
    score: float = 0.0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class ConfigManager:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;설정 관리 클래스&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    def __init__(self, config_file: str = &amp;quot;config.yaml&amp;quot;):&lt;br /&gt;
        self.config_file = config_file&lt;br /&gt;
        self.config = self._load_config()&lt;br /&gt;
    &lt;br /&gt;
    def _load_config(self) -&amp;gt; dict:&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;설정 파일 로드&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if os.path.exists(self.config_file):&lt;br /&gt;
            with open(self.config_file, &#039;r&#039;, encoding=&#039;utf-8&#039;) as f:&lt;br /&gt;
                return yaml.safe_load(f)&lt;br /&gt;
        else:&lt;br /&gt;
            return self._get_default_config()&lt;br /&gt;
    &lt;br /&gt;
    def _get_default_config(self) -&amp;gt; dict:&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;기본 설정 반환&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        return {&lt;br /&gt;
            &#039;search_directories&#039;: [],&lt;br /&gt;
            &#039;sql_extensions&#039;: [&#039;.sql&#039;, &#039;.pls&#039;, &#039;.pck&#039;, &#039;.pkb&#039;, &#039;.pks&#039;, &#039;.trg&#039;, &#039;.fnc&#039;, &#039;.prc&#039;],&lt;br /&gt;
            &#039;search_settings&#039;: {&lt;br /&gt;
                &#039;max_workers&#039;: 4,&lt;br /&gt;
                &#039;default_context_lines&#039;: 2,&lt;br /&gt;
                &#039;case_sensitive&#039;: False,&lt;br /&gt;
                &#039;use_regex&#039;: False&lt;br /&gt;
            },&lt;br /&gt;
            &#039;ui_settings&#039;: {&lt;br /&gt;
                &#039;window_width&#039;: 1200,&lt;br /&gt;
                &#039;window_height&#039;: 800,&lt;br /&gt;
                &#039;recent_searches_count&#039;: 10&lt;br /&gt;
            },&lt;br /&gt;
            &#039;index_settings&#039;: {&lt;br /&gt;
                &#039;enable_indexing&#039;: True,&lt;br /&gt;
                &#039;index_directory&#039;: &#039;./search_index&#039;,&lt;br /&gt;
                &#039;auto_update_index&#039;: False&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    &lt;br /&gt;
    def save_config(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;설정 파일 저장&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        with open(self.config_file, &#039;w&#039;, encoding=&#039;utf-8&#039;) as f:&lt;br /&gt;
            yaml.dump(self.config, f, allow_unicode=True, default_flow_style=False)&lt;br /&gt;
    &lt;br /&gt;
    def get(self, *keys):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;중첩된 키로 설정값 가져오기&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        value = self.config&lt;br /&gt;
        for key in keys:&lt;br /&gt;
            value = value.get(key, {})&lt;br /&gt;
        return value&lt;br /&gt;
    &lt;br /&gt;
    def set(self, value, *keys):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;중첩된 키로 설정값 설정하기&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        config = self.config&lt;br /&gt;
        for key in keys[:-1]:&lt;br /&gt;
            config = config.setdefault(key, {})&lt;br /&gt;
        config[keys[-1]] = value&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class WhooshIndexer:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Whoosh 인덱스 관리 클래스&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    def __init__(self, index_dir: str):&lt;br /&gt;
        self.index_dir = index_dir&lt;br /&gt;
        self.schema = Schema(&lt;br /&gt;
            filepath=ID(stored=True, unique=True),&lt;br /&gt;
            filename=TEXT(stored=True),&lt;br /&gt;
            content=TEXT(stored=True),&lt;br /&gt;
            modified=DATETIME(stored=True),&lt;br /&gt;
            size=NUMERIC(stored=True)&lt;br /&gt;
        )&lt;br /&gt;
        self.ix = None&lt;br /&gt;
        self._init_index()&lt;br /&gt;
    &lt;br /&gt;
    def _init_index(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;인덱스 초기화&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if not WHOOSH_AVAILABLE:&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        if not os.path.exists(self.index_dir):&lt;br /&gt;
            os.makedirs(self.index_dir)&lt;br /&gt;
        &lt;br /&gt;
        if exists_in(self.index_dir):&lt;br /&gt;
            self.ix = open_dir(self.index_dir)&lt;br /&gt;
        else:&lt;br /&gt;
            self.ix = create_in(self.index_dir, self.schema)&lt;br /&gt;
    &lt;br /&gt;
    def index_files(self, files: List[Path], progress_callback=None):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;파일들을 인덱싱&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if not WHOOSH_AVAILABLE or self.ix is None:&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        writer = self.ix.writer()&lt;br /&gt;
        &lt;br /&gt;
        for i, filepath in enumerate(files):&lt;br /&gt;
            try:&lt;br /&gt;
                with open(filepath, &#039;r&#039;, encoding=&#039;utf-8&#039;, errors=&#039;ignore&#039;) as f:&lt;br /&gt;
                    content = f.read()&lt;br /&gt;
                &lt;br /&gt;
                stat = filepath.stat()&lt;br /&gt;
                &lt;br /&gt;
                writer.update_document(&lt;br /&gt;
                    filepath=str(filepath),&lt;br /&gt;
                    filename=filepath.name,&lt;br /&gt;
                    content=content,&lt;br /&gt;
                    modified=datetime.fromtimestamp(stat.st_mtime),&lt;br /&gt;
                    size=stat.st_size&lt;br /&gt;
                )&lt;br /&gt;
                &lt;br /&gt;
                if progress_callback:&lt;br /&gt;
                    progress_callback(i + 1, len(files))&lt;br /&gt;
            &lt;br /&gt;
            except Exception as e:&lt;br /&gt;
                print(f&amp;quot;Error indexing {filepath}: {e}&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        writer.commit()&lt;br /&gt;
    &lt;br /&gt;
    def search(self, query_text: str, limit: int = 100) -&amp;gt; List[Dict]:&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;인덱스에서 검색&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if not WHOOSH_AVAILABLE or self.ix is None:&lt;br /&gt;
            return []&lt;br /&gt;
        &lt;br /&gt;
        results = []&lt;br /&gt;
        with self.ix.searcher() as searcher:&lt;br /&gt;
            query = MultifieldParser([&amp;quot;filename&amp;quot;, &amp;quot;content&amp;quot;], self.ix.schema).parse(query_text)&lt;br /&gt;
            search_results = searcher.search(query, limit=limit)&lt;br /&gt;
            &lt;br /&gt;
            for hit in search_results:&lt;br /&gt;
                results.append({&lt;br /&gt;
                    &#039;filepath&#039;: hit[&#039;filepath&#039;],&lt;br /&gt;
                    &#039;filename&#039;: hit[&#039;filename&#039;],&lt;br /&gt;
                    &#039;score&#039;: hit.score&lt;br /&gt;
                })&lt;br /&gt;
        &lt;br /&gt;
        return results&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class SQLScriptSearcher:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;SQL 스크립트 검색 엔진&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    def __init__(self, config_manager: ConfigManager):&lt;br /&gt;
        self.config = config_manager&lt;br /&gt;
        self.base_directories = self.config.get(&#039;search_directories&#039;)&lt;br /&gt;
        self.sql_extensions = self.config.get(&#039;sql_extensions&#039;)&lt;br /&gt;
        self.max_workers = self.config.get(&#039;search_settings&#039;, &#039;max_workers&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # Whoosh 인덱서 초기화&lt;br /&gt;
        if self.config.get(&#039;index_settings&#039;, &#039;enable_indexing&#039;) and WHOOSH_AVAILABLE:&lt;br /&gt;
            index_dir = self.config.get(&#039;index_settings&#039;, &#039;index_directory&#039;)&lt;br /&gt;
            self.indexer = WhooshIndexer(index_dir)&lt;br /&gt;
        else:&lt;br /&gt;
            self.indexer = None&lt;br /&gt;
        &lt;br /&gt;
        self.recent_searches = []&lt;br /&gt;
    &lt;br /&gt;
    def get_all_sql_files(self) -&amp;gt; List[Path]:&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;모든 SQL 관련 파일 수집&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        files = []&lt;br /&gt;
        for directory in self.base_directories:&lt;br /&gt;
            if not os.path.exists(directory):&lt;br /&gt;
                continue&lt;br /&gt;
            for ext in self.sql_extensions:&lt;br /&gt;
                files.extend(Path(directory).rglob(f&#039;*{ext}&#039;))&lt;br /&gt;
        return files&lt;br /&gt;
    &lt;br /&gt;
    def search(self, &lt;br /&gt;
               search_text: str, &lt;br /&gt;
               case_sensitive: bool = None,&lt;br /&gt;
               use_regex: bool = None,&lt;br /&gt;
               context_lines: int = None,&lt;br /&gt;
               use_ripgrep: bool = False,&lt;br /&gt;
               progress_callback=None) -&amp;gt; List[SearchResult]:&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        SQL 스크립트 파일에서 텍스트 검색&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # 기본값 설정&lt;br /&gt;
        if case_sensitive is None:&lt;br /&gt;
            case_sensitive = self.config.get(&#039;search_settings&#039;, &#039;case_sensitive&#039;)&lt;br /&gt;
        if use_regex is None:&lt;br /&gt;
            use_regex = self.config.get(&#039;search_settings&#039;, &#039;use_regex&#039;)&lt;br /&gt;
        if context_lines is None:&lt;br /&gt;
            context_lines = self.config.get(&#039;search_settings&#039;, &#039;default_context_lines&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # 검색 기록 추가&lt;br /&gt;
        self._add_to_recent_searches(search_text)&lt;br /&gt;
        &lt;br /&gt;
        # ripgrep 사용 가능하고 요청된 경우&lt;br /&gt;
        if use_ripgrep and RIPGREP_AVAILABLE:&lt;br /&gt;
            return self._search_with_ripgrep(search_text, case_sensitive, context_lines)&lt;br /&gt;
        &lt;br /&gt;
        # 기본 검색&lt;br /&gt;
        all_files = self.get_all_sql_files()&lt;br /&gt;
        &lt;br /&gt;
        if progress_callback:&lt;br /&gt;
            progress_callback(0, len(all_files))&lt;br /&gt;
        &lt;br /&gt;
        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:&lt;br /&gt;
            futures = []&lt;br /&gt;
            for filepath in all_files:&lt;br /&gt;
                future = executor.submit(&lt;br /&gt;
                    self._search_in_file, &lt;br /&gt;
                    filepath, &lt;br /&gt;
                    search_text, &lt;br /&gt;
                    case_sensitive, &lt;br /&gt;
                    use_regex, &lt;br /&gt;
                    context_lines&lt;br /&gt;
                )&lt;br /&gt;
                futures.append(future)&lt;br /&gt;
            &lt;br /&gt;
            results = []&lt;br /&gt;
            for i, future in enumerate(futures):&lt;br /&gt;
                file_results = future.result()&lt;br /&gt;
                results.extend(file_results)&lt;br /&gt;
                &lt;br /&gt;
                if progress_callback:&lt;br /&gt;
                    progress_callback(i + 1, len(all_files))&lt;br /&gt;
        &lt;br /&gt;
        return sorted(results, key=lambda x: (x.filepath, x.line_number))&lt;br /&gt;
    &lt;br /&gt;
    def _search_in_file(self, &lt;br /&gt;
                        filepath: Path, &lt;br /&gt;
                        search_text: str,&lt;br /&gt;
                        case_sensitive: bool,&lt;br /&gt;
                        use_regex: bool,&lt;br /&gt;
                        context_lines: int) -&amp;gt; List[SearchResult]:&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;단일 파일 내 검색&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        results = []&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            stat = filepath.stat()&lt;br /&gt;
            file_size = stat.st_size&lt;br /&gt;
            modified_date = datetime.fromtimestamp(stat.st_mtime)&lt;br /&gt;
            &lt;br /&gt;
            with open(filepath, &#039;r&#039;, encoding=&#039;utf-8&#039;, errors=&#039;ignore&#039;) as f:&lt;br /&gt;
                lines = f.readlines()&lt;br /&gt;
            &lt;br /&gt;
            if use_regex:&lt;br /&gt;
                flags = 0 if case_sensitive else re.IGNORECASE&lt;br /&gt;
                pattern = re.compile(search_text, flags)&lt;br /&gt;
            else:&lt;br /&gt;
                if not case_sensitive:&lt;br /&gt;
                    search_text = search_text.lower()&lt;br /&gt;
            &lt;br /&gt;
            for i, line in enumerate(lines):&lt;br /&gt;
                match_found = False&lt;br /&gt;
                &lt;br /&gt;
                if use_regex:&lt;br /&gt;
                    match_found = pattern.search(line) is not None&lt;br /&gt;
                else:&lt;br /&gt;
                    compare_line = line if case_sensitive else line.lower()&lt;br /&gt;
                    match_found = search_text in compare_line&lt;br /&gt;
                &lt;br /&gt;
                if match_found:&lt;br /&gt;
                    start_idx = max(0, i - context_lines)&lt;br /&gt;
                    end_idx = min(len(lines), i + context_lines + 1)&lt;br /&gt;
                    &lt;br /&gt;
                    context_before = [lines[j].rstrip() for j in range(start_idx, i)]&lt;br /&gt;
                    context_after = [lines[j].rstrip() for j in range(i + 1, end_idx)]&lt;br /&gt;
                    &lt;br /&gt;
                    results.append(SearchResult(&lt;br /&gt;
                        filepath=str(filepath),&lt;br /&gt;
                        line_number=i + 1,&lt;br /&gt;
                        line_content=line.rstrip(),&lt;br /&gt;
                        context_before=context_before,&lt;br /&gt;
                        context_after=context_after,&lt;br /&gt;
                        file_size=file_size,&lt;br /&gt;
                        modified_date=modified_date&lt;br /&gt;
                    ))&lt;br /&gt;
        &lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            pass&lt;br /&gt;
        &lt;br /&gt;
        return results&lt;br /&gt;
    &lt;br /&gt;
    def _search_with_ripgrep(self, search_text: str, case_sensitive: bool, context_lines: int) -&amp;gt; List[SearchResult]:&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;ripgrep을 사용한 빠른 검색&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        results = []&lt;br /&gt;
        &lt;br /&gt;
        for directory in self.base_directories:&lt;br /&gt;
            if not os.path.exists(directory):&lt;br /&gt;
                continue&lt;br /&gt;
            &lt;br /&gt;
            rg = Ripgrepy(search_text, directory)&lt;br /&gt;
            &lt;br /&gt;
            if not case_sensitive:&lt;br /&gt;
                rg = rg.ignore_case()&lt;br /&gt;
            &lt;br /&gt;
            rg = rg.with_filename().line_number()&lt;br /&gt;
            &lt;br /&gt;
            if context_lines &amp;gt; 0:&lt;br /&gt;
                rg = rg.context(context_lines)&lt;br /&gt;
            &lt;br /&gt;
            for ext in self.sql_extensions:&lt;br /&gt;
                rg = rg.glob(f&#039;*{ext}&#039;)&lt;br /&gt;
            &lt;br /&gt;
            try:&lt;br /&gt;
                for match in rg.run().as_dict:&lt;br /&gt;
                    filepath = Path(match[&#039;path&#039;])&lt;br /&gt;
                    stat = filepath.stat()&lt;br /&gt;
                    &lt;br /&gt;
                    results.append(SearchResult(&lt;br /&gt;
                        filepath=str(filepath),&lt;br /&gt;
                        line_number=match.get(&#039;line_number&#039;, 0),&lt;br /&gt;
                        line_content=match.get(&#039;line&#039;, &#039;&#039;),&lt;br /&gt;
                        context_before=[],&lt;br /&gt;
                        context_after=[],&lt;br /&gt;
                        file_size=stat.st_size,&lt;br /&gt;
                        modified_date=datetime.fromtimestamp(stat.st_mtime)&lt;br /&gt;
                    ))&lt;br /&gt;
            except Exception as e:&lt;br /&gt;
                print(f&amp;quot;Ripgrep error in {directory}: {e}&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        return results&lt;br /&gt;
    &lt;br /&gt;
    def search_sql_objects(self, &lt;br /&gt;
                          object_name: str, &lt;br /&gt;
                          object_types: List[str] = None,&lt;br /&gt;
                          progress_callback=None) -&amp;gt; List[SearchResult]:&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;SQL 객체(테이블, 프로시저, 함수 등) 검색&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if object_types is None:&lt;br /&gt;
            object_types = [&#039;TABLE&#039;, &#039;VIEW&#039;, &#039;PROCEDURE&#039;, &#039;FUNCTION&#039;, &#039;PACKAGE&#039;, &#039;TRIGGER&#039;]&lt;br /&gt;
        &lt;br /&gt;
        patterns = []&lt;br /&gt;
        for obj_type in object_types:&lt;br /&gt;
            if obj_type == &#039;TABLE&#039;:&lt;br /&gt;
                patterns.extend([&lt;br /&gt;
                    rf&#039;\bFROM\s+{object_name}\b&#039;,&lt;br /&gt;
                    rf&#039;\bJOIN\s+{object_name}\b&#039;,&lt;br /&gt;
                    rf&#039;\bINTO\s+{object_name}\b&#039;,&lt;br /&gt;
                    rf&#039;\bUPDATE\s+{object_name}\b&#039;,&lt;br /&gt;
                    rf&#039;\bDELETE\s+FROM\s+{object_name}\b&#039;&lt;br /&gt;
                ])&lt;br /&gt;
            elif obj_type == &#039;PROCEDURE&#039;:&lt;br /&gt;
                patterns.extend([&lt;br /&gt;
                    rf&#039;\bEXEC(UTE)?\s+{object_name}\b&#039;,&lt;br /&gt;
                    rf&#039;\bCREATE\s+(OR\s+REPLACE\s+)?PROCEDURE\s+{object_name}\b&#039;&lt;br /&gt;
                ])&lt;br /&gt;
            elif obj_type == &#039;FUNCTION&#039;:&lt;br /&gt;
                patterns.extend([&lt;br /&gt;
                    rf&#039;\b{object_name}\s*\(&#039;,&lt;br /&gt;
                    rf&#039;\bCREATE\s+(OR\s+REPLACE\s+)?FUNCTION\s+{object_name}\b&#039;&lt;br /&gt;
                ])&lt;br /&gt;
            elif obj_type == &#039;PACKAGE&#039;:&lt;br /&gt;
                patterns.extend([&lt;br /&gt;
                    rf&#039;\b{object_name}\.&#039;,&lt;br /&gt;
                    rf&#039;\bCREATE\s+(OR\s+REPLACE\s+)?PACKAGE\s+{object_name}\b&#039;&lt;br /&gt;
                ])&lt;br /&gt;
            elif obj_type == &#039;VIEW&#039;:&lt;br /&gt;
                patterns.extend([&lt;br /&gt;
                    rf&#039;\bFROM\s+{object_name}\b&#039;,&lt;br /&gt;
                    rf&#039;\bCREATE\s+(OR\s+REPLACE\s+)?VIEW\s+{object_name}\b&#039;&lt;br /&gt;
                ])&lt;br /&gt;
            elif obj_type == &#039;TRIGGER&#039;:&lt;br /&gt;
                patterns.append(rf&#039;\bCREATE\s+(OR\s+REPLACE\s+)?TRIGGER\s+{object_name}\b&#039;)&lt;br /&gt;
        &lt;br /&gt;
        combined_pattern = &#039;|&#039;.join(patterns)&lt;br /&gt;
        return self.search(&lt;br /&gt;
            combined_pattern, &lt;br /&gt;
            case_sensitive=False, &lt;br /&gt;
            use_regex=True,&lt;br /&gt;
            progress_callback=progress_callback&lt;br /&gt;
        )&lt;br /&gt;
    &lt;br /&gt;
    def _add_to_recent_searches(self, search_text: str):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;최근 검색어 추가&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if search_text in self.recent_searches:&lt;br /&gt;
            self.recent_searches.remove(search_text)&lt;br /&gt;
        self.recent_searches.insert(0, search_text)&lt;br /&gt;
        &lt;br /&gt;
        max_recent = self.config.get(&#039;ui_settings&#039;, &#039;recent_searches_count&#039;)&lt;br /&gt;
        self.recent_searches = self.recent_searches[:max_recent]&lt;br /&gt;
    &lt;br /&gt;
    def export_results(self, results: List[SearchResult], output_file: str, format: str = &#039;txt&#039;):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;검색 결과를 파일로 저장&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if format == &#039;txt&#039;:&lt;br /&gt;
            self._export_txt(results, output_file)&lt;br /&gt;
        elif format == &#039;html&#039;:&lt;br /&gt;
            self._export_html(results, output_file)&lt;br /&gt;
        elif format == &#039;csv&#039;:&lt;br /&gt;
            self._export_csv(results, output_file)&lt;br /&gt;
    &lt;br /&gt;
    def _export_txt(self, results: List[SearchResult], output_file: str):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;텍스트 형식으로 저장&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        with open(output_file, &#039;w&#039;, encoding=&#039;utf-8&#039;) as f:&lt;br /&gt;
            f.write(f&amp;quot;검색 결과: {len(results)}건\n&amp;quot;)&lt;br /&gt;
            f.write(&amp;quot;=&amp;quot; * 80 + &amp;quot;\n\n&amp;quot;)&lt;br /&gt;
            &lt;br /&gt;
            for result in results:&lt;br /&gt;
                f.write(f&amp;quot;파일: {result.filepath}\n&amp;quot;)&lt;br /&gt;
                f.write(f&amp;quot;라인: {result.line_number}\n&amp;quot;)&lt;br /&gt;
                f.write(f&amp;quot;수정일: {result.modified_date.strftime(&#039;%Y-%m-%d %H:%M:%S&#039;)}\n&amp;quot;)&lt;br /&gt;
                f.write(&amp;quot;-&amp;quot; * 80 + &amp;quot;\n&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
                for i, line in enumerate(result.context_before):&lt;br /&gt;
                    f.write(f&amp;quot;  {result.line_number - len(result.context_before) + i}: {line}\n&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
                f.write(f&amp;quot;&amp;gt;&amp;gt;&amp;gt;{result.line_number}: {result.line_content}\n&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
                for i, line in enumerate(result.context_after):&lt;br /&gt;
                    f.write(f&amp;quot;  {result.line_number + i + 1}: {line}\n&amp;quot;)&lt;br /&gt;
                &lt;br /&gt;
                f.write(&amp;quot;\n&amp;quot; + &amp;quot;=&amp;quot; * 80 + &amp;quot;\n\n&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    def _export_csv(self, results: List[SearchResult], output_file: str):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;CSV 형식으로 저장&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        import csv&lt;br /&gt;
        &lt;br /&gt;
        with open(output_file, &#039;w&#039;, encoding=&#039;utf-8-sig&#039;, newline=&#039;&#039;) as f:&lt;br /&gt;
            writer = csv.writer(f)&lt;br /&gt;
            writer.writerow([&#039;파일경로&#039;, &#039;라인번호&#039;, &#039;내용&#039;, &#039;파일크기(bytes)&#039;, &#039;수정일&#039;])&lt;br /&gt;
            &lt;br /&gt;
            for result in results:&lt;br /&gt;
                writer.writerow([&lt;br /&gt;
                    result.filepath,&lt;br /&gt;
                    result.line_number,&lt;br /&gt;
                    result.line_content,&lt;br /&gt;
                    result.file_size,&lt;br /&gt;
                    result.modified_date.strftime(&#039;%Y-%m-%d %H:%M:%S&#039;)&lt;br /&gt;
                ])&lt;br /&gt;
    &lt;br /&gt;
    def _export_html(self, results: List[SearchResult], output_file: str):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;HTML 형식으로 저장&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        html = f&amp;quot;&amp;quot;&amp;quot;&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;SQL 스크립트 검색 결과&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        body {{ font-family: &#039;Consolas&#039;, &#039;Courier New&#039;, monospace; margin: 20px; background: #f5f5f5; }}&lt;br /&gt;
        h1 {{ color: #333; border-bottom: 3px solid #0066cc; padding-bottom: 10px; }}&lt;br /&gt;
        .result {{ border: 1px solid #ddd; margin: 15px 0; padding: 15px; background: white; border-radius: 5px; }}&lt;br /&gt;
        .filepath {{ font-weight: bold; color: #0066cc; font-size: 14px; }}&lt;br /&gt;
        .meta {{ color: #666; font-size: 12px; margin: 5px 0; }}&lt;br /&gt;
        .line-number {{ color: #999; display: inline-block; width: 50px; text-align: right; margin-right: 10px; }}&lt;br /&gt;
        .matched-line {{ background: #ffff99; font-weight: bold; padding: 2px 0; }}&lt;br /&gt;
        .context {{ color: #333; }}&lt;br /&gt;
        pre {{ margin: 10px 0; line-height: 1.4; }}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;h1&amp;gt;SQL 스크립트 검색 결과 ({len(results)} 건)&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
        for result in results:&lt;br /&gt;
            html += f&amp;quot;&amp;quot;&amp;quot;    &amp;lt;div class=&amp;quot;result&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;div class=&amp;quot;filepath&amp;quot;&amp;gt;📄 {result.filepath}&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;div class=&amp;quot;meta&amp;quot;&amp;gt;라인: {result.line_number} | 크기: {result.file_size:,} bytes | 수정일: {result.modified_date.strftime(&#039;%Y-%m-%d %H:%M:%S&#039;)}&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;pre class=&amp;quot;context&amp;quot;&amp;gt;&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            &lt;br /&gt;
            for i, line in enumerate(result.context_before):&lt;br /&gt;
                line_num = result.line_number - len(result.context_before) + i&lt;br /&gt;
                html += f&#039;&amp;lt;span class=&amp;quot;line-number&amp;quot;&amp;gt;{line_num}&amp;lt;/span&amp;gt;{self._html_escape(line)}\n&#039;&lt;br /&gt;
            &lt;br /&gt;
            html += f&#039;&amp;lt;span class=&amp;quot;matched-line&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;line-number&amp;quot;&amp;gt;{result.line_number}&amp;lt;/span&amp;gt;{self._html_escape(result.line_content)}&amp;lt;/span&amp;gt;\n&#039;&lt;br /&gt;
            &lt;br /&gt;
            for i, line in enumerate(result.context_after):&lt;br /&gt;
                line_num = result.line_number + i + 1&lt;br /&gt;
                html += f&#039;&amp;lt;span class=&amp;quot;line-number&amp;quot;&amp;gt;{line_num}&amp;lt;/span&amp;gt;{self._html_escape(line)}\n&#039;&lt;br /&gt;
            &lt;br /&gt;
            html += &amp;quot;&amp;quot;&amp;quot;&amp;lt;/pre&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
        html += &amp;quot;&amp;quot;&amp;quot;&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
        with open(output_file, &#039;w&#039;, encoding=&#039;utf-8&#039;) as f:&lt;br /&gt;
            f.write(html)&lt;br /&gt;
    &lt;br /&gt;
    def _html_escape(self, text: str) -&amp;gt; str:&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;HTML 특수문자 이스케이프&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        return (text.replace(&#039;&amp;amp;&#039;, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
                   .replace(&#039;&amp;lt;&#039;, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
                   .replace(&#039;&amp;gt;&#039;, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
                   .replace(&#039;&amp;quot;&#039;, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
                   .replace(&amp;quot;&#039;&amp;quot;, &#039;&amp;amp;#39;&#039;))&lt;br /&gt;
&lt;br /&gt;
import sys&lt;br /&gt;
import os&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
from datetime import datetime&lt;br /&gt;
from typing import List&lt;br /&gt;
&lt;br /&gt;
from PyQt5.QtWidgets import (&lt;br /&gt;
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,&lt;br /&gt;
    QPushButton, QLineEdit, QTextEdit, QFileDialog, QCheckBox,&lt;br /&gt;
    QLabel, QSpinBox, QComboBox, QTableWidget, QTableWidgetItem,&lt;br /&gt;
    QProgressBar, QSplitter, QGroupBox, QMessageBox, QTabWidget,&lt;br /&gt;
    QHeaderView, QMenu, QAction&lt;br /&gt;
)&lt;br /&gt;
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer&lt;br /&gt;
from PyQt5.QtGui import QFont, QColor, QTextCursor, QIcon&lt;br /&gt;
&lt;br /&gt;
from sql_searcher_core import (&lt;br /&gt;
    SQLScriptSearcher, ConfigManager, SearchResult,&lt;br /&gt;
    RIPGREP_AVAILABLE, WHOOSH_AVAILABLE&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class SearchThread(QThread):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;검색 작업을 별도 스레드에서 실행&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    progress = pyqtSignal(int, int)  # current, total&lt;br /&gt;
    finished = pyqtSignal(list)  # results&lt;br /&gt;
    error = pyqtSignal(str)  # error message&lt;br /&gt;
    &lt;br /&gt;
    def __init__(self, searcher, search_params):&lt;br /&gt;
        super().__init__()&lt;br /&gt;
        self.searcher = searcher&lt;br /&gt;
        self.search_params = search_params&lt;br /&gt;
    &lt;br /&gt;
    def run(self):&lt;br /&gt;
        try:&lt;br /&gt;
            results = self.searcher.search(&lt;br /&gt;
                **self.search_params,&lt;br /&gt;
                progress_callback=self.progress.emit&lt;br /&gt;
            )&lt;br /&gt;
            self.finished.emit(results)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            self.error.emit(str(e))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class IndexingThread(QThread):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;인덱싱 작업을 별도 스레드에서 실행&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    progress = pyqtSignal(int, int)&lt;br /&gt;
    finished = pyqtSignal()&lt;br /&gt;
    error = pyqtSignal(str)&lt;br /&gt;
    &lt;br /&gt;
    def __init__(self, searcher):&lt;br /&gt;
        super().__init__()&lt;br /&gt;
        self.searcher = searcher&lt;br /&gt;
    &lt;br /&gt;
    def run(self):&lt;br /&gt;
        try:&lt;br /&gt;
            if self.searcher.indexer:&lt;br /&gt;
                files = self.searcher.get_all_sql_files()&lt;br /&gt;
                self.searcher.indexer.index_files(files, self.progress.emit)&lt;br /&gt;
            self.finished.emit()&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            self.error.emit(str(e))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class SQLSearcherGUI(QMainWindow):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;SQL 스크립트 검색기 메인 GUI&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        super().__init__()&lt;br /&gt;
        &lt;br /&gt;
        # 설정 및 검색 엔진 초기화&lt;br /&gt;
        self.config = ConfigManager()&lt;br /&gt;
        self.searcher = SQLScriptSearcher(self.config)&lt;br /&gt;
        self.current_results = []&lt;br /&gt;
        self.search_thread = None&lt;br /&gt;
        &lt;br /&gt;
        self.init_ui()&lt;br /&gt;
        self.load_settings()&lt;br /&gt;
    &lt;br /&gt;
    def init_ui(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;UI 초기화&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        self.setWindowTitle(&amp;quot;SQL Script Searcher - Professional Edition&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 창 크기 설정&lt;br /&gt;
        width = self.config.get(&#039;ui_settings&#039;, &#039;window_width&#039;)&lt;br /&gt;
        height = self.config.get(&#039;ui_settings&#039;, &#039;window_height&#039;)&lt;br /&gt;
        self.resize(width, height)&lt;br /&gt;
        &lt;br /&gt;
        # 중앙 위젯&lt;br /&gt;
        central_widget = QWidget()&lt;br /&gt;
        self.setCentralWidget(central_widget)&lt;br /&gt;
        &lt;br /&gt;
        # 메인 레이아웃&lt;br /&gt;
        main_layout = QVBoxLayout(central_widget)&lt;br /&gt;
        &lt;br /&gt;
        # 탭 위젯 생성&lt;br /&gt;
        self.tabs = QTabWidget()&lt;br /&gt;
        main_layout.addWidget(self.tabs)&lt;br /&gt;
        &lt;br /&gt;
        # 검색 탭&lt;br /&gt;
        search_tab = self.create_search_tab()&lt;br /&gt;
        self.tabs.addTab(search_tab, &amp;quot;🔍 검색&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 설정 탭&lt;br /&gt;
        settings_tab = self.create_settings_tab()&lt;br /&gt;
        self.tabs.addTab(settings_tab, &amp;quot;⚙️ 설정&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 인덱스 탭&lt;br /&gt;
        if WHOOSH_AVAILABLE:&lt;br /&gt;
            index_tab = self.create_index_tab()&lt;br /&gt;
            self.tabs.addTab(index_tab, &amp;quot;📇 인덱스&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 상태바&lt;br /&gt;
        self.statusBar().showMessage(&amp;quot;준비&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 메뉴바&lt;br /&gt;
        self.create_menu_bar()&lt;br /&gt;
    &lt;br /&gt;
    def create_menu_bar(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;메뉴바 생성&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        menubar = self.menuBar()&lt;br /&gt;
        &lt;br /&gt;
        # 파일 메뉴&lt;br /&gt;
        file_menu = menubar.addMenu(&amp;quot;파일&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        export_action = QAction(&amp;quot;결과 내보내기&amp;quot;, self)&lt;br /&gt;
        export_action.setShortcut(&amp;quot;Ctrl+E&amp;quot;)&lt;br /&gt;
        export_action.triggered.connect(self.export_results)&lt;br /&gt;
        file_menu.addAction(export_action)&lt;br /&gt;
        &lt;br /&gt;
        file_menu.addSeparator()&lt;br /&gt;
        &lt;br /&gt;
        exit_action = QAction(&amp;quot;종료&amp;quot;, self)&lt;br /&gt;
        exit_action.setShortcut(&amp;quot;Ctrl+Q&amp;quot;)&lt;br /&gt;
        exit_action.triggered.connect(self.close)&lt;br /&gt;
        file_menu.addAction(exit_action)&lt;br /&gt;
        &lt;br /&gt;
        # 도구 메뉴&lt;br /&gt;
        tools_menu = menubar.addMenu(&amp;quot;도구&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        refresh_action = QAction(&amp;quot;파일 목록 새로고침&amp;quot;, self)&lt;br /&gt;
        refresh_action.setShortcut(&amp;quot;F5&amp;quot;)&lt;br /&gt;
        refresh_action.triggered.connect(self.refresh_file_list)&lt;br /&gt;
        tools_menu.addAction(refresh_action)&lt;br /&gt;
        &lt;br /&gt;
        if WHOOSH_AVAILABLE:&lt;br /&gt;
            index_action = QAction(&amp;quot;인덱스 재구성&amp;quot;, self)&lt;br /&gt;
            index_action.triggered.connect(self.rebuild_index)&lt;br /&gt;
            tools_menu.addAction(index_action)&lt;br /&gt;
        &lt;br /&gt;
        # 도움말 메뉴&lt;br /&gt;
        help_menu = menubar.addMenu(&amp;quot;도움말&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        about_action = QAction(&amp;quot;정보&amp;quot;, self)&lt;br /&gt;
        about_action.triggered.connect(self.show_about)&lt;br /&gt;
        help_menu.addAction(about_action)&lt;br /&gt;
    &lt;br /&gt;
    def create_search_tab(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;검색 탭 생성&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        widget = QWidget()&lt;br /&gt;
        layout = QVBoxLayout(widget)&lt;br /&gt;
        &lt;br /&gt;
        # 검색 입력 영역&lt;br /&gt;
        search_group = QGroupBox(&amp;quot;검색 조건&amp;quot;)&lt;br /&gt;
        search_layout = QVBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        # 검색어 입력&lt;br /&gt;
        input_layout = QHBoxLayout()&lt;br /&gt;
        input_layout.addWidget(QLabel(&amp;quot;검색어:&amp;quot;))&lt;br /&gt;
        &lt;br /&gt;
        self.search_input = QComboBox()&lt;br /&gt;
        self.search_input.setEditable(True)&lt;br /&gt;
        self.search_input.setMinimumWidth(400)&lt;br /&gt;
        self.search_input.lineEdit().returnPressed.connect(self.start_search)&lt;br /&gt;
        input_layout.addWidget(self.search_input, 1)&lt;br /&gt;
        &lt;br /&gt;
        self.search_button = QPushButton(&amp;quot;🔍 검색&amp;quot;)&lt;br /&gt;
        self.search_button.clicked.connect(self.start_search)&lt;br /&gt;
        self.search_button.setMinimumWidth(100)&lt;br /&gt;
        input_layout.addWidget(self.search_button)&lt;br /&gt;
        &lt;br /&gt;
        search_layout.addLayout(input_layout)&lt;br /&gt;
        &lt;br /&gt;
        # 검색 옵션&lt;br /&gt;
        options_layout = QHBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        self.case_sensitive_cb = QCheckBox(&amp;quot;대소문자 구분&amp;quot;)&lt;br /&gt;
        options_layout.addWidget(self.case_sensitive_cb)&lt;br /&gt;
        &lt;br /&gt;
        self.regex_cb = QCheckBox(&amp;quot;정규표현식&amp;quot;)&lt;br /&gt;
        options_layout.addWidget(self.regex_cb)&lt;br /&gt;
        &lt;br /&gt;
        if RIPGREP_AVAILABLE:&lt;br /&gt;
            self.ripgrep_cb = QCheckBox(&amp;quot;ripgrep 사용&amp;quot;)&lt;br /&gt;
            options_layout.addWidget(self.ripgrep_cb)&lt;br /&gt;
        &lt;br /&gt;
        options_layout.addWidget(QLabel(&amp;quot;컨텍스트 라인:&amp;quot;))&lt;br /&gt;
        self.context_spin = QSpinBox()&lt;br /&gt;
        self.context_spin.setRange(0, 10)&lt;br /&gt;
        self.context_spin.setValue(2)&lt;br /&gt;
        options_layout.addWidget(self.context_spin)&lt;br /&gt;
        &lt;br /&gt;
        options_layout.addStretch()&lt;br /&gt;
        search_layout.addLayout(options_layout)&lt;br /&gt;
        &lt;br /&gt;
        search_group.setLayout(search_layout)&lt;br /&gt;
        layout.addWidget(search_group)&lt;br /&gt;
        &lt;br /&gt;
        # SQL 객체 검색&lt;br /&gt;
        object_group = QGroupBox(&amp;quot;SQL 객체 검색&amp;quot;)&lt;br /&gt;
        object_layout = QHBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        object_layout.addWidget(QLabel(&amp;quot;객체명:&amp;quot;))&lt;br /&gt;
        self.object_name_input = QLineEdit()&lt;br /&gt;
        object_layout.addWidget(self.object_name_input, 1)&lt;br /&gt;
        &lt;br /&gt;
        object_layout.addWidget(QLabel(&amp;quot;타입:&amp;quot;))&lt;br /&gt;
        self.object_type_combo = QComboBox()&lt;br /&gt;
        self.object_type_combo.addItems([&lt;br /&gt;
            &amp;quot;모두&amp;quot;, &amp;quot;TABLE&amp;quot;, &amp;quot;VIEW&amp;quot;, &amp;quot;PROCEDURE&amp;quot;, &lt;br /&gt;
            &amp;quot;FUNCTION&amp;quot;, &amp;quot;PACKAGE&amp;quot;, &amp;quot;TRIGGER&amp;quot;&lt;br /&gt;
        ])&lt;br /&gt;
        object_layout.addWidget(self.object_type_combo)&lt;br /&gt;
        &lt;br /&gt;
        self.object_search_button = QPushButton(&amp;quot;객체 검색&amp;quot;)&lt;br /&gt;
        self.object_search_button.clicked.connect(self.search_sql_object)&lt;br /&gt;
        object_layout.addWidget(self.object_search_button)&lt;br /&gt;
        &lt;br /&gt;
        object_group.setLayout(object_layout)&lt;br /&gt;
        layout.addWidget(object_group)&lt;br /&gt;
        &lt;br /&gt;
        # 진행률 바&lt;br /&gt;
        self.progress_bar = QProgressBar()&lt;br /&gt;
        self.progress_bar.setVisible(False)&lt;br /&gt;
        layout.addWidget(self.progress_bar)&lt;br /&gt;
        &lt;br /&gt;
        # 결과 영역 (Splitter 사용)&lt;br /&gt;
        splitter = QSplitter(Qt.Vertical)&lt;br /&gt;
        &lt;br /&gt;
        # 결과 테이블&lt;br /&gt;
        self.result_table = QTableWidget()&lt;br /&gt;
        self.result_table.setColumnCount(4)&lt;br /&gt;
        self.result_table.setHorizontalHeaderLabels([&amp;quot;파일&amp;quot;, &amp;quot;라인&amp;quot;, &amp;quot;크기&amp;quot;, &amp;quot;수정일&amp;quot;])&lt;br /&gt;
        self.result_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)&lt;br /&gt;
        self.result_table.setSelectionBehavior(QTableWidget.SelectRows)&lt;br /&gt;
        self.result_table.setAlternatingRowColors(True)&lt;br /&gt;
        self.result_table.itemSelectionChanged.connect(self.show_result_detail)&lt;br /&gt;
        self.result_table.setContextMenuPolicy(Qt.CustomContextMenu)&lt;br /&gt;
        self.result_table.customContextMenuRequested.connect(self.show_context_menu)&lt;br /&gt;
        splitter.addWidget(self.result_table)&lt;br /&gt;
        &lt;br /&gt;
        # 상세 내용 표시&lt;br /&gt;
        detail_widget = QWidget()&lt;br /&gt;
        detail_layout = QVBoxLayout(detail_widget)&lt;br /&gt;
        detail_layout.setContentsMargins(0, 0, 0, 0)&lt;br /&gt;
        &lt;br /&gt;
        self.detail_label = QLabel(&amp;quot;검색 결과를 선택하세요&amp;quot;)&lt;br /&gt;
        detail_layout.addWidget(self.detail_label)&lt;br /&gt;
        &lt;br /&gt;
        self.detail_text = QTextEdit()&lt;br /&gt;
        self.detail_text.setReadOnly(True)&lt;br /&gt;
        self.detail_text.setFont(QFont(&amp;quot;Consolas&amp;quot;, 10))&lt;br /&gt;
        detail_layout.addWidget(self.detail_text)&lt;br /&gt;
        &lt;br /&gt;
        splitter.addWidget(detail_widget)&lt;br /&gt;
        splitter.setSizes([300, 400])&lt;br /&gt;
        &lt;br /&gt;
        layout.addWidget(splitter, 1)&lt;br /&gt;
        &lt;br /&gt;
        # 결과 요약&lt;br /&gt;
        self.result_summary = QLabel(&amp;quot;검색 결과: 0건&amp;quot;)&lt;br /&gt;
        layout.addWidget(self.result_summary)&lt;br /&gt;
        &lt;br /&gt;
        return widget&lt;br /&gt;
    &lt;br /&gt;
    def create_settings_tab(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;설정 탭 생성&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        widget = QWidget()&lt;br /&gt;
        layout = QVBoxLayout(widget)&lt;br /&gt;
        &lt;br /&gt;
        # 검색 디렉토리 설정&lt;br /&gt;
        dir_group = QGroupBox(&amp;quot;검색 디렉토리&amp;quot;)&lt;br /&gt;
        dir_layout = QVBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        self.dir_list = QTextEdit()&lt;br /&gt;
        self.dir_list.setMaximumHeight(150)&lt;br /&gt;
        dirs = self.config.get(&#039;search_directories&#039;)&lt;br /&gt;
        self.dir_list.setPlainText(&#039;\n&#039;.join(dirs))&lt;br /&gt;
        dir_layout.addWidget(self.dir_list)&lt;br /&gt;
        &lt;br /&gt;
        dir_button_layout = QHBoxLayout()&lt;br /&gt;
        add_dir_button = QPushButton(&amp;quot;디렉토리 추가&amp;quot;)&lt;br /&gt;
        add_dir_button.clicked.connect(self.add_directory)&lt;br /&gt;
        dir_button_layout.addWidget(add_dir_button)&lt;br /&gt;
        &lt;br /&gt;
        clear_dir_button = QPushButton(&amp;quot;목록 지우기&amp;quot;)&lt;br /&gt;
        clear_dir_button.clicked.connect(lambda: self.dir_list.clear())&lt;br /&gt;
        dir_button_layout.addWidget(clear_dir_button)&lt;br /&gt;
        dir_button_layout.addStretch()&lt;br /&gt;
        &lt;br /&gt;
        dir_layout.addLayout(dir_button_layout)&lt;br /&gt;
        dir_group.setLayout(dir_layout)&lt;br /&gt;
        layout.addWidget(dir_group)&lt;br /&gt;
        &lt;br /&gt;
        # 파일 확장자 설정&lt;br /&gt;
        ext_group = QGroupBox(&amp;quot;검색 대상 확장자&amp;quot;)&lt;br /&gt;
        ext_layout = QVBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        self.ext_list = QTextEdit()&lt;br /&gt;
        self.ext_list.setMaximumHeight(100)&lt;br /&gt;
        exts = self.config.get(&#039;sql_extensions&#039;)&lt;br /&gt;
        self.ext_list.setPlainText(&#039;\n&#039;.join(exts))&lt;br /&gt;
        ext_layout.addWidget(self.ext_list)&lt;br /&gt;
        &lt;br /&gt;
        ext_group.setLayout(ext_layout)&lt;br /&gt;
        layout.addWidget(ext_group)&lt;br /&gt;
        &lt;br /&gt;
        # 성능 설정&lt;br /&gt;
        perf_group = QGroupBox(&amp;quot;성능 설정&amp;quot;)&lt;br /&gt;
        perf_layout = QHBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        perf_layout.addWidget(QLabel(&amp;quot;동시 작업 스레드:&amp;quot;))&lt;br /&gt;
        self.workers_spin = QSpinBox()&lt;br /&gt;
        self.workers_spin.setRange(1, 16)&lt;br /&gt;
        self.workers_spin.setValue(self.config.get(&#039;search_settings&#039;, &#039;max_workers&#039;))&lt;br /&gt;
        perf_layout.addWidget(self.workers_spin)&lt;br /&gt;
        &lt;br /&gt;
        perf_layout.addStretch()&lt;br /&gt;
        perf_group.setLayout(perf_layout)&lt;br /&gt;
        layout.addWidget(perf_group)&lt;br /&gt;
        &lt;br /&gt;
        # 시스템 정보&lt;br /&gt;
        info_group = QGroupBox(&amp;quot;시스템 정보&amp;quot;)&lt;br /&gt;
        info_layout = QVBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        info_text = f&amp;quot;&amp;quot;&amp;quot;Python: {sys.version.split()[0]}&lt;br /&gt;
PyQt5: {from PyQt5.QtCore import QT_VERSION_STR; QT_VERSION_STR}&lt;br /&gt;
ripgrep: {&#039;사용 가능&#039; if RIPGREP_AVAILABLE else &#039;사용 불가&#039;}&lt;br /&gt;
Whoosh: {&#039;사용 가능&#039; if WHOOSH_AVAILABLE else &#039;사용 불가&#039;}&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
        info_label = QLabel(info_text)&lt;br /&gt;
        info_label.setFont(QFont(&amp;quot;Consolas&amp;quot;, 9))&lt;br /&gt;
        info_layout.addWidget(info_label)&lt;br /&gt;
        &lt;br /&gt;
        info_group.setLayout(info_layout)&lt;br /&gt;
        layout.addWidget(info_group)&lt;br /&gt;
        &lt;br /&gt;
        # 저장 버튼&lt;br /&gt;
        save_layout = QHBoxLayout()&lt;br /&gt;
        save_layout.addStretch()&lt;br /&gt;
        &lt;br /&gt;
        save_button = QPushButton(&amp;quot;💾 설정 저장&amp;quot;)&lt;br /&gt;
        save_button.clicked.connect(self.save_settings)&lt;br /&gt;
        save_button.setMinimumWidth(150)&lt;br /&gt;
        save_layout.addWidget(save_button)&lt;br /&gt;
        &lt;br /&gt;
        layout.addLayout(save_layout)&lt;br /&gt;
        layout.addStretch()&lt;br /&gt;
        &lt;br /&gt;
        return widget&lt;br /&gt;
    &lt;br /&gt;
    def create_index_tab(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;인덱스 탭 생성&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        widget = QWidget()&lt;br /&gt;
        layout = QVBoxLayout(widget)&lt;br /&gt;
        &lt;br /&gt;
        info_label = QLabel(&amp;quot;Whoosh 전문 검색 엔진을 사용한 빠른 검색\n&amp;quot;&lt;br /&gt;
                           &amp;quot;파일을 인덱싱하면 검색 속도가 크게 향상됩니다.&amp;quot;)&lt;br /&gt;
        layout.addWidget(info_label)&lt;br /&gt;
        &lt;br /&gt;
        # 인덱스 상태&lt;br /&gt;
        status_group = QGroupBox(&amp;quot;인덱스 상태&amp;quot;)&lt;br /&gt;
        status_layout = QVBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        self.index_status_label = QLabel(&amp;quot;인덱스 정보를 불러오는 중...&amp;quot;)&lt;br /&gt;
        status_layout.addWidget(self.index_status_label)&lt;br /&gt;
        &lt;br /&gt;
        status_group.setLayout(status_layout)&lt;br /&gt;
        layout.addWidget(status_group)&lt;br /&gt;
        &lt;br /&gt;
        # 인덱스 작업&lt;br /&gt;
        action_group = QGroupBox(&amp;quot;인덱스 작업&amp;quot;)&lt;br /&gt;
        action_layout = QVBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        self.index_progress = QProgressBar()&lt;br /&gt;
        self.index_progress.setVisible(False)&lt;br /&gt;
        action_layout.addWidget(self.index_progress)&lt;br /&gt;
        &lt;br /&gt;
        button_layout = QHBoxLayout()&lt;br /&gt;
        &lt;br /&gt;
        rebuild_button = QPushButton(&amp;quot;🔄 인덱스 재구성&amp;quot;)&lt;br /&gt;
        rebuild_button.clicked.connect(self.rebuild_index)&lt;br /&gt;
        button_layout.addWidget(rebuild_button)&lt;br /&gt;
        &lt;br /&gt;
        button_layout.addStretch()&lt;br /&gt;
        action_layout.addLayout(button_layout)&lt;br /&gt;
        &lt;br /&gt;
        action_group.setLayout(action_layout)&lt;br /&gt;
        layout.addWidget(action_group)&lt;br /&gt;
        &lt;br /&gt;
        layout.addStretch()&lt;br /&gt;
        &lt;br /&gt;
        # 인덱스 정보 업데이트&lt;br /&gt;
        QTimer.singleShot(500, self.update_index_status)&lt;br /&gt;
        &lt;br /&gt;
        return widget&lt;br /&gt;
    &lt;br /&gt;
    def start_search(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;검색 시작&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        search_text = self.search_input.currentText().strip()&lt;br /&gt;
        &lt;br /&gt;
        if not search_text:&lt;br /&gt;
            QMessageBox.warning(self, &amp;quot;검색&amp;quot;, &amp;quot;검색어를 입력하세요.&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        # 검색 버튼 비활성화&lt;br /&gt;
        self.search_button.setEnabled(False)&lt;br /&gt;
        self.object_search_button.setEnabled(False)&lt;br /&gt;
        &lt;br /&gt;
        # 진행률 바 표시&lt;br /&gt;
        self.progress_bar.setVisible(True)&lt;br /&gt;
        self.progress_bar.setValue(0)&lt;br /&gt;
        &lt;br /&gt;
        # 상태바 업데이트&lt;br /&gt;
        self.statusBar().showMessage(&amp;quot;검색 중...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 검색 파라미터&lt;br /&gt;
        search_params = {&lt;br /&gt;
            &#039;search_text&#039;: search_text,&lt;br /&gt;
            &#039;case_sensitive&#039;: self.case_sensitive_cb.isChecked(),&lt;br /&gt;
            &#039;use_regex&#039;: self.regex_cb.isChecked(),&lt;br /&gt;
            &#039;context_lines&#039;: self.context_spin.value(),&lt;br /&gt;
            &#039;use_ripgrep&#039;: self.ripgrep_cb.isChecked() if RIPGREP_AVAILABLE else False&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        # 검색 스레드 시작&lt;br /&gt;
        self.search_thread = SearchThread(self.searcher, search_params)&lt;br /&gt;
        self.search_thread.progress.connect(self.update_progress)&lt;br /&gt;
        self.search_thread.finished.connect(self.search_finished)&lt;br /&gt;
        self.search_thread.error.connect(self.search_error)&lt;br /&gt;
        self.search_thread.start()&lt;br /&gt;
    &lt;br /&gt;
    def search_sql_object(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;SQL 객체 검색&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        object_name = self.object_name_input.text().strip()&lt;br /&gt;
        &lt;br /&gt;
        if not object_name:&lt;br /&gt;
            QMessageBox.warning(self, &amp;quot;객체 검색&amp;quot;, &amp;quot;객체명을 입력하세요.&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        # 검색 버튼 비활성화&lt;br /&gt;
        self.search_button.setEnabled(False)&lt;br /&gt;
        self.object_search_button.setEnabled(False)&lt;br /&gt;
        &lt;br /&gt;
        # 진행률 바 표시&lt;br /&gt;
        self.progress_bar.setVisible(True)&lt;br /&gt;
        self.progress_bar.setValue(0)&lt;br /&gt;
        &lt;br /&gt;
        # 상태바 업데이트&lt;br /&gt;
        self.statusBar().showMessage(f&amp;quot;&#039;{object_name}&#039; 객체 검색 중...&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 객체 타입&lt;br /&gt;
        object_type = self.object_type_combo.currentText()&lt;br /&gt;
        object_types = None if object_type == &amp;quot;모두&amp;quot; else [object_type]&lt;br /&gt;
        &lt;br /&gt;
        # 별도 스레드에서 실행&lt;br /&gt;
        from threading import Thread&lt;br /&gt;
        &lt;br /&gt;
        def search_worker():&lt;br /&gt;
            try:&lt;br /&gt;
                results = self.searcher.search_sql_objects(&lt;br /&gt;
                    object_name,&lt;br /&gt;
                    object_types,&lt;br /&gt;
                    progress_callback=lambda c, t: self.progress_bar.setValue(int(c/t*100))&lt;br /&gt;
                )&lt;br /&gt;
                self.search_finished(results)&lt;br /&gt;
            except Exception as e:&lt;br /&gt;
                self.search_error(str(e))&lt;br /&gt;
        &lt;br /&gt;
        thread = Thread(target=search_worker, daemon=True)&lt;br /&gt;
        thread.start()&lt;br /&gt;
    &lt;br /&gt;
    def update_progress(self, current, total):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;진행률 업데이트&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if total &amp;gt; 0:&lt;br /&gt;
            percentage = int((current / total) * 100)&lt;br /&gt;
            self.progress_bar.setValue(percentage)&lt;br /&gt;
            self.statusBar().showMessage(f&amp;quot;검색 중... {current}/{total} 파일&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    def search_finished(self, results):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;검색 완료&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        self.current_results = results&lt;br /&gt;
        &lt;br /&gt;
        # UI 업데이트&lt;br /&gt;
        self.search_button.setEnabled(True)&lt;br /&gt;
        self.object_search_button.setEnabled(True)&lt;br /&gt;
        self.progress_bar.setVisible(False)&lt;br /&gt;
        &lt;br /&gt;
        # 결과 표시&lt;br /&gt;
        self.display_results(results)&lt;br /&gt;
        &lt;br /&gt;
        # 상태바&lt;br /&gt;
        self.statusBar().showMessage(f&amp;quot;검색 완료: {len(results)}건 발견&amp;quot;, 5000)&lt;br /&gt;
        &lt;br /&gt;
        # 최근 검색어 업데이트&lt;br /&gt;
        current_text = self.search_input.currentText()&lt;br /&gt;
        if self.search_input.findText(current_text) == -1:&lt;br /&gt;
            self.search_input.addItem(current_text)&lt;br /&gt;
    &lt;br /&gt;
    def search_error(self, error_msg):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;검색 오류&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        self.search_button.setEnabled(True)&lt;br /&gt;
        self.object_search_button.setEnabled(True)&lt;br /&gt;
        self.progress_bar.setVisible(False)&lt;br /&gt;
        &lt;br /&gt;
        QMessageBox.critical(self, &amp;quot;검색 오류&amp;quot;, f&amp;quot;검색 중 오류가 발생했습니다:\n{error_msg}&amp;quot;)&lt;br /&gt;
        self.statusBar().showMessage(&amp;quot;검색 실패&amp;quot;, 5000)&lt;br /&gt;
    &lt;br /&gt;
    def display_results(self, results: List[SearchResult]):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;검색 결과 표시&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        self.result_table.setRowCount(0)&lt;br /&gt;
        &lt;br /&gt;
        for result in results:&lt;br /&gt;
            row = self.result_table.rowCount()&lt;br /&gt;
            self.result_table.insertRow(row)&lt;br /&gt;
            &lt;br /&gt;
            # 파일명 (전체 경로의 마지막 부분만)&lt;br /&gt;
            filename = Path(result.filepath).name&lt;br /&gt;
            self.result_table.setItem(row, 0, QTableWidgetItem(filename))&lt;br /&gt;
            &lt;br /&gt;
            # 라인 번호&lt;br /&gt;
            self.result_table.setItem(row, 1, QTableWidgetItem(str(result.line_number)))&lt;br /&gt;
            &lt;br /&gt;
            # 파일 크기&lt;br /&gt;
            size_kb = result.file_size / 1024&lt;br /&gt;
            size_str = f&amp;quot;{size_kb:.1f} KB&amp;quot; if size_kb &amp;lt; 1024 else f&amp;quot;{size_kb/1024:.1f} MB&amp;quot;&lt;br /&gt;
            self.result_table.setItem(row, 2, QTableWidgetItem(size_str))&lt;br /&gt;
            &lt;br /&gt;
            # 수정일&lt;br /&gt;
            date_str = result.modified_date.strftime(&#039;%Y-%m-%d %H:%M&#039;)&lt;br /&gt;
            self.result_table.setItem(row, 3, QTableWidgetItem(date_str))&lt;br /&gt;
        &lt;br /&gt;
        # 요약 업데이트&lt;br /&gt;
        self.result_summary.setText(f&amp;quot;검색 결과: {len(results)}건&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 첫 번째 결과 선택&lt;br /&gt;
        if results:&lt;br /&gt;
            self.result_table.selectRow(0)&lt;br /&gt;
    &lt;br /&gt;
    def show_result_detail(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;선택된 결과의 상세 내용 표시&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        selected_rows = self.result_table.selectionModel().selectedRows()&lt;br /&gt;
        &lt;br /&gt;
        if not selected_rows or not self.current_results:&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        row = selected_rows[0].row()&lt;br /&gt;
        if row &amp;gt;= len(self.current_results):&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        result = self.current_results[row]&lt;br /&gt;
        &lt;br /&gt;
        # 상세 정보 레이블&lt;br /&gt;
        self.detail_label.setText(&lt;br /&gt;
            f&amp;quot;📄 {result.filepath} (라인 {result.line_number})&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
        &lt;br /&gt;
        # 내용 표시&lt;br /&gt;
        content = []&lt;br /&gt;
        &lt;br /&gt;
        # 컨텍스트 이전&lt;br /&gt;
        for i, line in enumerate(result.context_before):&lt;br /&gt;
            line_num = result.line_number - len(result.context_before) + i&lt;br /&gt;
            content.append(f&amp;quot;{line_num:5d} | {line}&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 매칭된 라인 (강조)&lt;br /&gt;
        content.append(f&amp;quot;{result.line_number:5d} &amp;gt;&amp;gt;&amp;gt; {result.line_content}&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 컨텍스트 이후&lt;br /&gt;
        for i, line in enumerate(result.context_after):&lt;br /&gt;
            line_num = result.line_number + i + 1&lt;br /&gt;
            content.append(f&amp;quot;{line_num:5d} | {line}&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        self.detail_text.setPlainText(&#039;\n&#039;.join(content))&lt;br /&gt;
        &lt;br /&gt;
        # 매칭된 라인으로 스크롤&lt;br /&gt;
        cursor = self.detail_text.textCursor()&lt;br /&gt;
        cursor.movePosition(QTextCursor.Start)&lt;br /&gt;
        for _ in range(len(result.context_before)):&lt;br /&gt;
            cursor.movePosition(QTextCursor.Down)&lt;br /&gt;
        self.detail_text.setTextCursor(cursor)&lt;br /&gt;
    &lt;br /&gt;
    def show_context_menu(self, position):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;컨텍스트 메뉴 표시&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if not self.result_table.selectionModel().selectedRows():&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        menu = QMenu()&lt;br /&gt;
        &lt;br /&gt;
        open_file_action = QAction(&amp;quot;파일 열기&amp;quot;, self)&lt;br /&gt;
        open_file_action.triggered.connect(self.open_selected_file)&lt;br /&gt;
        menu.addAction(open_file_action)&lt;br /&gt;
        &lt;br /&gt;
        copy_path_action = QAction(&amp;quot;경로 복사&amp;quot;, self)&lt;br /&gt;
        copy_path_action.triggered.connect(self.copy_file_path)&lt;br /&gt;
        menu.addAction(copy_path_action)&lt;br /&gt;
        &lt;br /&gt;
        menu.addSeparator()&lt;br /&gt;
        &lt;br /&gt;
        export_action = QAction(&amp;quot;선택 항목 내보내기&amp;quot;, self)&lt;br /&gt;
        export_action.triggered.connect(self.export_selected)&lt;br /&gt;
        menu.addAction(export_action)&lt;br /&gt;
        &lt;br /&gt;
        menu.exec_(self.result_table.viewport().mapToGlobal(position))&lt;br /&gt;
    &lt;br /&gt;
    def open_selected_file(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;선택된 파일 열기&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        selected_rows = self.result_table.selectionModel().selectedRows()&lt;br /&gt;
        if not selected_rows or not self.current_results:&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        row = selected_rows[0].row()&lt;br /&gt;
        result = self.current_results[row]&lt;br /&gt;
        &lt;br /&gt;
        # 기본 프로그램으로 파일 열기&lt;br /&gt;
        import subprocess&lt;br /&gt;
        import platform&lt;br /&gt;
        &lt;br /&gt;
        if platform.system() == &#039;Windows&#039;:&lt;br /&gt;
            os.startfile(result.filepath)&lt;br /&gt;
        elif platform.system() == &#039;Darwin&#039;:  # macOS&lt;br /&gt;
            subprocess.call([&#039;open&#039;, result.filepath])&lt;br /&gt;
        else:  # Linux&lt;br /&gt;
            subprocess.call([&#039;xdg-open&#039;, result.filepath])&lt;br /&gt;
    &lt;br /&gt;
    def copy_file_path(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;파일 경로 복사&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        selected_rows = self.result_table.selectionModel().selectedRows()&lt;br /&gt;
        if not selected_rows or not self.current_results:&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        row = selected_rows[0].row()&lt;br /&gt;
        result = self.current_results[row]&lt;br /&gt;
        &lt;br /&gt;
        clipboard = QApplication.clipboard()&lt;br /&gt;
        clipboard.setText(result.filepath)&lt;br /&gt;
        &lt;br /&gt;
        self.statusBar().showMessage(&amp;quot;경로가 클립보드에 복사되었습니다.&amp;quot;, 3000)&lt;br /&gt;
    &lt;br /&gt;
    def export_results(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;전체 결과 내보내기&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if not self.current_results:&lt;br /&gt;
            QMessageBox.information(self, &amp;quot;내보내기&amp;quot;, &amp;quot;내보낼 결과가 없습니다.&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        # 파일 형식 선택&lt;br /&gt;
        file_filter = &amp;quot;HTML 파일 (*.html);;CSV 파일 (*.csv);;텍스트 파일 (*.txt)&amp;quot;&lt;br /&gt;
        filename, selected_filter = QFileDialog.getSaveFileName(&lt;br /&gt;
            self, &amp;quot;결과 내보내기&amp;quot;, &amp;quot;&amp;quot;, file_filter&lt;br /&gt;
        )&lt;br /&gt;
        &lt;br /&gt;
        if not filename:&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        # 형식 결정&lt;br /&gt;
        if &#039;HTML&#039; in selected_filter:&lt;br /&gt;
            format_type = &#039;html&#039;&lt;br /&gt;
        elif &#039;CSV&#039; in selected_filter:&lt;br /&gt;
            format_type = &#039;csv&#039;&lt;br /&gt;
        else:&lt;br /&gt;
            format_type = &#039;txt&#039;&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            self.searcher.export_results(self.current_results, filename, format_type)&lt;br /&gt;
            QMessageBox.information(self, &amp;quot;내보내기&amp;quot;, f&amp;quot;결과가 {filename}에 저장되었습니다.&amp;quot;)&lt;br /&gt;
            self.statusBar().showMessage(f&amp;quot;파일 저장 완료: {filename}&amp;quot;, 5000)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            QMessageBox.critical(self, &amp;quot;내보내기 오류&amp;quot;, f&amp;quot;파일 저장 중 오류:\n{str(e)}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    def export_selected(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;선택된 항목만 내보내기&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        selected_rows = self.result_table.selectionModel().selectedRows()&lt;br /&gt;
        if not selected_rows:&lt;br /&gt;
            QMessageBox.information(self, &amp;quot;내보내기&amp;quot;, &amp;quot;선택된 항목이 없습니다.&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        selected_results = [self.current_results[row.row()] for row in selected_rows]&lt;br /&gt;
        &lt;br /&gt;
        file_filter = &amp;quot;HTML 파일 (*.html);;CSV 파일 (*.csv);;텍스트 파일 (*.txt)&amp;quot;&lt;br /&gt;
        filename, selected_filter = QFileDialog.getSaveFileName(&lt;br /&gt;
            self, &amp;quot;선택 항목 내보내기&amp;quot;, &amp;quot;&amp;quot;, file_filter&lt;br /&gt;
        )&lt;br /&gt;
        &lt;br /&gt;
        if not filename:&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        if &#039;HTML&#039; in selected_filter:&lt;br /&gt;
            format_type = &#039;html&#039;&lt;br /&gt;
        elif &#039;CSV&#039; in selected_filter:&lt;br /&gt;
            format_type = &#039;csv&#039;&lt;br /&gt;
        else:&lt;br /&gt;
            format_type = &#039;txt&#039;&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            self.searcher.export_results(selected_results, filename, format_type)&lt;br /&gt;
            QMessageBox.information(self, &amp;quot;내보내기&amp;quot;, f&amp;quot;{len(selected_results)}건이 저장되었습니다.&amp;quot;)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            QMessageBox.critical(self, &amp;quot;내보내기 오류&amp;quot;, f&amp;quot;파일 저장 중 오류:\n{str(e)}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    def add_directory(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;검색 디렉토리 추가&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        directory = QFileDialog.getExistingDirectory(self, &amp;quot;디렉토리 선택&amp;quot;)&lt;br /&gt;
        if directory:&lt;br /&gt;
            current_text = self.dir_list.toPlainText()&lt;br /&gt;
            if current_text:&lt;br /&gt;
                self.dir_list.setPlainText(current_text + &#039;\n&#039; + directory)&lt;br /&gt;
            else:&lt;br /&gt;
                self.dir_list.setPlainText(directory)&lt;br /&gt;
    &lt;br /&gt;
    def save_settings(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;설정 저장&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # 디렉토리 목록&lt;br /&gt;
        dirs = [d.strip() for d in self.dir_list.toPlainText().split(&#039;\n&#039;) if d.strip()]&lt;br /&gt;
        self.config.set(dirs, &#039;search_directories&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # 확장자 목록&lt;br /&gt;
        exts = [e.strip() for e in self.ext_list.toPlainText().split(&#039;\n&#039;) if e.strip()]&lt;br /&gt;
        self.config.set(exts, &#039;sql_extensions&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # 성능 설정&lt;br /&gt;
        self.config.set(self.workers_spin.value(), &#039;search_settings&#039;, &#039;max_workers&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # 설정 파일 저장&lt;br /&gt;
        self.config.save_config()&lt;br /&gt;
        &lt;br /&gt;
        # 검색 엔진 재초기화&lt;br /&gt;
        self.searcher = SQLScriptSearcher(self.config)&lt;br /&gt;
        &lt;br /&gt;
        QMessageBox.information(self, &amp;quot;설정&amp;quot;, &amp;quot;설정이 저장되었습니다.&amp;quot;)&lt;br /&gt;
        self.statusBar().showMessage(&amp;quot;설정 저장 완료&amp;quot;, 3000)&lt;br /&gt;
    &lt;br /&gt;
    def load_settings(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;설정 로드&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # 최근 검색어 복원&lt;br /&gt;
        for search_text in self.searcher.recent_searches:&lt;br /&gt;
            self.search_input.addItem(search_text)&lt;br /&gt;
    &lt;br /&gt;
    def refresh_file_list(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;파일 목록 새로고침&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        files = self.searcher.get_all_sql_files()&lt;br /&gt;
        count = len(files)&lt;br /&gt;
        QMessageBox.information(self, &amp;quot;파일 목록&amp;quot;, f&amp;quot;총 {count}개의 SQL 파일을 찾았습니다.&amp;quot;)&lt;br /&gt;
        self.statusBar().showMessage(f&amp;quot;파일 목록 새로고침 완료: {count}개&amp;quot;, 5000)&lt;br /&gt;
    &lt;br /&gt;
    def rebuild_index(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;인덱스 재구성&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if not WHOOSH_AVAILABLE or not self.searcher.indexer:&lt;br /&gt;
            QMessageBox.warning(self, &amp;quot;인덱스&amp;quot;, &amp;quot;Whoosh 인덱싱을 사용할 수 없습니다.&amp;quot;)&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        reply = QMessageBox.question(&lt;br /&gt;
            self, &amp;quot;인덱스 재구성&amp;quot;,&lt;br /&gt;
            &amp;quot;모든 파일을 다시 인덱싱합니다.\n시간이 걸릴 수 있습니다. 계속하시겠습니까?&amp;quot;,&lt;br /&gt;
            QMessageBox.Yes | QMessageBox.No&lt;br /&gt;
        )&lt;br /&gt;
        &lt;br /&gt;
        if reply == QMessageBox.No:&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        self.index_progress.setVisible(True)&lt;br /&gt;
        self.index_progress.setValue(0)&lt;br /&gt;
        &lt;br /&gt;
        # 인덱싱 스레드 시작&lt;br /&gt;
        self.indexing_thread = IndexingThread(self.searcher)&lt;br /&gt;
        self.indexing_thread.progress.connect(self.update_index_progress)&lt;br /&gt;
        self.indexing_thread.finished.connect(self.indexing_finished)&lt;br /&gt;
        self.indexing_thread.error.connect(self.indexing_error)&lt;br /&gt;
        self.indexing_thread.start()&lt;br /&gt;
    &lt;br /&gt;
    def update_index_progress(self, current, total):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;인덱싱 진행률 업데이트&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if total &amp;gt; 0:&lt;br /&gt;
            percentage = int((current / total) * 100)&lt;br /&gt;
            self.index_progress.setValue(percentage)&lt;br /&gt;
            self.statusBar().showMessage(f&amp;quot;인덱싱 중... {current}/{total} 파일&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    def indexing_finished(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;인덱싱 완료&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        self.index_progress.setVisible(False)&lt;br /&gt;
        QMessageBox.information(self, &amp;quot;인덱스&amp;quot;, &amp;quot;인덱싱이 완료되었습니다.&amp;quot;)&lt;br /&gt;
        self.statusBar().showMessage(&amp;quot;인덱싱 완료&amp;quot;, 5000)&lt;br /&gt;
        self.update_index_status()&lt;br /&gt;
    &lt;br /&gt;
    def indexing_error(self, error_msg):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;인덱싱 오류&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        self.index_progress.setVisible(False)&lt;br /&gt;
        QMessageBox.critical(self, &amp;quot;인덱싱 오류&amp;quot;, f&amp;quot;인덱싱 중 오류:\n{error_msg}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    def update_index_status(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;인덱스 상태 업데이트&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        if not WHOOSH_AVAILABLE or not self.searcher.indexer:&lt;br /&gt;
            return&lt;br /&gt;
        &lt;br /&gt;
        try:&lt;br /&gt;
            with self.searcher.indexer.ix.searcher() as searcher:&lt;br /&gt;
                doc_count = searcher.doc_count_all()&lt;br /&gt;
            &lt;br /&gt;
            status_text = f&amp;quot;인덱싱된 문서: {doc_count}개\n&amp;quot;&lt;br /&gt;
            status_text += f&amp;quot;인덱스 디렉토리: {self.searcher.indexer.index_dir}&amp;quot;&lt;br /&gt;
            &lt;br /&gt;
            self.index_status_label.setText(status_text)&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            self.index_status_label.setText(f&amp;quot;상태 확인 오류: {str(e)}&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    def show_about(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;정보 대화상자&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        about_text = &amp;quot;&amp;quot;&amp;quot;&amp;lt;h2&amp;gt;SQL Script Searcher&amp;lt;/h2&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;&amp;lt;b&amp;gt;Professional Edition v1.0&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;p&amp;gt;Oracle DBA를 위한 강력한 SQL 스크립트 검색 도구&amp;lt;/p&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;p&amp;gt;&amp;lt;b&amp;gt;주요 기능:&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;ul&amp;gt;&lt;br /&gt;
            &amp;lt;li&amp;gt;다중 스레드 고속 검색&amp;lt;/li&amp;gt;&lt;br /&gt;
            &amp;lt;li&amp;gt;정규표현식 지원&amp;lt;/li&amp;gt;&lt;br /&gt;
            &amp;lt;li&amp;gt;SQL 객체 특화 검색&amp;lt;/li&amp;gt;&lt;br /&gt;
            &amp;lt;li&amp;gt;Whoosh 전문 검색 엔진&amp;lt;/li&amp;gt;&lt;br /&gt;
            &amp;lt;li&amp;gt;ripgrep 통합 (선택)&amp;lt;/li&amp;gt;&lt;br /&gt;
        &amp;lt;/ul&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;p&amp;gt;&amp;lt;b&amp;gt;기술 스택:&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;ul&amp;gt;&lt;br /&gt;
            &amp;lt;li&amp;gt;Python 3.x&amp;lt;/li&amp;gt;&lt;br /&gt;
            &amp;lt;li&amp;gt;PyQt5&amp;lt;/li&amp;gt;&lt;br /&gt;
            &amp;lt;li&amp;gt;Whoosh&amp;lt;/li&amp;gt;&lt;br /&gt;
            &amp;lt;li&amp;gt;PyYAML&amp;lt;/li&amp;gt;&lt;br /&gt;
        &amp;lt;/ul&amp;gt;&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        &lt;br /&gt;
        QMessageBox.about(self, &amp;quot;SQL Script Searcher&amp;quot;, about_text)&lt;br /&gt;
    &lt;br /&gt;
    def closeEvent(self, event):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;프로그램 종료 시&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # 윈도우 크기 저장&lt;br /&gt;
        self.config.set(self.width(), &#039;ui_settings&#039;, &#039;window_width&#039;)&lt;br /&gt;
        self.config.set(self.height(), &#039;ui_settings&#039;, &#039;window_height&#039;)&lt;br /&gt;
        self.config.save_config()&lt;br /&gt;
        &lt;br /&gt;
        event.accept()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main():&lt;br /&gt;
    app = QApplication(sys.argv)&lt;br /&gt;
    app.setStyle(&#039;Fusion&#039;)  # 모던한 스타일&lt;br /&gt;
    &lt;br /&gt;
    window = SQLSearcherGUI()&lt;br /&gt;
    window.show()&lt;br /&gt;
    &lt;br /&gt;
    sys.exit(app.exec_())&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    main()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 실행 방법===&lt;br /&gt;
* bash# 1. 가상환경 생성 (권장)&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
python -m venv venv&lt;br /&gt;
venv\Scripts\activate  # Windows&lt;br /&gt;
# source venv/bin/activate  # Linux/Mac&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
* 2. 필요한 라이브러리 설치&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
* 3. 프로그램 실행&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
python sql_searcher_gui.py&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%8A%A4%ED%8E%98%EC%9D%B8_%ED%8F%AC%EB%A5%B4%ED%88%AC%EA%B0%88_%EC%97%AC%ED%96%89_%EC%8B%9C_%EA%B8%B0%EB%B3%B8_%EC%96%B8%EC%96%B4&amp;diff=2341</id>
		<title>스페인 포르투갈 여행 시 기본 언어</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%8A%A4%ED%8E%98%EC%9D%B8_%ED%8F%AC%EB%A5%B4%ED%88%AC%EA%B0%88_%EC%97%AC%ED%96%89_%EC%8B%9C_%EA%B8%B0%EB%B3%B8_%EC%96%B8%EC%96%B4&amp;diff=2341"/>
		<updated>2025-12-21T04:31:58Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: 스페인과 포르투갈 여행에 유용한 현지어 표현들을 알려드릴게요!  ## 🇪🇸 스페인어 (바르셀로나)  ### 기본 인사  - **안녕하세요**: Hola (올라) - **좋은 아침**: Buenos días (부에노스 디아스) - **안녕히 가세요**: Adiós (아디오스) - **감사합니다**: Gracias (그라시아스) - **천만에요**: De nada (데 나다) - **죄송합니다**: Lo siento (로 시엔토) - **실례합니다**: Perdón / Disculpe (뻬...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;스페인과 포르투갈 여행에 유용한 현지어 표현들을 알려드릴게요!&lt;br /&gt;
&lt;br /&gt;
## 🇪🇸 스페인어 (바르셀로나)&lt;br /&gt;
&lt;br /&gt;
### 기본 인사&lt;br /&gt;
&lt;br /&gt;
- **안녕하세요**: Hola (올라)&lt;br /&gt;
- **좋은 아침**: Buenos días (부에노스 디아스)&lt;br /&gt;
- **안녕히 가세요**: Adiós (아디오스)&lt;br /&gt;
- **감사합니다**: Gracias (그라시아스)&lt;br /&gt;
- **천만에요**: De nada (데 나다)&lt;br /&gt;
- **죄송합니다**: Lo siento (로 시엔토)&lt;br /&gt;
- **실례합니다**: Perdón / Disculpe (뻬르돈 / 디스꿀뻬)&lt;br /&gt;
&lt;br /&gt;
### 실용 표현&lt;br /&gt;
&lt;br /&gt;
- **네**: Sí (씨)&lt;br /&gt;
- **아니오**: No (노)&lt;br /&gt;
- **영어 하세요?**: ¿Habla inglés? (아블라 잉글레스?)&lt;br /&gt;
- **얼마예요?**: ¿Cuánto cuesta? (꾸안토 꾸에스타?)&lt;br /&gt;
- **화장실 어디예요?**: ¿Dónde está el baño? (돈데 에스타 엘 바뇨?)&lt;br /&gt;
- **계산서 주세요**: La cuenta, por favor (라 꾸엔타, 뽀르 파보르)&lt;br /&gt;
- **물 주세요**: Agua, por favor (아구아, 뽀르 파보르)&lt;br /&gt;
- **맥주 주세요**: Una cerveza, por favor (우나 세르베사, 뽀르 파보르)&lt;br /&gt;
&lt;br /&gt;
### 레스토랑&lt;br /&gt;
&lt;br /&gt;
- **메뉴판 주세요**: El menú, por favor (엘 메누, 뽀르 파보르)&lt;br /&gt;
- **이거 주세요**: Esto, por favor (에스토, 뽀르 파보르)&lt;br /&gt;
- **맛있어요**: Está rico / Delicioso (에스타 리코 / 델리시오소)&lt;br /&gt;
- **카드 되나요?**: ¿Aceptan tarjeta? (아셉탄 타르헤타?)&lt;br /&gt;
&lt;br /&gt;
### 숫자 (1-10)&lt;br /&gt;
&lt;br /&gt;
- 1: uno (우노), 2: dos (도스), 3: tres (트레스)&lt;br /&gt;
- 4: cuatro (꾸아트로), 5: cinco (싱코)&lt;br /&gt;
- 10: diez (디에스)&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
## 🇵🇹 포르투갈어 (포르투)&lt;br /&gt;
&lt;br /&gt;
### 기본 인사&lt;br /&gt;
&lt;br /&gt;
- **안녕하세요**: Olá (올라)&lt;br /&gt;
- **좋은 아침**: Bom dia (봉 디아)&lt;br /&gt;
- **좋은 저녁**: Boa tarde (보아 따르드)&lt;br /&gt;
- **안녕히 가세요**: Adeus (아데우스) / Tchau (차우 - 친근함)&lt;br /&gt;
- **감사합니다**: Obrigado/a (오브리가두/다) *남성/여성&lt;br /&gt;
- **천만에요**: De nada (드 나다)&lt;br /&gt;
- **죄송합니다**: Desculpe (데스꿀프)&lt;br /&gt;
&lt;br /&gt;
### 실용 표현&lt;br /&gt;
&lt;br /&gt;
- **네**: Sim (씽)&lt;br /&gt;
- **아니오**: Não (나웅)&lt;br /&gt;
- **영어 하세요?**: Fala inglês? (팔라 잉글레스?)&lt;br /&gt;
- **얼마예요?**: Quanto custa? (꾸안투 꾸스타?)&lt;br /&gt;
- **화장실 어디예요?**: Onde é a casa de banho? (온드 에 아 까자 드 바뉴?)&lt;br /&gt;
- **계산서 주세요**: A conta, por favor (아 꼰따, 뽀르 파보르)&lt;br /&gt;
- **물 주세요**: Água, por favor (아구아, 뽀르 파보르)&lt;br /&gt;
- **맥주 주세요**: Uma cerveja, por favor (우마 세르베자, 뽀르 파보르)&lt;br /&gt;
&lt;br /&gt;
### 레스토랑&lt;br /&gt;
&lt;br /&gt;
- **메뉴판 주세요**: O menu, por favor (우 메누, 뽀르 파보르)&lt;br /&gt;
- **이거 주세요**: Isto, por favor (이슈투, 뽀르 파보르)&lt;br /&gt;
- **맛있어요**: Está delicioso (슈타 델리시오주)&lt;br /&gt;
- **맛있게 드세요** (상대방이 말함): Bom apetite (봉 아프티트)&lt;br /&gt;
&lt;br /&gt;
### 특별 표현&lt;br /&gt;
&lt;br /&gt;
- **포트 와인 주세요**: Vinho do Porto, por favor (비뉴 두 포르투, 뽀르 파보르)&lt;br /&gt;
- **에그타르트 (파스텔 드 나타)**: Um pastel de nata (웅 파슈텔 드 나따)&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
## 💡 팁&lt;br /&gt;
&lt;br /&gt;
**바르셀로나 특별 사항:**&lt;br /&gt;
&lt;br /&gt;
- 바르셀로나는 카탈루냐 지역이라 카탈루냐어도 사용됩니다&lt;br /&gt;
- 감사합니다 (카탈루냐어): **Gràcies** (그라시에스)&lt;br /&gt;
- 안녕하세요 (카탈루냐어): **Bon dia** (본 디아)&lt;br /&gt;
- 하지만 스페인어로 말해도 전혀 문제없어요!&lt;br /&gt;
&lt;br /&gt;
**발음 팁:**&lt;br /&gt;
&lt;br /&gt;
- 스페인어 “j”는 “ㅎ” 소리 (예: viejo = 비에호)&lt;br /&gt;
- 포르투갈어는 비음(코소리)이 많아요 (ão, õe 등)&lt;br /&gt;
- 두 언어 모두 “r”은 굴리는 소리&lt;br /&gt;
&lt;br /&gt;
**실전 활용:**&lt;br /&gt;
&lt;br /&gt;
- “Por favor”(please)를 붙이면 공손해요&lt;br /&gt;
- 미소와 함께 말하면 다 통합니다! 😊&lt;br /&gt;
- 영어가 잘 안 통할 때 현지어 한마디면 분위기가 좋아져요&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%95%B4%EC%99%B8%EC%97%AC%ED%96%89_%EC%98%81%EC%96%B4_%EC%9D%B4%EA%B1%B0%EB%A7%8C_%ED%95%98%EB%A9%B4_%EB%81%9D&amp;diff=2340</id>
		<title>해외여행 영어 이거만 하면 끝</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%95%B4%EC%99%B8%EC%97%AC%ED%96%89_%EC%98%81%EC%96%B4_%EC%9D%B4%EA%B1%B0%EB%A7%8C_%ED%95%98%EB%A9%B4_%EB%81%9D&amp;diff=2340"/>
		<updated>2025-12-21T04:26:59Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: == 해외여행 영어 표현 상황별 정리 ==  === 레스토랑/카페=== # ”자리 있나요?“ #:“Do you have a table available?” / “Is there a table for two?” #:예상 답변: “Yes, right this way.” / “I’m sorry, there’s a 20-minute wait.” #:추가 질문: “How long is the wait?” (얼마나 기다려야 하나요?) # “여기 뭐가 맛있어요?”  - 영어: “What’s good here?” / “What do you recommend?” - 예상 답변: “Our spec...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 해외여행 영어 표현 상황별 정리 ==&lt;br /&gt;
&lt;br /&gt;
=== 레스토랑/카페===&lt;br /&gt;
# ”자리 있나요?“&lt;br /&gt;
#:“Do you have a table available?” / “Is there a table for two?”&lt;br /&gt;
#:예상 답변: “Yes, right this way.” / “I’m sorry, there’s a 20-minute wait.”&lt;br /&gt;
#:추가 질문: “How long is the wait?” (얼마나 기다려야 하나요?)&lt;br /&gt;
# “여기 뭐가 맛있어요?”&lt;br /&gt;
&lt;br /&gt;
- 영어: “What’s good here?” / “What do you recommend?”&lt;br /&gt;
- 예상 답변: “Our specialty is the seafood pasta.” / “The steak is very popular.”&lt;br /&gt;
- 추가 질문: “Is it spicy?” / “Does it contain nuts?” (견과류 들어가나요?)&lt;br /&gt;
&lt;br /&gt;
**“추천 좀 해주세요.”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Can you recommend something?” / “What’s your recommendation?”&lt;br /&gt;
- 예상 답변: “I’d suggest the chef’s special.”&lt;br /&gt;
- 추가 질문: “What’s in it?” (뭐가 들어가나요?)&lt;br /&gt;
&lt;br /&gt;
**“주문해도 될까요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Can I order now?” / “Are you ready to take my order?”&lt;br /&gt;
- 예상 답변: “Yes, what would you like?” / “Just a moment, please.”&lt;br /&gt;
&lt;br /&gt;
**“뭐 드릴까요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: (상대방이 묻는 질문) “What can I get you?” / “Are you ready to order?”&lt;br /&gt;
- 답변 예시: “I’ll have the burger, please.”&lt;br /&gt;
&lt;br /&gt;
**“치즈버거랑 콜라 주세요.”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Can I have a cheeseburger and a Coke, please?” / “I’ll take a cheeseburger and a Coke.”&lt;br /&gt;
- 예상 답변: “Sure, anything else?” / “For here or to go?”&lt;br /&gt;
- 추가 질문: “Can I get that without pickles?” (피클 빼주세요)&lt;br /&gt;
&lt;br /&gt;
**“물티슈 있어요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Do you have wet wipes?” / “Can I get some wet towels?”&lt;br /&gt;
- 예상 답변: “Sure, just a moment.” / “They’re on the table.”&lt;br /&gt;
&lt;br /&gt;
**“냅킨 좀 주시겠어요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Could I get some napkins, please?” / “May I have more napkins?”&lt;br /&gt;
- 예상 답변: “Of course, here you go.”&lt;br /&gt;
&lt;br /&gt;
**“앞접시 주실 수 있나요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Could we get some extra plates?” / “Can I have a side plate?”&lt;br /&gt;
- 예상 답변: “Sure, I’ll bring them right away.”&lt;br /&gt;
&lt;br /&gt;
**“계산 좀 부탁드려요.”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Can I get the check, please?” / “Could we have the bill?”&lt;br /&gt;
- 예상 답변: “I’ll bring it right over.” / “Will that be together or separate?”&lt;br /&gt;
- 추가 질문: “Do you accept credit cards?” / “Is service included?” (봉사료 포함인가요?)&lt;br /&gt;
&lt;br /&gt;
**“덕분에 좋은 시간이었어요.”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “We had a great time, thank you!” / “Everything was wonderful, thanks!”&lt;br /&gt;
- 예상 답변: “Thank you! Please come again.”&lt;br /&gt;
&lt;br /&gt;
## 🗺️ 관광/길찾기&lt;br /&gt;
&lt;br /&gt;
**“어디를 꼭 가봐야 할까요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “What are the must-see places?” / “Where should I definitely visit?”&lt;br /&gt;
- 예상 답변: “You should definitely see the cathedral and the old town.”&lt;br /&gt;
- 추가 질문: “How far is it from here?” / “Is it walking distance?”&lt;br /&gt;
&lt;br /&gt;
**“언제 가는 게 제일 좋아요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “When’s the best time to go?” / “What time do you recommend visiting?”&lt;br /&gt;
- 예상 답변: “Early morning is best to avoid crowds.” / “Sunset is beautiful there.”&lt;br /&gt;
- 추가 질문: “What time does it open/close?”&lt;br /&gt;
&lt;br /&gt;
**“거기 어떻게 가야 하죠?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “How do I get there?” / “What’s the best way to get there?”&lt;br /&gt;
- 예상 답변: “Take the metro line 3 to Plaza España.” / “It’s about a 15-minute walk.”&lt;br /&gt;
- 추가 질문: “Can I walk there?” / “Which bus should I take?”&lt;br /&gt;
&lt;br /&gt;
**“택시비는 얼마인가요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “How much is a taxi to…?” / “What’s the taxi fare to the airport?”&lt;br /&gt;
- 예상 답변: “It should be around 20-25 euros.”&lt;br /&gt;
- 추가 질문: “Is Uber available here?” / “Can you call a taxi for me?”&lt;br /&gt;
&lt;br /&gt;
**“이 버스 여기 가나요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Does this bus go to…?” / “Is this the right bus for…?”&lt;br /&gt;
- 예상 답변: “Yes, it does.” / “No, you need the number 24.”&lt;br /&gt;
- 추가 질문: “How many stops is it?” / “Where should I get off?”&lt;br /&gt;
&lt;br /&gt;
## 🛍️ 쇼핑&lt;br /&gt;
&lt;br /&gt;
**“이거 입어봐도 되나요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Can I try this on?” / “May I try this?”&lt;br /&gt;
- 예상 답변: “Sure, the fitting room is over there.”&lt;br /&gt;
- 추가 질문: “Where’s the fitting room?” (탈의실 어디인가요?)&lt;br /&gt;
&lt;br /&gt;
**“사이즈가 안 맞아요.”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “This doesn’t fit.” / “It’s too tight/loose.”&lt;br /&gt;
- 예상 답변: “Would you like to try a different size?”&lt;br /&gt;
&lt;br /&gt;
**“더 작은/큰 사이즈 있나요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Do you have this in a smaller/larger size?” / “Do you have a size small/large?”&lt;br /&gt;
- 예상 답변: “Let me check for you.” / “I’m sorry, that’s our last one.”&lt;br /&gt;
- 추가 질문: “Do you have this in a different color?”&lt;br /&gt;
&lt;br /&gt;
**“환불 받을 수 있나요?”**&lt;br /&gt;
&lt;br /&gt;
- 영어: “Can I get a refund?” / “What’s your return policy?”&lt;br /&gt;
- 예상 답변: “Do you have the receipt?” / “You can exchange it within 14 days.”&lt;br /&gt;
- 추가 질문: “Can I exchange this instead?” (교환 가능한가요?)&lt;br /&gt;
&lt;br /&gt;
## 💡 추가 유용한 표현&lt;br /&gt;
&lt;br /&gt;
- “Could you speak more slowly?” (천천히 말씀해주시겠어요?)&lt;br /&gt;
- “I don’t understand.” (이해 못했어요)&lt;br /&gt;
- “Can you write it down?” (적어주시겠어요?)&lt;br /&gt;
- “Where’s the restroom?” (화장실 어디인가요?)&lt;br /&gt;
- “Is wifi available?” (와이파이 되나요?)&lt;br /&gt;
- “Do you speak Korean?” (한국어 하시나요? - 급할 때)&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%8C%94%EB%9E%80%ED%8B%B0%EC%96%B4_%EC%98%A8%ED%86%A8%EB%A1%9C%EC%A7%80_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8_%EC%98%88%EC%A0%9C&amp;diff=2339</id>
		<title>팔란티어 온톨로지 프로그램 예제</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%8C%94%EB%9E%80%ED%8B%B0%EC%96%B4_%EC%98%A8%ED%86%A8%EB%A1%9C%EC%A7%80_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8_%EC%98%88%EC%A0%9C&amp;diff=2339"/>
		<updated>2025-12-15T05:53:39Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: == Palantir Ontology 기반 CRUD 자동 생성 시스템 설계 ==  === Ontology 개념 적용 개요 ===  Palantir의 Ontology는 데이터를 **Objects(객체)**, **Properties(속성)**, **Links(관계)**로 모델링하여 비즈니스 도메인을 표현합니다. 이를 적용하여 단순 테이블 기반 CRUD를 넘어 **의미론적 데이터 관리 시스템**으로 진화시킵니다.  ### 핵심 개선 사항  - **Object Types**: 테이블을 비즈니스 객체로...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Palantir Ontology 기반 CRUD 자동 생성 시스템 설계 ==&lt;br /&gt;
&lt;br /&gt;
=== Ontology 개념 적용 개요 ===&lt;br /&gt;
&lt;br /&gt;
Palantir의 Ontology는 데이터를 **Objects(객체)**, **Properties(속성)**, **Links(관계)**로 모델링하여 비즈니스 도메인을 표현합니다. 이를 적용하여 단순 테이블 기반 CRUD를 넘어 **의미론적 데이터 관리 시스템**으로 진화시킵니다.&lt;br /&gt;
&lt;br /&gt;
### 핵심 개선 사항&lt;br /&gt;
&lt;br /&gt;
- **Object Types**: 테이블을 비즈니스 객체로 추상화&lt;br /&gt;
- **Property Types**: 컬럼을 타입화된 속성으로 정의&lt;br /&gt;
- **Link Types**: 테이블 간 관계를 명시적 링크로 표현&lt;br /&gt;
- **Actions**: CRUD를 넘어 비즈니스 액션 정의&lt;br /&gt;
- **Pipelines**: 데이터 변환 및 통합 파이프라인&lt;br /&gt;
&lt;br /&gt;
=== 개선된 기술 스택 ===&lt;br /&gt;
&lt;br /&gt;
### 백엔드&lt;br /&gt;
&lt;br /&gt;
- **언어**: Python 3.10+&lt;br /&gt;
- **웹 프레임워크**: FastAPI 0.104 (비동기 처리, 자동 API 문서)&lt;br /&gt;
- **데이터베이스**: SQLite (기본), PostgreSQL, Oracle&lt;br /&gt;
- **Graph DB**: Neo4j 5.x (관계 저장 및 탐색)&lt;br /&gt;
- **ORM**: SQLAlchemy 2.0 + Neomodel (Graph ORM)&lt;br /&gt;
- **검색 엔진**: Elasticsearch 8.x (온톨로지 검색)&lt;br /&gt;
&lt;br /&gt;
### 프론트엔드&lt;br /&gt;
&lt;br /&gt;
- **프레임워크**: React 18 + TypeScript&lt;br /&gt;
- **UI 라이브러리**: shadcn/ui, Material-UI&lt;br /&gt;
- **그래프 시각화**: D3.js, Cytoscape.js&lt;br /&gt;
- **상태 관리**: Zustand&lt;br /&gt;
- **폼 관리**: React Hook Form + Zod&lt;br /&gt;
&lt;br /&gt;
=== 필요한 Python 패키지 ===&lt;br /&gt;
&lt;br /&gt;
```txt&lt;br /&gt;
# 웹 프레임워크&lt;br /&gt;
fastapi==0.104.1&lt;br /&gt;
uvicorn[standard]==0.24.0&lt;br /&gt;
pydantic==2.5.0&lt;br /&gt;
pydantic-settings==2.1.0&lt;br /&gt;
&lt;br /&gt;
# 데이터베이스&lt;br /&gt;
sqlalchemy==2.0.23&lt;br /&gt;
alembic==1.12.1&lt;br /&gt;
oracledb==2.0.0&lt;br /&gt;
psycopg2-binary==2.9.9&lt;br /&gt;
&lt;br /&gt;
# Graph Database&lt;br /&gt;
neo4j==5.14.0&lt;br /&gt;
neomodel==5.2.1&lt;br /&gt;
&lt;br /&gt;
# 검색&lt;br /&gt;
elasticsearch==8.11.0&lt;br /&gt;
&lt;br /&gt;
# 데이터 처리&lt;br /&gt;
pandas==2.1.3&lt;br /&gt;
networkx==3.2.1&lt;br /&gt;
&lt;br /&gt;
# 파일 처리&lt;br /&gt;
python-multipart==0.0.6&lt;br /&gt;
aiofiles==23.2.1&lt;br /&gt;
Pillow==10.1.0&lt;br /&gt;
&lt;br /&gt;
# 유틸리티&lt;br /&gt;
python-dotenv==1.0.0&lt;br /&gt;
PyYAML==6.0.1&lt;br /&gt;
pydantic-core==2.14.5&lt;br /&gt;
&lt;br /&gt;
# API 문서&lt;br /&gt;
fastapi-users==12.1.2&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
=== 개선된 프로젝트 구조 ===&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
ontology_crud_system/&lt;br /&gt;
│&lt;br /&gt;
├── main.py                         # FastAPI 애플리케이션 진입점&lt;br /&gt;
├── config.py                       # 설정 관리&lt;br /&gt;
├── requirements.txt&lt;br /&gt;
├── .env&lt;br /&gt;
├── README.md&lt;br /&gt;
│&lt;br /&gt;
├── alembic/                        # DB 마이그레이션&lt;br /&gt;
│   └── versions/&lt;br /&gt;
│&lt;br /&gt;
├── app/&lt;br /&gt;
│   ├── __init__.py&lt;br /&gt;
│   │&lt;br /&gt;
│   ├── ontology/                   # 온톨로지 핵심 모듈&lt;br /&gt;
│   │   ├── __init__.py&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── models/                 # 온톨로지 모델&lt;br /&gt;
│   │   │   ├── __init__.py&lt;br /&gt;
│   │   │   ├── object_type.py      # 객체 타입 정의&lt;br /&gt;
│   │   │   ├── property_type.py    # 속성 타입 정의&lt;br /&gt;
│   │   │   ├── link_type.py        # 링크 타입 정의&lt;br /&gt;
│   │   │   ├── action_type.py      # 액션 타입 정의&lt;br /&gt;
│   │   │   └── pipeline.py         # 데이터 파이프라인&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── registry/               # 온톨로지 레지스트리&lt;br /&gt;
│   │   │   ├── __init__.py&lt;br /&gt;
│   │   │   ├── object_registry.py  # 객체 타입 등록/관리&lt;br /&gt;
│   │   │   ├── property_registry.py&lt;br /&gt;
│   │   │   ├── link_registry.py&lt;br /&gt;
│   │   │   └── action_registry.py&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── engine/                 # 온톨로지 엔진&lt;br /&gt;
│   │   │   ├── __init__.py&lt;br /&gt;
│   │   │   ├── inference_engine.py # 추론 엔진&lt;br /&gt;
│   │   │   ├── validation_engine.py # 검증 엔진&lt;br /&gt;
│   │   │   ├── query_engine.py     # 온톨로지 쿼리&lt;br /&gt;
│   │   │   └── graph_engine.py     # 그래프 탐색&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   └── schema/                 # 온톨로지 스키마&lt;br /&gt;
│   │       ├── __init__.py&lt;br /&gt;
│   │       ├── schema_builder.py   # 스키마 빌더&lt;br /&gt;
│   │       ├── schema_validator.py # 스키마 검증&lt;br /&gt;
│   │       └── schema_evolution.py # 스키마 버전 관리&lt;br /&gt;
│   │&lt;br /&gt;
│   ├── models/                     # 데이터 모델&lt;br /&gt;
│   │   ├── __init__.py&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── relational/             # 관계형 DB 모델&lt;br /&gt;
│   │   │   ├── __init__.py&lt;br /&gt;
│   │   │   ├── base.py&lt;br /&gt;
│   │   │   ├── ontology_metadata.py # 온톨로지 메타데이터&lt;br /&gt;
│   │   │   ├── object_instance.py   # 객체 인스턴스&lt;br /&gt;
│   │   │   ├── property_value.py    # 속성 값&lt;br /&gt;
│   │   │   └── attachment.py&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   └── graph/                  # 그래프 DB 모델&lt;br /&gt;
│   │       ├── __init__.py&lt;br /&gt;
│   │       ├── object_node.py      # 객체 노드&lt;br /&gt;
│   │       ├── link_relationship.py # 링크 관계&lt;br /&gt;
│   │       └── property_edge.py    # 속성 엣지&lt;br /&gt;
│   │&lt;br /&gt;
│   ├── core/                       # 핵심 비즈니스 로직&lt;br /&gt;
│   │   ├── __init__.py&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── discovery/              # 자동 발견&lt;br /&gt;
│   │   │   ├── __init__.py&lt;br /&gt;
│   │   │   ├── schema_scanner.py   # 스키마 스캔&lt;br /&gt;
│   │   │   ├── relationship_detector.py # 관계 감지&lt;br /&gt;
│   │   │   └── ontology_mapper.py  # 온톨로지 매핑&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── generator/              # 코드 생성&lt;br /&gt;
│   │   │   ├── __init__.py&lt;br /&gt;
│   │   │   ├── object_crud_gen.py  # 객체 CRUD 생성&lt;br /&gt;
│   │   │   ├── link_crud_gen.py    # 링크 CRUD 생성&lt;br /&gt;
│   │   │   ├── action_gen.py       # 액션 생성&lt;br /&gt;
│   │   │   └── ui_component_gen.py # UI 컴포넌트 생성&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── transformation/         # 데이터 변환&lt;br /&gt;
│   │   │   ├── __init__.py&lt;br /&gt;
│   │   │   ├── pipeline_executor.py&lt;br /&gt;
│   │   │   ├── etl_engine.py&lt;br /&gt;
│   │   │   └── data_mapper.py&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   └── intelligence/           # 지능형 기능&lt;br /&gt;
│   │       ├── __init__.py&lt;br /&gt;
│   │       ├── auto_linker.py      # 자동 링크 생성&lt;br /&gt;
│   │       ├── property_suggester.py # 속성 제안&lt;br /&gt;
│   │       └── anomaly_detector.py # 이상 탐지&lt;br /&gt;
│   │&lt;br /&gt;
│   ├── api/                        # API 엔드포인트&lt;br /&gt;
│   │   ├── __init__.py&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── v1/&lt;br /&gt;
│   │   │   ├── __init__.py&lt;br /&gt;
│   │   │   ├── objects.py          # 객체 API&lt;br /&gt;
│   │   │   ├── properties.py       # 속성 API&lt;br /&gt;
│   │   │   ├── links.py            # 링크 API&lt;br /&gt;
│   │   │   ├── actions.py          # 액션 API&lt;br /&gt;
│   │   │   ├── search.py           # 검색 API&lt;br /&gt;
│   │   │   ├── graph.py            # 그래프 탐색 API&lt;br /&gt;
│   │   │   └── ontology.py         # 온톨로지 관리 API&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   └── websocket/              # WebSocket&lt;br /&gt;
│   │       ├── __init__.py&lt;br /&gt;
│   │       └── realtime_updates.py # 실시간 업데이트&lt;br /&gt;
│   │&lt;br /&gt;
│   ├── services/                   # 비즈니스 서비스&lt;br /&gt;
│   │   ├── __init__.py&lt;br /&gt;
│   │   ├── object_service.py       # 객체 관리&lt;br /&gt;
│   │   ├── link_service.py         # 링크 관리&lt;br /&gt;
│   │   ├── action_service.py       # 액션 실행&lt;br /&gt;
│   │   ├── search_service.py       # 검색 서비스&lt;br /&gt;
│   │   ├── graph_service.py        # 그래프 서비스&lt;br /&gt;
│   │   └── pipeline_service.py     # 파이프라인 서비스&lt;br /&gt;
│   │&lt;br /&gt;
│   ├── repositories/               # 데이터 접근 계층&lt;br /&gt;
│   │   ├── __init__.py&lt;br /&gt;
│   │   ├── relational_repo.py      # 관계형 DB 리포지토리&lt;br /&gt;
│   │   ├── graph_repo.py           # 그래프 DB 리포지토리&lt;br /&gt;
│   │   └── search_repo.py          # 검색 리포지토리&lt;br /&gt;
│   │&lt;br /&gt;
│   ├── schemas/                    # Pydantic 스키마&lt;br /&gt;
│   │   ├── __init__.py&lt;br /&gt;
│   │   ├── object_schema.py&lt;br /&gt;
│   │   ├── property_schema.py&lt;br /&gt;
│   │   ├── link_schema.py&lt;br /&gt;
│   │   ├── action_schema.py&lt;br /&gt;
│   │   └── response_schema.py&lt;br /&gt;
│   │&lt;br /&gt;
│   └── utils/                      # 유틸리티&lt;br /&gt;
│       ├── __init__.py&lt;br /&gt;
│       ├── validators.py&lt;br /&gt;
│       ├── type_converters.py&lt;br /&gt;
│       ├── graph_utils.py&lt;br /&gt;
│       └── logger.py&lt;br /&gt;
│&lt;br /&gt;
├── frontend/                       # React 프론트엔드&lt;br /&gt;
│   ├── package.json&lt;br /&gt;
│   ├── tsconfig.json&lt;br /&gt;
│   │&lt;br /&gt;
│   ├── src/&lt;br /&gt;
│   │   ├── App.tsx&lt;br /&gt;
│   │   ├── main.tsx&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── features/               # 기능별 모듈&lt;br /&gt;
│   │   │   ├── ontology/&lt;br /&gt;
│   │   │   │   ├── components/     # 온톨로지 관리 컴포넌트&lt;br /&gt;
│   │   │   │   │   ├── ObjectTypeEditor.tsx&lt;br /&gt;
│   │   │   │   │   ├── PropertyTypeEditor.tsx&lt;br /&gt;
│   │   │   │   │   ├── LinkTypeEditor.tsx&lt;br /&gt;
│   │   │   │   │   └── OntologyViewer.tsx&lt;br /&gt;
│   │   │   │   └── hooks/&lt;br /&gt;
│   │   │   │       └── useOntology.ts&lt;br /&gt;
│   │   │   │&lt;br /&gt;
│   │   │   ├── objects/            # 객체 관리&lt;br /&gt;
│   │   │   │   ├── components/&lt;br /&gt;
│   │   │   │   │   ├── ObjectList.tsx&lt;br /&gt;
│   │   │   │   │   ├── ObjectDetail.tsx&lt;br /&gt;
│   │   │   │   │   ├── ObjectForm.tsx&lt;br /&gt;
│   │   │   │   │   └── ObjectGraph.tsx&lt;br /&gt;
│   │   │   │   └── hooks/&lt;br /&gt;
│   │   │   │       └── useObjects.ts&lt;br /&gt;
│   │   │   │&lt;br /&gt;
│   │   │   ├── links/              # 링크 관리&lt;br /&gt;
│   │   │   │   ├── components/&lt;br /&gt;
│   │   │   │   │   ├── LinkExplorer.tsx&lt;br /&gt;
│   │   │   │   │   ├── LinkCreator.tsx&lt;br /&gt;
│   │   │   │   │   └── NetworkGraph.tsx&lt;br /&gt;
│   │   │   │   └── hooks/&lt;br /&gt;
│   │   │   │       └── useLinks.ts&lt;br /&gt;
│   │   │   │&lt;br /&gt;
│   │   │   ├── actions/            # 액션 실행&lt;br /&gt;
│   │   │   │   ├── components/&lt;br /&gt;
│   │   │   │   │   ├── ActionPanel.tsx&lt;br /&gt;
│   │   │   │   │   ├── ActionForm.tsx&lt;br /&gt;
│   │   │   │   │   └── ActionHistory.tsx&lt;br /&gt;
│   │   │   │   └── hooks/&lt;br /&gt;
│   │   │   │       └── useActions.ts&lt;br /&gt;
│   │   │   │&lt;br /&gt;
│   │   │   └── search/             # 검색&lt;br /&gt;
│   │   │       ├── components/&lt;br /&gt;
│   │   │       │   ├── OntologySearch.tsx&lt;br /&gt;
│   │   │       │   ├── SearchFilters.tsx&lt;br /&gt;
│   │   │       │   └── SearchResults.tsx&lt;br /&gt;
│   │   │       └── hooks/&lt;br /&gt;
│   │   │           └── useSearch.ts&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── components/             # 공통 컴포넌트&lt;br /&gt;
│   │   │   ├── ui/                 # shadcn/ui 컴포넌트&lt;br /&gt;
│   │   │   ├── layout/&lt;br /&gt;
│   │   │   │   ├── Header.tsx&lt;br /&gt;
│   │   │   │   ├── Sidebar.tsx&lt;br /&gt;
│   │   │   │   ├── Navigation.tsx&lt;br /&gt;
│   │   │   │   └── Footer.tsx&lt;br /&gt;
│   │   │   ├── graph/&lt;br /&gt;
│   │   │   │   ├── GraphVisualization.tsx&lt;br /&gt;
│   │   │   │   └── GraphControls.tsx&lt;br /&gt;
│   │   │   └── forms/&lt;br /&gt;
│   │   │       ├── DynamicForm.tsx&lt;br /&gt;
│   │   │       └── FileUpload.tsx&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── stores/                 # Zustand 스토어&lt;br /&gt;
│   │   │   ├── ontologyStore.ts&lt;br /&gt;
│   │   │   ├── objectStore.ts&lt;br /&gt;
│   │   │   ├── linkStore.ts&lt;br /&gt;
│   │   │   └── themeStore.ts&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── services/               # API 서비스&lt;br /&gt;
│   │   │   ├── api.ts&lt;br /&gt;
│   │   │   ├── ontologyApi.ts&lt;br /&gt;
│   │   │   ├── objectApi.ts&lt;br /&gt;
│   │   │   └── linkApi.ts&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   ├── types/                  # TypeScript 타입&lt;br /&gt;
│   │   │   ├── ontology.ts&lt;br /&gt;
│   │   │   ├── object.ts&lt;br /&gt;
│   │   │   └── link.ts&lt;br /&gt;
│   │   │&lt;br /&gt;
│   │   └── utils/&lt;br /&gt;
│   │       ├── graphUtils.ts&lt;br /&gt;
│   │       └── validators.ts&lt;br /&gt;
│   │&lt;br /&gt;
│   └── public/&lt;br /&gt;
│&lt;br /&gt;
└── tests/&lt;br /&gt;
    ├── unit/&lt;br /&gt;
    ├── integration/&lt;br /&gt;
    └── e2e/&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
=== 온톨로지 데이터 모델 ===&lt;br /&gt;
&lt;br /&gt;
==== Object Types (객체 타입) ====&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
ontology_object_types&lt;br /&gt;
- id: UUID PRIMARY KEY&lt;br /&gt;
- type_name: VARCHAR(100) UNIQUE (예: Employee, Department, Project)&lt;br /&gt;
- display_name: VARCHAR(200)&lt;br /&gt;
- description: TEXT&lt;br /&gt;
- icon: VARCHAR(50)&lt;br /&gt;
- color: VARCHAR(20)&lt;br /&gt;
- base_table: VARCHAR(100) (원본 테이블명)&lt;br /&gt;
- is_abstract: BOOLEAN (추상 타입 여부)&lt;br /&gt;
- parent_type_id: UUID FK (상속 관계)&lt;br /&gt;
- metadata: JSONB (추가 메타데이터)&lt;br /&gt;
- created_at: TIMESTAMP&lt;br /&gt;
- updated_at: TIMESTAMP&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
==== Property Types (속성 타입) ====&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
ontology_property_types&lt;br /&gt;
- id: UUID PRIMARY KEY&lt;br /&gt;
- property_name: VARCHAR(100)&lt;br /&gt;
- display_name: VARCHAR(200)&lt;br /&gt;
- description: TEXT&lt;br /&gt;
- data_type: VARCHAR(50) (String, Number, Date, Boolean, File, Reference)&lt;br /&gt;
- base_type: VARCHAR(50) (원본 SQL 타입)&lt;br /&gt;
- object_type_id: UUID FK&lt;br /&gt;
- is_required: BOOLEAN&lt;br /&gt;
- is_unique: BOOLEAN&lt;br /&gt;
- is_indexed: BOOLEAN&lt;br /&gt;
- default_value: TEXT&lt;br /&gt;
- validation_rules: JSONB&lt;br /&gt;
  {&lt;br /&gt;
    &amp;quot;min_length&amp;quot;: 5,&lt;br /&gt;
    &amp;quot;max_length&amp;quot;: 100,&lt;br /&gt;
    &amp;quot;pattern&amp;quot;: &amp;quot;^[A-Z].*&amp;quot;,&lt;br /&gt;
    &amp;quot;min_value&amp;quot;: 0,&lt;br /&gt;
    &amp;quot;max_value&amp;quot;: 100,&lt;br /&gt;
    &amp;quot;allowed_values&amp;quot;: [&amp;quot;A&amp;quot;, &amp;quot;B&amp;quot;, &amp;quot;C&amp;quot;]&lt;br /&gt;
  }&lt;br /&gt;
- format_options: JSONB&lt;br /&gt;
  {&lt;br /&gt;
    &amp;quot;date_format&amp;quot;: &amp;quot;YYYY-MM-DD&amp;quot;,&lt;br /&gt;
    &amp;quot;number_format&amp;quot;: &amp;quot;#,##0.00&amp;quot;,&lt;br /&gt;
    &amp;quot;currency&amp;quot;: &amp;quot;KRW&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
- ui_config: JSONB&lt;br /&gt;
  {&lt;br /&gt;
    &amp;quot;input_type&amp;quot;: &amp;quot;text/number/date/select/file&amp;quot;,&lt;br /&gt;
    &amp;quot;placeholder&amp;quot;: &amp;quot;Enter value...&amp;quot;,&lt;br /&gt;
    &amp;quot;help_text&amp;quot;: &amp;quot;Description&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
- created_at: TIMESTAMP&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
==== Link Types (링크 타입) ====&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
ontology_link_types&lt;br /&gt;
- id: UUID PRIMARY KEY&lt;br /&gt;
- link_name: VARCHAR(100) (예: WorksFor, BelongsTo, Manages)&lt;br /&gt;
- display_name: VARCHAR(200)&lt;br /&gt;
- description: TEXT&lt;br /&gt;
- source_object_type_id: UUID FK&lt;br /&gt;
- target_object_type_id: UUID FK&lt;br /&gt;
- cardinality: VARCHAR(20) (ONE_TO_ONE, ONE_TO_MANY, MANY_TO_MANY)&lt;br /&gt;
- is_bidirectional: BOOLEAN&lt;br /&gt;
- reverse_link_name: VARCHAR(100)&lt;br /&gt;
- base_constraint: VARCHAR(200) (FK 제약조건명)&lt;br /&gt;
- link_properties: JSONB (링크 자체의 속성)&lt;br /&gt;
  {&lt;br /&gt;
    &amp;quot;start_date&amp;quot;: &amp;quot;date&amp;quot;,&lt;br /&gt;
    &amp;quot;end_date&amp;quot;: &amp;quot;date&amp;quot;,&lt;br /&gt;
    &amp;quot;role&amp;quot;: &amp;quot;string&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
- created_at: TIMESTAMP&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
====Link Instances (링크 인스턴스)====&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
ontology_link_instances&lt;br /&gt;
- id: UUID PRIMARY KEY&lt;br /&gt;
- link_type_id: UUID FK&lt;br /&gt;
- source_object_id: UUID FK&lt;br /&gt;
- target_object_id: UUID FK&lt;br /&gt;
- properties: JSONB (링크 속성 값)&lt;br /&gt;
- created_at: TIMESTAMP&lt;br /&gt;
- created_by: VARCHAR(100)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
==== Action Types (액션 타입) ====&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
ontology_action_types&lt;br /&gt;
- id: UUID PRIMARY KEY&lt;br /&gt;
- action_name: VARCHAR(100) (예: Approve, Reject, Transfer)&lt;br /&gt;
- display_name: VARCHAR(200)&lt;br /&gt;
- description: TEXT&lt;br /&gt;
- object_type_id: UUID FK (적용 대상 객체)&lt;br /&gt;
- action_category: VARCHAR(50) (CREATE, UPDATE, DELETE, CUSTOM)&lt;br /&gt;
- icon: VARCHAR(50)&lt;br /&gt;
- color: VARCHAR(20)&lt;br /&gt;
- parameters: JSONB&lt;br /&gt;
  [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;name&amp;quot;: &amp;quot;approval_comment&amp;quot;,&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
      &amp;quot;required&amp;quot;: true&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
- preconditions: JSONB (실행 전 조건)&lt;br /&gt;
  {&lt;br /&gt;
    &amp;quot;status&amp;quot;: [&amp;quot;PENDING&amp;quot;, &amp;quot;REVIEW&amp;quot;],&lt;br /&gt;
    &amp;quot;user_role&amp;quot;: [&amp;quot;MANAGER&amp;quot;, &amp;quot;ADMIN&amp;quot;]&lt;br /&gt;
  }&lt;br /&gt;
- effects: JSONB (실행 후 효과)&lt;br /&gt;
  {&lt;br /&gt;
    &amp;quot;update_properties&amp;quot;: {&amp;quot;status&amp;quot;: &amp;quot;APPROVED&amp;quot;},&lt;br /&gt;
    &amp;quot;create_link&amp;quot;: {&amp;quot;link_type&amp;quot;: &amp;quot;ApprovedBy&amp;quot;},&lt;br /&gt;
    &amp;quot;send_notification&amp;quot;: true&lt;br /&gt;
  }&lt;br /&gt;
- implementation: TEXT (Python 코드 또는 SQL)&lt;br /&gt;
- is_active: BOOLEAN&lt;br /&gt;
- created_at: TIMESTAMP&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
==== Pipelines (데이터 파이프라인) ====&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
ontology_pipelines&lt;br /&gt;
- id: UUID PRIMARY KEY&lt;br /&gt;
- pipeline_name: VARCHAR(100)&lt;br /&gt;
- description: TEXT&lt;br /&gt;
- source_config: JSONB&lt;br /&gt;
  {&lt;br /&gt;
    &amp;quot;type&amp;quot;: &amp;quot;database/api/file&amp;quot;,&lt;br /&gt;
    &amp;quot;connection&amp;quot;: &amp;quot;...&amp;quot;,&lt;br /&gt;
    &amp;quot;query&amp;quot;: &amp;quot;...&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
- transformations: JSONB&lt;br /&gt;
  [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;map&amp;quot;,&lt;br /&gt;
      &amp;quot;source_field&amp;quot;: &amp;quot;emp_name&amp;quot;,&lt;br /&gt;
      &amp;quot;target_property&amp;quot;: &amp;quot;full_name&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;type&amp;quot;: &amp;quot;filter&amp;quot;,&lt;br /&gt;
      &amp;quot;condition&amp;quot;: &amp;quot;salary &amp;gt; 50000&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
- target_object_type_id: UUID FK&lt;br /&gt;
- schedule: VARCHAR(100) (cron 표현식)&lt;br /&gt;
- is_active: BOOLEAN&lt;br /&gt;
- last_run_at: TIMESTAMP&lt;br /&gt;
- created_at: TIMESTAMP&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
==== Object Instances (객체 인스턴스) ====&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
ontology_object_instances&lt;br /&gt;
- id: UUID PRIMARY KEY&lt;br /&gt;
- object_type_id: UUID FK&lt;br /&gt;
- display_title: VARCHAR(500) (동적 생성)&lt;br /&gt;
- properties: JSONB (모든 속성 값)&lt;br /&gt;
  {&lt;br /&gt;
    &amp;quot;employee_name&amp;quot;: &amp;quot;홍길동&amp;quot;,&lt;br /&gt;
    &amp;quot;employee_number&amp;quot;: &amp;quot;E12345&amp;quot;,&lt;br /&gt;
    &amp;quot;hire_date&amp;quot;: &amp;quot;2024-01-15&amp;quot;,&lt;br /&gt;
    &amp;quot;salary&amp;quot;: 50000000&lt;br /&gt;
  }&lt;br /&gt;
- base_record_id: VARCHAR(100) (원본 테이블 PK)&lt;br /&gt;
- tags: TEXT[] (검색용 태그)&lt;br /&gt;
- version: INTEGER (버전 관리)&lt;br /&gt;
- created_at: TIMESTAMP&lt;br /&gt;
- updated_at: TIMESTAMP&lt;br /&gt;
- created_by: VARCHAR(100)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
=== 주요 모듈 기능 설명 ===&lt;br /&gt;
&lt;br /&gt;
==== ontology/engine/inference_engine.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 온톨로지 기반 추론&lt;br /&gt;
&lt;br /&gt;
- 객체 타입 상속 관계에서 속성 자동 상속&lt;br /&gt;
- 링크 타입 체인을 통한 간접 관계 추론&lt;br /&gt;
- 속성 값 기반 자동 분류 (예: 급여 범위로 직급 추론)&lt;br /&gt;
- 누락된 링크 자동 제안&lt;br /&gt;
- 제약조건 충돌 탐지&lt;br /&gt;
&lt;br /&gt;
==== ontology/engine/validation_engine.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 온톨로지 규칙 검증&lt;br /&gt;
&lt;br /&gt;
- 속성 타입 검증 (데이터 타입, 범위, 패턴)&lt;br /&gt;
- 필수 속성 검증&lt;br /&gt;
- 링크 카디널리티 검증 (ONE_TO_MANY 등)&lt;br /&gt;
- 순환 참조 탐지&lt;br /&gt;
- 비즈니스 규칙 검증 (사용자 정의 규칙)&lt;br /&gt;
&lt;br /&gt;
==== core/discovery/relationship_detector.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 테이블 간 관계 자동 감지&lt;br /&gt;
&lt;br /&gt;
- FK 제약조건 분석하여 링크 타입 자동 생성&lt;br /&gt;
- 컬럼명 패턴 분석 (예: dept_id → Department 링크)&lt;br /&gt;
- 데이터 분포 분석으로 숨겨진 관계 발견&lt;br /&gt;
- M:N 관계 중간 테이블 자동 인식&lt;br /&gt;
- 추천 신뢰도 점수 제공&lt;br /&gt;
&lt;br /&gt;
==== core/generator/object_crud_gen.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 객체 기반 CRUD 생성&lt;br /&gt;
&lt;br /&gt;
- Object Type 기반 동적 API 엔드포인트 생성&lt;br /&gt;
- Property Type에 따른 입력 폼 자동 생성&lt;br /&gt;
- 유효성 검사 로직 자동 생성&lt;br /&gt;
- 파일 업로드 속성 자동 처리&lt;br /&gt;
- 버전 관리 기능 포함&lt;br /&gt;
&lt;br /&gt;
===== core/generator/link_crud_gen.py =====&lt;br /&gt;
&lt;br /&gt;
**기능**: 링크 기반 CRUD 생성&lt;br /&gt;
&lt;br /&gt;
- 링크 생성/삭제 API 생성&lt;br /&gt;
- 양방향 링크 자동 처리&lt;br /&gt;
- 링크 탐색 UI 생성 (그래프 뷰)&lt;br /&gt;
- 링크 속성 편집 기능&lt;br /&gt;
- 연쇄 링크 생성 (여러 객체 동시 연결)&lt;br /&gt;
&lt;br /&gt;
==== core/generator/action_gen.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 액션 코드 자동 생성&lt;br /&gt;
&lt;br /&gt;
- Action Type 정의에서 Python 함수 생성&lt;br /&gt;
- Preconditions를 데코레이터로 변환&lt;br /&gt;
- Effects를 트랜잭션으로 실행&lt;br /&gt;
- 액션 히스토리 자동 기록&lt;br /&gt;
- 롤백 메커니즘 포함&lt;br /&gt;
&lt;br /&gt;
==== core/intelligence/auto_linker.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 지능형 자동 링크 생성&lt;br /&gt;
&lt;br /&gt;
- 객체 속성 유사도 분석&lt;br /&gt;
- 텍스트 매칭 (이름, 코드 등)&lt;br /&gt;
- 기존 링크 패턴 학습&lt;br /&gt;
- 자동 링크 제안 및 확률 점수&lt;br /&gt;
- 배치 링크 생성&lt;br /&gt;
&lt;br /&gt;
==== services/graph_service.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 그래프 탐색 서비스&lt;br /&gt;
&lt;br /&gt;
- BFS/DFS 그래프 탐색&lt;br /&gt;
- 최단 경로 찾기&lt;br /&gt;
- N-hop 관계 조회&lt;br /&gt;
- 서브그래프 추출&lt;br /&gt;
- 영향도 분석 (한 객체 변경 시 연관 객체)&lt;br /&gt;
&lt;br /&gt;
==== services/search_service.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 온톨로지 기반 검색&lt;br /&gt;
&lt;br /&gt;
- 전문 검색 (Elasticsearch)&lt;br /&gt;
- 속성 기반 필터링&lt;br /&gt;
- 링크 기반 검색 (연결된 객체 찾기)&lt;br /&gt;
- 패싯 검색 (속성별 그룹핑)&lt;br /&gt;
- 자연어 쿼리 지원&lt;br /&gt;
&lt;br /&gt;
==== api/v1/ontology.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 온톨로지 관리 API&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
POST   /api/v1/ontology/object-types          # 객체 타입 생성&lt;br /&gt;
GET    /api/v1/ontology/object-types          # 전체 온톨로지 조회&lt;br /&gt;
PUT    /api/v1/ontology/object-types/{id}     # 객체 타입 수정&lt;br /&gt;
DELETE /api/v1/ontology/object-types/{id}     # 객체 타입 삭제&lt;br /&gt;
&lt;br /&gt;
POST   /api/v1/ontology/property-types        # 속성 타입 생성&lt;br /&gt;
POST   /api/v1/ontology/link-types            # 링크 타입 생성&lt;br /&gt;
POST   /api/v1/ontology/action-types          # 액션 타입 생성&lt;br /&gt;
&lt;br /&gt;
GET    /api/v1/ontology/schema                # 전체 스키마 내보내기&lt;br /&gt;
POST   /api/v1/ontology/schema/import         # 스키마 가져오기&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
==== api/v1/objects.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 객체 인스턴스 API&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
GET    /api/v1/objects/{type}                 # 객체 목록 조회&lt;br /&gt;
POST   /api/v1/objects/{type}                 # 객체 생성&lt;br /&gt;
GET    /api/v1/objects/{type}/{id}            # 객체 상세 조회&lt;br /&gt;
PUT    /api/v1/objects/{type}/{id}            # 객체 수정&lt;br /&gt;
DELETE /api/v1/objects/{type}/{id}            # 객체 삭제&lt;br /&gt;
&lt;br /&gt;
GET    /api/v1/objects/{type}/{id}/links      # 연결된 링크 조회&lt;br /&gt;
GET    /api/v1/objects/{type}/{id}/history    # 변경 이력&lt;br /&gt;
POST   /api/v1/objects/{type}/{id}/actions/{action} # 액션 실행&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
==== api/v1/graph.py ====&lt;br /&gt;
&lt;br /&gt;
**기능**: 그래프 탐색 API&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
GET    /api/v1/graph/explore/{object_id}      # 객체 중심 그래프 탐색&lt;br /&gt;
GET    /api/v1/graph/path/{from}/{to}         # 두 객체 간 경로&lt;br /&gt;
GET    /api/v1/graph/neighbors/{object_id}    # 인접 객체&lt;br /&gt;
POST   /api/v1/graph/subgraph                 # 서브그래프 추출&lt;br /&gt;
GET    /api/v1/graph/analytics/centrality     # 중심성 분석&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 7. 프론트엔드 주요 컴포넌트&lt;br /&gt;
&lt;br /&gt;
### 7.1 OntologyViewer.tsx&lt;br /&gt;
&lt;br /&gt;
**기능**: 온톨로지 전체 시각화&lt;br /&gt;
&lt;br /&gt;
- 객체 타입을 노드로 표시&lt;br /&gt;
- 링크 타입을 엣지로 연결&lt;br /&gt;
- 드래그앤드롭으로 레이아웃 조정&lt;br /&gt;
- 줌/팬 기능&lt;br /&gt;
- 타입 클릭 시 상세 정보 표시&lt;br /&gt;
&lt;br /&gt;
### 7.2 ObjectGraph.tsx&lt;br /&gt;
&lt;br /&gt;
**기능**: 객체 관계 그래프&lt;br /&gt;
&lt;br /&gt;
- 선택한 객체와 연결된 객체들 시각화&lt;br /&gt;
- N-hop 깊이 조절&lt;br /&gt;
- 링크 타입별 색상 구분&lt;br /&gt;
- 노드 클릭 시 상세 페이지 이동&lt;br /&gt;
- 그래프 내 검색&lt;br /&gt;
&lt;br /&gt;
### 7.3 DynamicForm.tsx&lt;br /&gt;
&lt;br /&gt;
**기능**: 동적 폼 생성&lt;br /&gt;
&lt;br /&gt;
- Property Type 정의에서 자동 폼 생성&lt;br /&gt;
- 타입별 입력 위젯 (text, number, date, select, file)&lt;br /&gt;
- 실시간 유효성 검사&lt;br /&gt;
- 조건부 필드 표시&lt;br /&gt;
- 파일 업로드 통합&lt;br /&gt;
&lt;br /&gt;
### 7.4 ActionPanel.tsx&lt;br /&gt;
&lt;br /&gt;
**기능**: 액션 실행 패널&lt;br /&gt;
&lt;br /&gt;
- 객체에 적용 가능한 액션 목록&lt;br /&gt;
- 액션 파라미터 입력 폼&lt;br /&gt;
- Precondition 검증 결과 표시&lt;br /&gt;
- 액션 실행 진행 상태&lt;br /&gt;
- 실행 결과 및 에러 표시&lt;br /&gt;
&lt;br /&gt;
### 7.5 OntologySearch.tsx&lt;br /&gt;
&lt;br /&gt;
**기능**: 온톨로지 기반 검색&lt;br /&gt;
&lt;br /&gt;
- 객체 타입 선택&lt;br /&gt;
- 속성 기반 필터&lt;br /&gt;
- 링크 기반 필터 (예: “Manager가 ‘홍길동’인 Employee”)&lt;br /&gt;
- 검색 결과 그리드/그래프 토글&lt;br /&gt;
- 저장된 검색 쿼리&lt;br /&gt;
&lt;br /&gt;
## 8. 워크플로우&lt;br /&gt;
&lt;br /&gt;
### 8.1 온톨로지 자동 구축 프로세스&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 기존 SQLite/Oracle 테이블 스캔&lt;br /&gt;
   ↓&lt;br /&gt;
2. schema_scanner가 테이블/컬럼 메타데이터 추출&lt;br /&gt;
   ↓&lt;br /&gt;
3. relationship_detector가 FK 및 패턴으로 관계 감지&lt;br /&gt;
   ↓&lt;br /&gt;
4. ontology_mapper가 자동 매핑&lt;br /&gt;
   - 테이블 → Object Type&lt;br /&gt;
   - 컬럼 → Property Type&lt;br /&gt;
   - FK → Link Type&lt;br /&gt;
   ↓&lt;br /&gt;
5. 사용자 검토 및 수정&lt;br /&gt;
   - 타입명 변경&lt;br /&gt;
   - 추가 링크 정의&lt;br /&gt;
   - 비즈니스 규칙 추가&lt;br /&gt;
   ↓&lt;br /&gt;
6. Object Registry에 등록&lt;br /&gt;
   ↓&lt;br /&gt;
7. CRUD 및 UI 자동 생성&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 8.2 객체 생성 및 링크 연결&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 사용자가 객체 생성 폼 작성&lt;br /&gt;
   ↓&lt;br /&gt;
2. validation_engine이 검증&lt;br /&gt;
   - 속성 타입 검증&lt;br /&gt;
   - 필수 속성 확인&lt;br /&gt;
   - 비즈니스 규칙 검증&lt;br /&gt;
   ↓&lt;br /&gt;
3. 객체 인스턴스 저장&lt;br /&gt;
   - ontology_object_instances 테이블&lt;br /&gt;
   - 원본 테이블 (선택)&lt;br /&gt;
   - Elasticsearch 색인&lt;br /&gt;
   ↓&lt;br /&gt;
4. auto_linker가 추천 링크 생성&lt;br /&gt;
   - 유사 객체 찾기&lt;br /&gt;
   - 기존 패턴 학습&lt;br /&gt;
   ↓&lt;br /&gt;
5. 사용자가 링크 확인/추가&lt;br /&gt;
   ↓&lt;br /&gt;
6. 링크 인스턴스 저장&lt;br /&gt;
   - ontology_link_instances 테이블&lt;br /&gt;
   - Neo4j 그래프 DB&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 8.3 액션 실행 프로세스&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
1. 사용자가 객체에서 액션 선택&lt;br /&gt;
   ↓&lt;br /&gt;
2. Precondition 검증&lt;br /&gt;
   - 객체 상태 확인&lt;br /&gt;
   - 사용자 권한 확인&lt;br /&gt;
   ↓&lt;br /&gt;
3. 액션 파라미터 입력&lt;br /&gt;
   ↓&lt;br /&gt;
4. 액션 실행&lt;br /&gt;
   - 트랜잭션 시작&lt;br /&gt;
   - 속성 업데이트&lt;br /&gt;
   - 링크 생성/삭제&lt;br /&gt;
   - 알림 발송&lt;br /&gt;
   ↓&lt;br /&gt;
5. Effects 적용&lt;br /&gt;
   ↓&lt;br /&gt;
6. 액션 히스토리 기록&lt;br /&gt;
   ↓&lt;br /&gt;
7. 실시간 업데이트 (WebSocket)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 8.4 그래프 탐색 시나리오&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
예: &amp;quot;홍길동과 관련된 모든 프로젝트 찾기&amp;quot;&lt;br /&gt;
&lt;br /&gt;
1. OntologySearch에서 &amp;quot;Employee&amp;quot; 타입 선택&lt;br /&gt;
   ↓&lt;br /&gt;
2. 속성 필터: name = &amp;quot;홍길동&amp;quot;&lt;br /&gt;
   ↓&lt;br /&gt;
3. 링크 필터: &amp;quot;WorksOn&amp;quot; → &amp;quot;Project&amp;quot;&lt;br /&gt;
   ↓&lt;br /&gt;
4. graph_service.find_linked_objects() 호출&lt;br /&gt;
   ↓&lt;br /&gt;
5. Neo4j 쿼리 실행:&lt;br /&gt;
   MATCH (e:Employee {name: &#039;홍길동&#039;})-[:WorksOn]-&amp;gt;(p:Project)&lt;br /&gt;
   RETURN p&lt;br /&gt;
   ↓&lt;br /&gt;
6. 결과를 NetworkGraph로 시각화&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 9. 환경 설정&lt;br /&gt;
&lt;br /&gt;
### 9.1 .env&lt;br /&gt;
&lt;br /&gt;
```env&lt;br /&gt;
# FastAPI 설정&lt;br /&gt;
APP_NAME=Ontology CRUD System&lt;br /&gt;
APP_VERSION=1.0.0&lt;br /&gt;
DEBUG=True&lt;br /&gt;
SECRET_KEY=your-secret-key&lt;br /&gt;
&lt;br /&gt;
# SQLite 설정&lt;br /&gt;
DATABASE_TYPE=sqlite&lt;br /&gt;
SQLITE_DB_PATH=./instance/app.db&lt;br /&gt;
&lt;br /&gt;
# PostgreSQL 설정&lt;br /&gt;
POSTGRES_HOST=localhost&lt;br /&gt;
POSTGRES_PORT=5432&lt;br /&gt;
POSTGRES_DB=ontology_db&lt;br /&gt;
POSTGRES_USER=postgres&lt;br /&gt;
POSTGRES_PASSWORD=password&lt;br /&gt;
&lt;br /&gt;
# Oracle 설정&lt;br /&gt;
ORACLE_USER=your_user&lt;br /&gt;
ORACLE_PASSWORD=your_password&lt;br /&gt;
ORACLE_DSN=localhost:1521/ORCL&lt;br /&gt;
&lt;br /&gt;
# Neo4j 설정&lt;br /&gt;
NEO4J_URI=bolt://localhost:7687&lt;br /&gt;
NEO4J_USER=neo4j&lt;br /&gt;
NEO4J_PASSWORD=password&lt;br /&gt;
&lt;br /&gt;
# Elasticsearch 설정&lt;br /&gt;
ES_HOST=localhost&lt;br /&gt;
ES_PORT=9200&lt;br /&gt;
&lt;br /&gt;
# 파일 업로드&lt;br /&gt;
MAX_FILE_SIZE=10485760&lt;br /&gt;
UPLOAD_DIR=./uploads&lt;br /&gt;
&lt;br /&gt;
# UI 설정&lt;br /&gt;
DEFAULT_THEME=shadcn&lt;br /&gt;
ENABLE_DARK_MODE=True&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 9.2 config.py&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
# 환경별 설정 클래스&lt;br /&gt;
# DB 연결 풀 설정&lt;br /&gt;
# 캐시 설정&lt;br /&gt;
# 로깅 설정&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 10. 핵심 개선 사항 요약&lt;br /&gt;
&lt;br /&gt;
### 10.1 단순 테이블 CRUD → 온톨로지 기반 객체 관리&lt;br /&gt;
&lt;br /&gt;
- **Before**: 테이블별 독립적 CRUD&lt;br /&gt;
- **After**: 객체 타입 중심의 통합 관리, 상속 및 다형성 지원&lt;br /&gt;
&lt;br /&gt;
### 10.2 암묵적 관계 → 명시적 링크 타입&lt;br /&gt;
&lt;br /&gt;
- **Before**: FK만으로 관계 표현&lt;br /&gt;
- **After**: 의미있는 링크 타입 (WorksFor, Manages 등), 링크 속성 지원&lt;br /&gt;
&lt;br /&gt;
### 10.3 정적 화면 → 동적 생성&lt;br /&gt;
&lt;br /&gt;
- **Before**: 테이블당 고정 CRUD 화면&lt;br /&gt;
- **After**: 온톨로지 메타데이터 기반 실시간 UI 생성&lt;br /&gt;
&lt;br /&gt;
### 10.4 단순 검색 → 지능형 검색&lt;br /&gt;
&lt;br /&gt;
- **Before**: 컬럼 값 기반 검색&lt;br /&gt;
- **After**: 관계 탐색, 그래프 쿼리, 추론 기반 검색&lt;br /&gt;
&lt;br /&gt;
### 10.5 수동 관계 설정 → 자동 발견&lt;br /&gt;
&lt;br /&gt;
- **Before**: 개발자가 수동으로 관계 정의&lt;br /&gt;
- **After**: AI 기반 자동 관계 감지 및 링크 제안&lt;br /&gt;
&lt;br /&gt;
### 10.6 CRUD만 → 비즈니스 액션&lt;br /&gt;
&lt;br /&gt;
- **Before**: 기본 CRUD만 지원&lt;br /&gt;
- **After**: 도메인 특화 액션 (승인, 이관, 병합 등) 정의 및 실행&lt;br /&gt;
&lt;br /&gt;
### 10.7 단일 DB → 하이브리드 아키텍처&lt;br /&gt;
&lt;br /&gt;
- **Before**: 관계형 DB만 사용&lt;br /&gt;
- **After**: 관계형 DB + 그래프 DB + 검색 엔진 조합&lt;br /&gt;
&lt;br /&gt;
이 설계는 Palantir Foundry의 핵심 개념인 **유연한 데이터 모델링**, **관계 중심 사고**, **지능형 자동화**를 적용하여 단순 CRUD를 넘어 엔터프라이즈급 데이터 관리 플랫폼으로 발전시킵니다.​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%98%A8%ED%86%A8%EB%A1%9C%EC%A7%80_%EC%99%80_%EC%98%88%EC%8B%9C&amp;diff=2338</id>
		<title>온톨로지 와 예시</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%98%A8%ED%86%A8%EB%A1%9C%EC%A7%80_%EC%99%80_%EC%98%88%EC%8B%9C&amp;diff=2338"/>
		<updated>2025-12-12T06:52:07Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서:  온톨로지(Ontology)를 쉽게 설명해드리겠습니다.  ## 온톨로지란?  **“어떤 분야의 지식을 체계적으로 정리한 개념 구조”**입니다. 쉽게 말해, 사물과 개념들 사이의 관계를 명확하게 정의한 일종의 “지식 지도”라고 보시면 됩니다.  ## 실생활 예시: 병원 온톨로지  ``` [사람]   ├─ [환자]   │   ├─ 입원환자   │   └─ 외래환자   │   └─ [의료진]       ├─ [의...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
온톨로지(Ontology)를 쉽게 설명해드리겠습니다.&lt;br /&gt;
&lt;br /&gt;
## 온톨로지란?&lt;br /&gt;
&lt;br /&gt;
**“어떤 분야의 지식을 체계적으로 정리한 개념 구조”**입니다. 쉽게 말해, 사물과 개념들 사이의 관계를 명확하게 정의한 일종의 “지식 지도”라고 보시면 됩니다.&lt;br /&gt;
&lt;br /&gt;
## 실생활 예시: 병원 온톨로지&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
[사람]&lt;br /&gt;
  ├─ [환자]&lt;br /&gt;
  │   ├─ 입원환자&lt;br /&gt;
  │   └─ 외래환자&lt;br /&gt;
  │&lt;br /&gt;
  └─ [의료진]&lt;br /&gt;
      ├─ [의사]&lt;br /&gt;
      │   ├─ 전문의&lt;br /&gt;
      │   └─ 레지던트&lt;br /&gt;
      └─ [간호사]&lt;br /&gt;
          ├─ 수간호사&lt;br /&gt;
          └─ 일반간호사&lt;br /&gt;
&lt;br /&gt;
[관계 정의]&lt;br /&gt;
- 의사 &amp;quot;진료한다&amp;quot; 환자&lt;br /&gt;
- 간호사 &amp;quot;돌본다&amp;quot; 환자&lt;br /&gt;
- 환자 &amp;quot;가지고있다&amp;quot; 질병&lt;br /&gt;
- 의사 &amp;quot;처방한다&amp;quot; 약물&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## Oracle DBA 업무로 보는 예시&lt;br /&gt;
&lt;br /&gt;
데이터베이스 온톨로지를 만든다면:&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
[데이터베이스 객체]&lt;br /&gt;
  ├─ [테이블]&lt;br /&gt;
  │   ├─ 파티션 테이블&lt;br /&gt;
  │   └─ 일반 테이블&lt;br /&gt;
  │&lt;br /&gt;
  ├─ [인덱스]&lt;br /&gt;
  │   ├─ B-Tree 인덱스&lt;br /&gt;
  │   ├─ Bitmap 인덱스&lt;br /&gt;
  │   └─ Function-Based 인덱스&lt;br /&gt;
  │&lt;br /&gt;
  └─ [세그먼트]&lt;br /&gt;
&lt;br /&gt;
[관계]&lt;br /&gt;
- 테이블 &amp;quot;포함한다&amp;quot; 컬럼&lt;br /&gt;
- 인덱스 &amp;quot;참조한다&amp;quot; 테이블&lt;br /&gt;
- 테이블스페이스 &amp;quot;저장한다&amp;quot; 세그먼트&lt;br /&gt;
- DBA &amp;quot;관리한다&amp;quot; 데이터베이스&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 온톨로지 vs 일반 분류의 차이&lt;br /&gt;
&lt;br /&gt;
**일반 폴더 구조** (단순 분류):&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
/동물&lt;br /&gt;
  /고양이&lt;br /&gt;
  /개&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**온톨로지** (관계까지 정의):&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
동물&lt;br /&gt;
  - 포유류&lt;br /&gt;
    - 고양이 [특징: 육식, 4개 다리, 털 있음]&lt;br /&gt;
      관계: &amp;quot;잡아먹는다&amp;quot; → 쥐&lt;br /&gt;
      관계: &amp;quot;기른다&amp;quot; ← 사람&lt;br /&gt;
    - 개 [특징: 잡식, 4개 다리, 털 있음]&lt;br /&gt;
      관계: &amp;quot;사냥한다&amp;quot; → 작은동물&lt;br /&gt;
      관계: &amp;quot;기른다&amp;quot; ← 사람&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 실제 활용 예시&lt;br /&gt;
&lt;br /&gt;
**1. 검색 엔진**&lt;br /&gt;
&lt;br /&gt;
- 사용자가 “감기약” 검색 → 온톨로지를 통해 “해열제”, “진통제”, “종합감기약”도 함께 제안&lt;br /&gt;
&lt;br /&gt;
**2. 의료 시스템**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
환자 증상: &amp;quot;기침, 열&amp;quot;&lt;br /&gt;
온톨로지 추론: &lt;br /&gt;
  → 호흡기 질환 가능성&lt;br /&gt;
  → 관련 검사: 흉부 X-ray, 혈액검사&lt;br /&gt;
  → 처방 가능 약물: 해열제, 진해거담제&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**3. 쇼핑몰 추천**&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
고객이 &amp;quot;노트북&amp;quot; 구매&lt;br /&gt;
온톨로지 관계:&lt;br /&gt;
  → &amp;quot;필요하다&amp;quot; 노트북 가방&lt;br /&gt;
  → &amp;quot;호환된다&amp;quot; 무선 마우스&lt;br /&gt;
  → &amp;quot;함께 쓰인다&amp;quot; 모니터&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## Python으로 간단한 온톨로지 표현&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
# 간단한 온톨로지 구조&lt;br /&gt;
ontology = {&lt;br /&gt;
    &amp;quot;테이블&amp;quot;: {&lt;br /&gt;
        &amp;quot;부모&amp;quot;: &amp;quot;데이터베이스객체&amp;quot;,&lt;br /&gt;
        &amp;quot;속성&amp;quot;: [&amp;quot;컬럼&amp;quot;, &amp;quot;제약조건&amp;quot;, &amp;quot;트리거&amp;quot;],&lt;br /&gt;
        &amp;quot;관계&amp;quot;: {&lt;br /&gt;
            &amp;quot;포함한다&amp;quot;: [&amp;quot;컬럼&amp;quot;, &amp;quot;인덱스&amp;quot;],&lt;br /&gt;
            &amp;quot;저장된다&amp;quot;: &amp;quot;테이블스페이스&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;인덱스&amp;quot;: {&lt;br /&gt;
        &amp;quot;부모&amp;quot;: &amp;quot;데이터베이스객체&amp;quot;,&lt;br /&gt;
        &amp;quot;관계&amp;quot;: {&lt;br /&gt;
            &amp;quot;참조한다&amp;quot;: &amp;quot;테이블&amp;quot;,&lt;br /&gt;
            &amp;quot;개선한다&amp;quot;: &amp;quot;쿼리성능&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**핵심 포인트**: 온톨로지는 단순히 “분류”만 하는 게 아니라 **“개념들 간의 관계와 규칙”**까지 명확히 정의한다는 점이 중요합니다. 이를 통해 컴퓨터가 지식을 이해하고 추론할 수 있게 됩니다.​​​​​​​​​​​​​​​​&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%8C%8C%EC%9D%B4%EC%8D%AC_%EA%B0%80%EC%83%81%ED%99%98%EA%B2%BD_%EB%8B%A4%EB%A5%B8_%EB%B2%84%EC%A0%84_%EC%82%AC%EC%9A%A9_pyenv&amp;diff=2337</id>
		<title>파이썬 가상환경 다른 버전 사용 pyenv</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%8C%8C%EC%9D%B4%EC%8D%AC_%EA%B0%80%EC%83%81%ED%99%98%EA%B2%BD_%EB%8B%A4%EB%A5%B8_%EB%B2%84%EC%A0%84_%EC%82%AC%EC%9A%A9_pyenv&amp;diff=2337"/>
		<updated>2025-11-16T09:27:10Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: === macOS에서 다른 버전의 Python을 가상환경으로 추가하는 방법 ===  ## 방법 1: pyenv 사용 (가장 추천)  ### 1-1. pyenv 설치  ```bash # Homebrew로 pyenv 설치 brew install pyenv  # pyenv-virtualenv도 함께 설치 brew install pyenv-virtualenv ```  ### 1-2. 쉘 설정 추가  ```bash # zsh 사용하는 경우 (~/.zshrc) echo &amp;#039;export PYENV_ROOT=&amp;quot;$HOME/.pyenv&amp;quot;&amp;#039; &amp;gt;&amp;gt; ~/.zshrc echo &amp;#039;export PATH=&amp;quot;$PYENV_ROOT/bin:$PATH&amp;quot;&amp;#039; &amp;gt;&amp;gt; ~/.zshrc echo &amp;#039;eval &amp;quot;$(pyenv init...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== macOS에서 다른 버전의 Python을 가상환경으로 추가하는 방법 ===&lt;br /&gt;
&lt;br /&gt;
## 방법 1: pyenv 사용 (가장 추천)&lt;br /&gt;
&lt;br /&gt;
### 1-1. pyenv 설치&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# Homebrew로 pyenv 설치&lt;br /&gt;
brew install pyenv&lt;br /&gt;
&lt;br /&gt;
# pyenv-virtualenv도 함께 설치&lt;br /&gt;
brew install pyenv-virtualenv&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 1-2. 쉘 설정 추가&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# zsh 사용하는 경우 (~/.zshrc)&lt;br /&gt;
echo &#039;export PYENV_ROOT=&amp;quot;$HOME/.pyenv&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
echo &#039;export PATH=&amp;quot;$PYENV_ROOT/bin:$PATH&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
echo &#039;eval &amp;quot;$(pyenv init --path)&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
echo &#039;eval &amp;quot;$(pyenv init -)&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
echo &#039;eval &amp;quot;$(pyenv virtualenv-init -)&amp;quot;&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
&lt;br /&gt;
# 설정 적용&lt;br /&gt;
source ~/.zshrc&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 1-3. Python 버전 설치&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 설치 가능한 Python 버전 목록 확인&lt;br /&gt;
pyenv install --list&lt;br /&gt;
&lt;br /&gt;
# 특정 버전 설치 (예시)&lt;br /&gt;
pyenv install 3.9.18&lt;br /&gt;
pyenv install 3.10.13&lt;br /&gt;
pyenv install 3.11.7&lt;br /&gt;
pyenv install 3.12.1&lt;br /&gt;
&lt;br /&gt;
# 설치된 버전 확인&lt;br /&gt;
pyenv versions&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 1-4. 가상환경 생성&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# pyenv로 가상환경 생성&lt;br /&gt;
pyenv virtualenv 3.9.18 oracle-py39&lt;br /&gt;
pyenv virtualenv 3.10.13 oracle-py310&lt;br /&gt;
pyenv virtualenv 3.11.7 oracle-py311&lt;br /&gt;
&lt;br /&gt;
# 가상환경 목록 확인&lt;br /&gt;
pyenv virtualenvs&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 1-5. 가상환경 활성화&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 가상환경 활성화&lt;br /&gt;
pyenv activate oracle-py39&lt;br /&gt;
&lt;br /&gt;
# 비활성화&lt;br /&gt;
pyenv deactivate&lt;br /&gt;
&lt;br /&gt;
# 특정 디렉토리에 자동 활성화 설정&lt;br /&gt;
cd ~/python-projects/project1&lt;br /&gt;
pyenv local oracle-py39  # .python-version 파일 생성됨&lt;br /&gt;
&lt;br /&gt;
cd ~/python-projects/project2&lt;br /&gt;
pyenv local oracle-py310&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 방법 2: Homebrew로 여러 Python 버전 설치&lt;br /&gt;
&lt;br /&gt;
### 2-1. 여러 Python 버전 설치&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 최신 Python 3&lt;br /&gt;
brew install python@3.12&lt;br /&gt;
brew install python@3.11&lt;br /&gt;
brew install python@3.10&lt;br /&gt;
brew install python@3.9&lt;br /&gt;
&lt;br /&gt;
# 설치된 Python 확인&lt;br /&gt;
ls -la /opt/homebrew/bin/python*&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 2-2. 특정 버전으로 가상환경 생성&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# Python 3.9로 가상환경 생성&lt;br /&gt;
/opt/homebrew/bin/python3.9 -m venv ~/venvs/oracle-py39&lt;br /&gt;
&lt;br /&gt;
# Python 3.10으로 가상환경 생성&lt;br /&gt;
/opt/homebrew/bin/python3.10 -m venv ~/venvs/oracle-py310&lt;br /&gt;
&lt;br /&gt;
# Python 3.11로 가상환경 생성&lt;br /&gt;
/opt/homebrew/bin/python3.11 -m venv ~/venvs/oracle-py311&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 2-3. 가상환경 활성화&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 가상환경 활성화&lt;br /&gt;
source ~/venvs/oracle-py39/bin/activate&lt;br /&gt;
&lt;br /&gt;
# Python 버전 확인&lt;br /&gt;
python --version&lt;br /&gt;
&lt;br /&gt;
# 비활성화&lt;br /&gt;
deactivate&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 방법 3: python.org에서 직접 설치&lt;br /&gt;
&lt;br /&gt;
### 3-1. Python 다운로드 및 설치&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# python.org에서 원하는 버전 다운로드&lt;br /&gt;
# https://www.python.org/downloads/macos/&lt;br /&gt;
&lt;br /&gt;
# 설치 후 경로 확인&lt;br /&gt;
ls /Library/Frameworks/Python.framework/Versions/&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 3-2. 가상환경 생성&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 설치된 특정 버전으로 가상환경 생성&lt;br /&gt;
/Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m venv ~/venvs/py39&lt;br /&gt;
&lt;br /&gt;
# 활성화&lt;br /&gt;
source ~/venvs/py39/bin/activate&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## Oracle DBA 작업용 권장 설정&lt;br /&gt;
&lt;br /&gt;
### pyenv로 Oracle 프로젝트 환경 구성&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# Python 3.10 설치 (cx_Oracle 호환성 좋음)&lt;br /&gt;
pyenv install 3.10.13&lt;br /&gt;
&lt;br /&gt;
# Oracle 작업용 가상환경 생성&lt;br /&gt;
pyenv virtualenv 3.10.13 oracle-dba&lt;br /&gt;
&lt;br /&gt;
# 프로젝트 디렉토리에 설정&lt;br /&gt;
cd ~/python-projects/oracle-scripts&lt;br /&gt;
pyenv local oracle-dba&lt;br /&gt;
&lt;br /&gt;
# 필요한 패키지 설치&lt;br /&gt;
pip install --upgrade pip&lt;br /&gt;
pip install cx_Oracle&lt;br /&gt;
pip install pandas&lt;br /&gt;
pip install openpyxl&lt;br /&gt;
pip install sqlalchemy&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## VSCode에서 Python 버전 선택&lt;br /&gt;
&lt;br /&gt;
### VSCode 설정&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 프로젝트 폴더에서 VSCode 실행&lt;br /&gt;
cd ~/python-projects/oracle-scripts&lt;br /&gt;
code .&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
**VSCode에서**:&lt;br /&gt;
1. `Cmd + Shift + P`&lt;br /&gt;
2. &amp;quot;Python: Select Interpreter&amp;quot; 검색&lt;br /&gt;
3. 원하는 Python 버전/가상환경 선택&lt;br /&gt;
&lt;br /&gt;
### .vscode/settings.json 설정&lt;br /&gt;
&lt;br /&gt;
```json&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;python.defaultInterpreterPath&amp;quot;: &amp;quot;${workspaceFolder}/.venv/bin/python&amp;quot;,&lt;br /&gt;
  &amp;quot;python.terminal.activateEnvironment&amp;quot;: true,&lt;br /&gt;
  &amp;quot;python.venvPath&amp;quot;: &amp;quot;~/.pyenv/versions&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 프로젝트별 Python 버전 관리&lt;br /&gt;
&lt;br /&gt;
### 프로젝트 구조 예시&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
~/python-projects/&lt;br /&gt;
├── oracle-19c-scripts/          # Python 3.10&lt;br /&gt;
│   ├── .python-version          # oracle-py310&lt;br /&gt;
│   ├── requirements.txt&lt;br /&gt;
│   └── scripts/&lt;br /&gt;
├── oracle-23c-scripts/          # Python 3.11&lt;br /&gt;
│   ├── .python-version          # oracle-py311&lt;br /&gt;
│   ├── requirements.txt&lt;br /&gt;
│   └── scripts/&lt;br /&gt;
└── legacy-oracle-scripts/       # Python 3.9&lt;br /&gt;
    ├── .python-version          # oracle-py39&lt;br /&gt;
    ├── requirements.txt&lt;br /&gt;
    └── scripts/&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### requirements.txt 생성&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 현재 환경의 패키지 목록 저장&lt;br /&gt;
pip freeze &amp;gt; requirements.txt&lt;br /&gt;
&lt;br /&gt;
# 다른 환경에서 동일하게 설치&lt;br /&gt;
pip install -r requirements.txt&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## pyenv 유용한 명령어&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 설치된 Python 버전 확인&lt;br /&gt;
pyenv versions&lt;br /&gt;
&lt;br /&gt;
# 전역 Python 버전 설정&lt;br /&gt;
pyenv global 3.10.13&lt;br /&gt;
&lt;br /&gt;
# 현재 디렉토리의 Python 버전 설정&lt;br /&gt;
pyenv local 3.10.13&lt;br /&gt;
&lt;br /&gt;
# 쉘 세션의 Python 버전 설정 (임시)&lt;br /&gt;
pyenv shell 3.9.18&lt;br /&gt;
&lt;br /&gt;
# 가상환경 삭제&lt;br /&gt;
pyenv uninstall oracle-py39&lt;br /&gt;
&lt;br /&gt;
# Python 버전 제거&lt;br /&gt;
pyenv uninstall 3.9.18&lt;br /&gt;
&lt;br /&gt;
# pyenv 업데이트&lt;br /&gt;
brew upgrade pyenv&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 문제 해결&lt;br /&gt;
&lt;br /&gt;
### pyenv로 Python 설치 시 오류&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 빌드 도구 설치&lt;br /&gt;
xcode-select --install&lt;br /&gt;
&lt;br /&gt;
# 필요한 라이브러리 설치&lt;br /&gt;
brew install openssl readline sqlite3 xz zlib&lt;br /&gt;
&lt;br /&gt;
# 환경변수 설정 후 재설치&lt;br /&gt;
export LDFLAGS=&amp;quot;-L/opt/homebrew/opt/openssl@3/lib&amp;quot;&lt;br /&gt;
export CPPFLAGS=&amp;quot;-I/opt/homebrew/opt/openssl@3/include&amp;quot;&lt;br /&gt;
pyenv install 3.10.13&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### cx_Oracle 설치 오류&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# Oracle Instant Client 설치&lt;br /&gt;
brew install instantclient-basic&lt;br /&gt;
brew install instantclient-sdk&lt;br /&gt;
&lt;br /&gt;
# 환경변수 설정&lt;br /&gt;
echo &#039;export ORACLE_HOME=/opt/homebrew/opt/instantclient-basic&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
echo &#039;export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&#039; &amp;gt;&amp;gt; ~/.zshrc&lt;br /&gt;
source ~/.zshrc&lt;br /&gt;
&lt;br /&gt;
# cx_Oracle 재설치&lt;br /&gt;
pip install cx_Oracle&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 편리한 alias 설정&lt;br /&gt;
&lt;br /&gt;
**~/.zshrc**에 추가:&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# Python 가상환경 관련 alias&lt;br /&gt;
alias pylist=&#039;pyenv versions&#039;&lt;br /&gt;
alias pyvenv=&#039;pyenv virtualenvs&#039;&lt;br /&gt;
alias pyactive=&#039;pyenv activate&#039;&lt;br /&gt;
alias pydeactive=&#039;pyenv deactivate&#039;&lt;br /&gt;
&lt;br /&gt;
# 프로젝트별 바로가기&lt;br /&gt;
alias oracle39=&#039;cd ~/python-projects/oracle-scripts &amp;amp;&amp;amp; pyenv activate oracle-py39&#039;&lt;br /&gt;
alias oracle310=&#039;cd ~/python-projects/oracle-scripts &amp;amp;&amp;amp; pyenv activate oracle-py310&#039;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
적용:&lt;br /&gt;
```bash&lt;br /&gt;
source ~/.zshrc&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
## 실전 활용 예시&lt;br /&gt;
&lt;br /&gt;
```bash&lt;br /&gt;
# 1. 새 프로젝트 시작&lt;br /&gt;
mkdir ~/python-projects/awr-analyzer&lt;br /&gt;
cd ~/python-projects/awr-analyzer&lt;br /&gt;
&lt;br /&gt;
# 2. Python 3.10 가상환경 생성&lt;br /&gt;
pyenv virtualenv 3.10.13 awr-analyzer&lt;br /&gt;
&lt;br /&gt;
# 3. 로컬 설정&lt;br /&gt;
pyenv local awr-analyzer&lt;br /&gt;
&lt;br /&gt;
# 4. 패키지 설치&lt;br /&gt;
pip install cx_Oracle pandas matplotlib&lt;br /&gt;
&lt;br /&gt;
# 5. VSCode로 열기&lt;br /&gt;
code .&lt;br /&gt;
&lt;br /&gt;
# 6. VSCode에서 인터프리터 선택&lt;br /&gt;
# Cmd+Shift+P → &amp;quot;Python: Select Interpreter&amp;quot;&lt;br /&gt;
# → awr-analyzer 선택&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
pyenv를 사용하는 것이 가장 편리하고 권장되는 방법입니다. 추가 질문 있으시면 말씀해주세요!&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=DBA_Tune&amp;diff=2334</id>
		<title>DBA Tune</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=DBA_Tune&amp;diff=2334"/>
		<updated>2025-11-12T12:52:07Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== DBA Tunes ==&lt;br /&gt;
&lt;br /&gt;
## 프로그램 수정 가이드 - 핵심 요약&lt;br /&gt;
&lt;br /&gt;
===프로그램 구조===&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
app.py                    # Flask 백엔드 서버 (API, 데이터베이스)&lt;br /&gt;
templates/index_ag.html   # 프론트엔드 메인 페이지&lt;br /&gt;
instance/sql_tuning.db    # SQLite 데이터베이스&lt;br /&gt;
uploads/                  # 첨부파일 저장 폴더&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===주요 수정 포인트===&lt;br /&gt;
&lt;br /&gt;
====A. 데이터베이스 필드 추가====&lt;br /&gt;
위치: `app.py` 24-82줄 (TuningRequest 클래스)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 1. 필드 추가&lt;br /&gt;
class TuningRequest(db.Model):&lt;br /&gt;
    # ... 기존 필드들&lt;br /&gt;
    NEW_FIELD = db.Column(db.String(100))  # 새 필드 추가&lt;br /&gt;
&lt;br /&gt;
# 2. to_dict() 메서드에 추가&lt;br /&gt;
def to_dict(self):&lt;br /&gt;
    return {&lt;br /&gt;
        # ... 기존 필드들&lt;br /&gt;
        &#039;NEW_FIELD&#039;: self.NEW_FIELD,&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### B. 프론트엔드 컬럼 추가&lt;br /&gt;
위치: `index_ag.html` 227-238줄 (basicColumnDefs)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=js&amp;gt;&lt;br /&gt;
&lt;br /&gt;
const basicColumnDefs = [&lt;br /&gt;
    // ... 기존 컬럼들&lt;br /&gt;
    { headerName: &#039;새 컬럼&#039;, field: &#039;NEW_FIELD&#039;, width: 100, editable: true },&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### C. 새 요청 모달에 입력 필드 추가&lt;br /&gt;
위치: `index_ag.html` 113-197줄 (새 요청 추가 모달)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;label&amp;gt;새 필드&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;formNewField&amp;quot; placeholder=&amp;quot;새 필드 입력&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### D. 파일 크기 제한 변경&lt;br /&gt;
위치: `app.py` 15줄&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
app.config[&#039;MAX_CONTENT_LENGTH&#039;] = 50 * 1024 * 1024  # 50MB로 변경&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### E. 파일 타입 제한 추가&lt;br /&gt;
위치: `app.py` 276-318줄 (upload_attachment 함수)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ALLOWED_EXTENSIONS = {&#039;txt&#039;, &#039;pdf&#039;, &#039;png&#039;, &#039;jpg&#039;, &#039;sql&#039;}&lt;br /&gt;
file_extension = os.path.splitext(original_filename)[1][1:].lower()&lt;br /&gt;
&lt;br /&gt;
if file_extension not in ALLOWED_EXTENSIONS:&lt;br /&gt;
    return jsonify({&#039;error&#039;: &#039;허용되지 않는 파일 타입입니다.&#039;}), 400&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 주요 함수 위치===&lt;br /&gt;
&lt;br /&gt;
#### 백엔드 (app.py)&lt;br /&gt;
- 요청 생성: 156-188줄 (`create_request`)&lt;br /&gt;
- 요청 수정: 190-225줄 (`update_request`)&lt;br /&gt;
- 요청 삭제: 227-232줄 (`delete_request`)&lt;br /&gt;
- 파일 업로드: 276-318줄 (`upload_attachment`)&lt;br /&gt;
- 파일 다운로드: 329-341줄 (`download_attachment`)&lt;br /&gt;
- 파일 삭제: 343-361줄 (`delete_attachment`)&lt;br /&gt;
&lt;br /&gt;
#### 프론트엔드 (index_ag.html)&lt;br /&gt;
- 데이터 로드: 342-397줄 (`loadData`)&lt;br /&gt;
- 새 요청 추가: 402-425줄 (`addRow`)&lt;br /&gt;
- 새 요청 저장: 497-579줄 (`saveNewRequest`)&lt;br /&gt;
- 파일 업로드: 471-495줄 (`uploadFiles`)&lt;br /&gt;
- 상세 패널 표시: 809-1016줄 (`showDetailPanel`)&lt;br /&gt;
- 상세 정보 저장: 757-807줄 (`saveDetailChanges`)&lt;br /&gt;
&lt;br /&gt;
=== 스타일 수정===&lt;br /&gt;
위치: `index_ag.html` 9-76줄 (`&amp;lt;style&amp;gt;` 섹션)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/* 색상 변경 */&lt;br /&gt;
.btn-primary {&lt;br /&gt;
    background:#2563eb;  /* 파란색 */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 폰트 크기 변경 */&lt;br /&gt;
body {&lt;br /&gt;
    font-size: 14px;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===API 엔드포인트 목록===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
GET    /api/requests                    # 요청 목록 조회&lt;br /&gt;
POST   /api/requests                    # 새 요청 생성&lt;br /&gt;
PUT    /api/requests/&amp;lt;req_no&amp;gt;           # 요청 수정&lt;br /&gt;
DELETE /api/requests/&amp;lt;req_no&amp;gt;           # 요청 삭제&lt;br /&gt;
GET    /api/requests/&amp;lt;req_no&amp;gt;/sql-text  # SQL 텍스트 조회&lt;br /&gt;
PUT    /api/requests/&amp;lt;req_no&amp;gt;/sql-text  # SQL 텍스트 수정&lt;br /&gt;
POST   /api/requests/&amp;lt;req_no&amp;gt;/attachments  # 파일 업로드&lt;br /&gt;
GET    /api/requests/&amp;lt;req_no&amp;gt;/attachments  # 파일 목록 조회&lt;br /&gt;
GET    /api/attachments/&amp;lt;attach_no&amp;gt;     # 파일 다운로드&lt;br /&gt;
DELETE /api/attachments/&amp;lt;attach_no&amp;gt;     # 파일 삭제&lt;br /&gt;
POST   /api/init-data                   # 샘플 데이터 초기화&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 데이터베이스 테이블===&lt;br /&gt;
&lt;br /&gt;
#### TN_REQ (TuningRequest)&lt;br /&gt;
- 주요 필드: REQ_NO, REQ_USER, REQ_DT, SQL_PROGRAM_ID 등&lt;br /&gt;
- 관계: 첨부파일(1:N), 액션(1:1)&lt;br /&gt;
&lt;br /&gt;
#### TN_ATTACH (Attachment)&lt;br /&gt;
- 주요 필드: ATTACH_NO, REQ_NO, ORIGINAL_FILENAME, STORED_FILENAME&lt;br /&gt;
- 관계: 요청(N:1)&lt;br /&gt;
&lt;br /&gt;
#### TN_ACTN (TuningAction)&lt;br /&gt;
- 주요 필드: REQ_NO, ACTION_DATE, ACTION_USER&lt;br /&gt;
- 관계: 요청(1:1)&lt;br /&gt;
&lt;br /&gt;
===수정 시 주의사항===&lt;br /&gt;
&lt;br /&gt;
1. 데이터베이스 필드 추가 시&lt;br /&gt;
   - 기존 데이터는 NULL로 설정됨&lt;br /&gt;
   - 백업 후 수정 권장&lt;br /&gt;
&lt;br /&gt;
2. 파일 업로드 시&lt;br /&gt;
   - 파일 크기 제한 확인 (기본 16MB)&lt;br /&gt;
   - 업로드 폴더 권한 확인&lt;br /&gt;
&lt;br /&gt;
3. 프론트엔드 수정 시&lt;br /&gt;
   - JavaScript 함수명 중복 주의&lt;br /&gt;
   - HTML ID 중복 주의&lt;br /&gt;
&lt;br /&gt;
4. API 수정 시&lt;br /&gt;
   - 응답 형식 일관성 유지&lt;br /&gt;
   - 에러 처리 추가 권장&lt;br /&gt;
&lt;br /&gt;
=== 빠른 수정 예시===&lt;br /&gt;
&lt;br /&gt;
#### 예시 1: 새 필드 추가&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# app.py - TuningRequest 클래스에 필드 추가&lt;br /&gt;
PRIORITY = db.Column(db.String(10))  # 우선순위&lt;br /&gt;
&lt;br /&gt;
# app.py - to_dict() 메서드에 추가&lt;br /&gt;
&#039;PRIORITY&#039;: self.PRIORITY,&lt;br /&gt;
&lt;br /&gt;
# index_ag.html - 컬럼 추가&lt;br /&gt;
{ headerName: &#039;우선순위&#039;, field: &#039;PRIORITY&#039;, width: 100, editable: true },&lt;br /&gt;
&lt;br /&gt;
# index_ag.html - 모달에 입력 필드 추가&lt;br /&gt;
&amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;label&amp;gt;우선순위&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;select id=&amp;quot;formPriority&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;option value=&amp;quot;높음&amp;quot;&amp;gt;높음&amp;lt;/option&amp;gt;&lt;br /&gt;
        &amp;lt;option value=&amp;quot;보통&amp;quot;&amp;gt;보통&amp;lt;/option&amp;gt;&lt;br /&gt;
        &amp;lt;option value=&amp;quot;낮음&amp;quot;&amp;gt;낮음&amp;lt;/option&amp;gt;&lt;br /&gt;
    &amp;lt;/select&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### 예시 2: 파일 타입 제한&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# app.py - upload_attachment 함수에 추가&lt;br /&gt;
ALLOWED_EXTENSIONS = {&#039;txt&#039;, &#039;pdf&#039;, &#039;sql&#039;, &#039;jpg&#039;, &#039;png&#039;}&lt;br /&gt;
file_extension = os.path.splitext(original_filename)[1][1:].lower()&lt;br /&gt;
&lt;br /&gt;
if file_extension not in ALLOWED_EXTENSIONS:&lt;br /&gt;
    return jsonify({&#039;error&#039;: f&#039;{file_extension} 파일은 허용되지 않습니다.&#039;}), 400&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=DBA_Tune&amp;diff=2333</id>
		<title>DBA Tune</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=DBA_Tune&amp;diff=2333"/>
		<updated>2025-11-12T12:50:37Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== DBA Tunes ==&lt;br /&gt;
&lt;br /&gt;
## 프로그램 수정 가이드 - 핵심 요약&lt;br /&gt;
&lt;br /&gt;
### 1. 프로그램 구조&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
app.py                    # Flask 백엔드 서버 (API, 데이터베이스)&lt;br /&gt;
templates/index_ag.html   # 프론트엔드 메인 페이지&lt;br /&gt;
instance/sql_tuning.db    # SQLite 데이터베이스&lt;br /&gt;
uploads/                  # 첨부파일 저장 폴더&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
### 2. 주요 수정 포인트&lt;br /&gt;
&lt;br /&gt;
#### A. 데이터베이스 필드 추가&lt;br /&gt;
위치: `app.py` 24-82줄 (TuningRequest 클래스)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
# 1. 필드 추가&lt;br /&gt;
class TuningRequest(db.Model):&lt;br /&gt;
    # ... 기존 필드들&lt;br /&gt;
    NEW_FIELD = db.Column(db.String(100))  # 새 필드 추가&lt;br /&gt;
&lt;br /&gt;
# 2. to_dict() 메서드에 추가&lt;br /&gt;
def to_dict(self):&lt;br /&gt;
    return {&lt;br /&gt;
        # ... 기존 필드들&lt;br /&gt;
        &#039;NEW_FIELD&#039;: self.NEW_FIELD,&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### B. 프론트엔드 컬럼 추가&lt;br /&gt;
위치: `index_ag.html` 227-238줄 (basicColumnDefs)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=js&amp;gt;&lt;br /&gt;
&lt;br /&gt;
const basicColumnDefs = [&lt;br /&gt;
    // ... 기존 컬럼들&lt;br /&gt;
    { headerName: &#039;새 컬럼&#039;, field: &#039;NEW_FIELD&#039;, width: 100, editable: true },&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### C. 새 요청 모달에 입력 필드 추가&lt;br /&gt;
위치: `index_ag.html` 113-197줄 (새 요청 추가 모달)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;label&amp;gt;새 필드&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;formNewField&amp;quot; placeholder=&amp;quot;새 필드 입력&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### D. 파일 크기 제한 변경&lt;br /&gt;
위치: `app.py` 15줄&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
app.config[&#039;MAX_CONTENT_LENGTH&#039;] = 50 * 1024 * 1024  # 50MB로 변경&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### E. 파일 타입 제한 추가&lt;br /&gt;
위치: `app.py` 276-318줄 (upload_attachment 함수)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ALLOWED_EXTENSIONS = {&#039;txt&#039;, &#039;pdf&#039;, &#039;png&#039;, &#039;jpg&#039;, &#039;sql&#039;}&lt;br /&gt;
file_extension = os.path.splitext(original_filename)[1][1:].lower()&lt;br /&gt;
&lt;br /&gt;
if file_extension not in ALLOWED_EXTENSIONS:&lt;br /&gt;
    return jsonify({&#039;error&#039;: &#039;허용되지 않는 파일 타입입니다.&#039;}), 400&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### 3. 주요 함수 위치&lt;br /&gt;
&lt;br /&gt;
#### 백엔드 (app.py)&lt;br /&gt;
- 요청 생성: 156-188줄 (`create_request`)&lt;br /&gt;
- 요청 수정: 190-225줄 (`update_request`)&lt;br /&gt;
- 요청 삭제: 227-232줄 (`delete_request`)&lt;br /&gt;
- 파일 업로드: 276-318줄 (`upload_attachment`)&lt;br /&gt;
- 파일 다운로드: 329-341줄 (`download_attachment`)&lt;br /&gt;
- 파일 삭제: 343-361줄 (`delete_attachment`)&lt;br /&gt;
&lt;br /&gt;
#### 프론트엔드 (index_ag.html)&lt;br /&gt;
- 데이터 로드: 342-397줄 (`loadData`)&lt;br /&gt;
- 새 요청 추가: 402-425줄 (`addRow`)&lt;br /&gt;
- 새 요청 저장: 497-579줄 (`saveNewRequest`)&lt;br /&gt;
- 파일 업로드: 471-495줄 (`uploadFiles`)&lt;br /&gt;
- 상세 패널 표시: 809-1016줄 (`showDetailPanel`)&lt;br /&gt;
- 상세 정보 저장: 757-807줄 (`saveDetailChanges`)&lt;br /&gt;
&lt;br /&gt;
### 4. 스타일 수정&lt;br /&gt;
위치: `index_ag.html` 9-76줄 (`&amp;lt;style&amp;gt;` 섹션)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
/* 색상 변경 */&lt;br /&gt;
.btn-primary {&lt;br /&gt;
    background:#2563eb;  /* 파란색 */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 폰트 크기 변경 */&lt;br /&gt;
body {&lt;br /&gt;
    font-size: 14px;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### 5. API 엔드포인트 목록&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
GET    /api/requests                    # 요청 목록 조회&lt;br /&gt;
POST   /api/requests                    # 새 요청 생성&lt;br /&gt;
PUT    /api/requests/&amp;lt;req_no&amp;gt;           # 요청 수정&lt;br /&gt;
DELETE /api/requests/&amp;lt;req_no&amp;gt;           # 요청 삭제&lt;br /&gt;
GET    /api/requests/&amp;lt;req_no&amp;gt;/sql-text  # SQL 텍스트 조회&lt;br /&gt;
PUT    /api/requests/&amp;lt;req_no&amp;gt;/sql-text  # SQL 텍스트 수정&lt;br /&gt;
POST   /api/requests/&amp;lt;req_no&amp;gt;/attachments  # 파일 업로드&lt;br /&gt;
GET    /api/requests/&amp;lt;req_no&amp;gt;/attachments  # 파일 목록 조회&lt;br /&gt;
GET    /api/attachments/&amp;lt;attach_no&amp;gt;     # 파일 다운로드&lt;br /&gt;
DELETE /api/attachments/&amp;lt;attach_no&amp;gt;     # 파일 삭제&lt;br /&gt;
POST   /api/init-data                   # 샘플 데이터 초기화&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### 6. 데이터베이스 테이블&lt;br /&gt;
&lt;br /&gt;
#### TN_REQ (TuningRequest)&lt;br /&gt;
- 주요 필드: REQ_NO, REQ_USER, REQ_DT, SQL_PROGRAM_ID 등&lt;br /&gt;
- 관계: 첨부파일(1:N), 액션(1:1)&lt;br /&gt;
&lt;br /&gt;
#### TN_ATTACH (Attachment)&lt;br /&gt;
- 주요 필드: ATTACH_NO, REQ_NO, ORIGINAL_FILENAME, STORED_FILENAME&lt;br /&gt;
- 관계: 요청(N:1)&lt;br /&gt;
&lt;br /&gt;
#### TN_ACTN (TuningAction)&lt;br /&gt;
- 주요 필드: REQ_NO, ACTION_DATE, ACTION_USER&lt;br /&gt;
- 관계: 요청(1:1)&lt;br /&gt;
&lt;br /&gt;
### 7. 수정 시 주의사항&lt;br /&gt;
&lt;br /&gt;
1. 데이터베이스 필드 추가 시&lt;br /&gt;
   - 기존 데이터는 NULL로 설정됨&lt;br /&gt;
   - 백업 후 수정 권장&lt;br /&gt;
&lt;br /&gt;
2. 파일 업로드 시&lt;br /&gt;
   - 파일 크기 제한 확인 (기본 16MB)&lt;br /&gt;
   - 업로드 폴더 권한 확인&lt;br /&gt;
&lt;br /&gt;
3. 프론트엔드 수정 시&lt;br /&gt;
   - JavaScript 함수명 중복 주의&lt;br /&gt;
   - HTML ID 중복 주의&lt;br /&gt;
&lt;br /&gt;
4. API 수정 시&lt;br /&gt;
   - 응답 형식 일관성 유지&lt;br /&gt;
   - 에러 처리 추가 권장&lt;br /&gt;
&lt;br /&gt;
### 8. 빠른 수정 예시&lt;br /&gt;
&lt;br /&gt;
#### 예시 1: 새 필드 추가&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# app.py - TuningRequest 클래스에 필드 추가&lt;br /&gt;
PRIORITY = db.Column(db.String(10))  # 우선순위&lt;br /&gt;
&lt;br /&gt;
# app.py - to_dict() 메서드에 추가&lt;br /&gt;
&#039;PRIORITY&#039;: self.PRIORITY,&lt;br /&gt;
&lt;br /&gt;
# index_ag.html - 컬럼 추가&lt;br /&gt;
{ headerName: &#039;우선순위&#039;, field: &#039;PRIORITY&#039;, width: 100, editable: true },&lt;br /&gt;
&lt;br /&gt;
# index_ag.html - 모달에 입력 필드 추가&lt;br /&gt;
&amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;label&amp;gt;우선순위&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;select id=&amp;quot;formPriority&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;option value=&amp;quot;높음&amp;quot;&amp;gt;높음&amp;lt;/option&amp;gt;&lt;br /&gt;
        &amp;lt;option value=&amp;quot;보통&amp;quot;&amp;gt;보통&amp;lt;/option&amp;gt;&lt;br /&gt;
        &amp;lt;option value=&amp;quot;낮음&amp;quot;&amp;gt;낮음&amp;lt;/option&amp;gt;&lt;br /&gt;
    &amp;lt;/select&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#### 예시 2: 파일 타입 제한&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# app.py - upload_attachment 함수에 추가&lt;br /&gt;
ALLOWED_EXTENSIONS = {&#039;txt&#039;, &#039;pdf&#039;, &#039;sql&#039;, &#039;jpg&#039;, &#039;png&#039;}&lt;br /&gt;
file_extension = os.path.splitext(original_filename)[1][1:].lower()&lt;br /&gt;
&lt;br /&gt;
if file_extension not in ALLOWED_EXTENSIONS:&lt;br /&gt;
    return jsonify({&#039;error&#039;: f&#039;{file_extension} 파일은 허용되지 않습니다.&#039;}), 400&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[category:python]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=DBA_Tune&amp;diff=2332</id>
		<title>DBA Tune</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=DBA_Tune&amp;diff=2332"/>
		<updated>2025-11-12T12:45:03Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: == DBA Tunes ==  프로그램 수정 가이드를 작성했습니다. 핵심만 간단 요약합니다.  ## 프로그램 수정 가이드 - 핵심 요약  ### 1. 프로그램 구조 ``` app.py                    # Flask 백엔드 서버 (API, 데이터베이스) templates/index_ag.html   # 프론트엔드 메인 페이지 instance/sql_tuning.db    # SQLite 데이터베이스 uploads/                  # 첨부파일 저장 폴더 ```  ### 2. 주요 수정 포인트  #### A. 데...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== DBA Tunes ==&lt;br /&gt;
&lt;br /&gt;
프로그램 수정 가이드를 작성했습니다. 핵심만 간단 요약합니다.&lt;br /&gt;
&lt;br /&gt;
## 프로그램 수정 가이드 - 핵심 요약&lt;br /&gt;
&lt;br /&gt;
### 1. 프로그램 구조&lt;br /&gt;
```&lt;br /&gt;
app.py                    # Flask 백엔드 서버 (API, 데이터베이스)&lt;br /&gt;
templates/index_ag.html   # 프론트엔드 메인 페이지&lt;br /&gt;
instance/sql_tuning.db    # SQLite 데이터베이스&lt;br /&gt;
uploads/                  # 첨부파일 저장 폴더&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 2. 주요 수정 포인트&lt;br /&gt;
&lt;br /&gt;
#### A. 데이터베이스 필드 추가&lt;br /&gt;
위치: `app.py` 24-82줄 (TuningRequest 클래스)&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
# 1. 필드 추가&lt;br /&gt;
class TuningRequest(db.Model):&lt;br /&gt;
    # ... 기존 필드들&lt;br /&gt;
    NEW_FIELD = db.Column(db.String(100))  # 새 필드 추가&lt;br /&gt;
&lt;br /&gt;
# 2. to_dict() 메서드에 추가&lt;br /&gt;
def to_dict(self):&lt;br /&gt;
    return {&lt;br /&gt;
        # ... 기존 필드들&lt;br /&gt;
        &#039;NEW_FIELD&#039;: self.NEW_FIELD,&lt;br /&gt;
    }&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### B. 프론트엔드 컬럼 추가&lt;br /&gt;
위치: `index_ag.html` 227-238줄 (basicColumnDefs)&lt;br /&gt;
&lt;br /&gt;
```javascript&lt;br /&gt;
const basicColumnDefs = [&lt;br /&gt;
    // ... 기존 컬럼들&lt;br /&gt;
    { headerName: &#039;새 컬럼&#039;, field: &#039;NEW_FIELD&#039;, width: 100, editable: true },&lt;br /&gt;
];&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### C. 새 요청 모달에 입력 필드 추가&lt;br /&gt;
위치: `index_ag.html` 113-197줄 (새 요청 추가 모달)&lt;br /&gt;
&lt;br /&gt;
```html&lt;br /&gt;
&amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;label&amp;gt;새 필드&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;formNewField&amp;quot; placeholder=&amp;quot;새 필드 입력&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### D. 파일 크기 제한 변경&lt;br /&gt;
위치: `app.py` 15줄&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
app.config[&#039;MAX_CONTENT_LENGTH&#039;] = 50 * 1024 * 1024  # 50MB로 변경&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### E. 파일 타입 제한 추가&lt;br /&gt;
위치: `app.py` 276-318줄 (upload_attachment 함수)&lt;br /&gt;
&lt;br /&gt;
```python&lt;br /&gt;
ALLOWED_EXTENSIONS = {&#039;txt&#039;, &#039;pdf&#039;, &#039;png&#039;, &#039;jpg&#039;, &#039;sql&#039;}&lt;br /&gt;
file_extension = os.path.splitext(original_filename)[1][1:].lower()&lt;br /&gt;
&lt;br /&gt;
if file_extension not in ALLOWED_EXTENSIONS:&lt;br /&gt;
    return jsonify({&#039;error&#039;: &#039;허용되지 않는 파일 타입입니다.&#039;}), 400&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 3. 주요 함수 위치&lt;br /&gt;
&lt;br /&gt;
#### 백엔드 (app.py)&lt;br /&gt;
- 요청 생성: 156-188줄 (`create_request`)&lt;br /&gt;
- 요청 수정: 190-225줄 (`update_request`)&lt;br /&gt;
- 요청 삭제: 227-232줄 (`delete_request`)&lt;br /&gt;
- 파일 업로드: 276-318줄 (`upload_attachment`)&lt;br /&gt;
- 파일 다운로드: 329-341줄 (`download_attachment`)&lt;br /&gt;
- 파일 삭제: 343-361줄 (`delete_attachment`)&lt;br /&gt;
&lt;br /&gt;
#### 프론트엔드 (index_ag.html)&lt;br /&gt;
- 데이터 로드: 342-397줄 (`loadData`)&lt;br /&gt;
- 새 요청 추가: 402-425줄 (`addRow`)&lt;br /&gt;
- 새 요청 저장: 497-579줄 (`saveNewRequest`)&lt;br /&gt;
- 파일 업로드: 471-495줄 (`uploadFiles`)&lt;br /&gt;
- 상세 패널 표시: 809-1016줄 (`showDetailPanel`)&lt;br /&gt;
- 상세 정보 저장: 757-807줄 (`saveDetailChanges`)&lt;br /&gt;
&lt;br /&gt;
### 4. 스타일 수정&lt;br /&gt;
위치: `index_ag.html` 9-76줄 (`&amp;lt;style&amp;gt;` 섹션)&lt;br /&gt;
&lt;br /&gt;
```css&lt;br /&gt;
/* 색상 변경 */&lt;br /&gt;
.btn-primary {&lt;br /&gt;
    background:#2563eb;  /* 파란색 */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 폰트 크기 변경 */&lt;br /&gt;
body {&lt;br /&gt;
    font-size: 14px;&lt;br /&gt;
}&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 5. API 엔드포인트 목록&lt;br /&gt;
&lt;br /&gt;
```&lt;br /&gt;
GET    /api/requests                    # 요청 목록 조회&lt;br /&gt;
POST   /api/requests                    # 새 요청 생성&lt;br /&gt;
PUT    /api/requests/&amp;lt;req_no&amp;gt;           # 요청 수정&lt;br /&gt;
DELETE /api/requests/&amp;lt;req_no&amp;gt;           # 요청 삭제&lt;br /&gt;
GET    /api/requests/&amp;lt;req_no&amp;gt;/sql-text  # SQL 텍스트 조회&lt;br /&gt;
PUT    /api/requests/&amp;lt;req_no&amp;gt;/sql-text  # SQL 텍스트 수정&lt;br /&gt;
POST   /api/requests/&amp;lt;req_no&amp;gt;/attachments  # 파일 업로드&lt;br /&gt;
GET    /api/requests/&amp;lt;req_no&amp;gt;/attachments  # 파일 목록 조회&lt;br /&gt;
GET    /api/attachments/&amp;lt;attach_no&amp;gt;     # 파일 다운로드&lt;br /&gt;
DELETE /api/attachments/&amp;lt;attach_no&amp;gt;     # 파일 삭제&lt;br /&gt;
POST   /api/init-data                   # 샘플 데이터 초기화&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
### 6. 데이터베이스 테이블&lt;br /&gt;
&lt;br /&gt;
#### TN_REQ (TuningRequest)&lt;br /&gt;
- 주요 필드: REQ_NO, REQ_USER, REQ_DT, SQL_PROGRAM_ID 등&lt;br /&gt;
- 관계: 첨부파일(1:N), 액션(1:1)&lt;br /&gt;
&lt;br /&gt;
#### TN_ATTACH (Attachment)&lt;br /&gt;
- 주요 필드: ATTACH_NO, REQ_NO, ORIGINAL_FILENAME, STORED_FILENAME&lt;br /&gt;
- 관계: 요청(N:1)&lt;br /&gt;
&lt;br /&gt;
#### TN_ACTN (TuningAction)&lt;br /&gt;
- 주요 필드: REQ_NO, ACTION_DATE, ACTION_USER&lt;br /&gt;
- 관계: 요청(1:1)&lt;br /&gt;
&lt;br /&gt;
### 7. 수정 시 주의사항&lt;br /&gt;
&lt;br /&gt;
1. 데이터베이스 필드 추가 시&lt;br /&gt;
   - 기존 데이터는 NULL로 설정됨&lt;br /&gt;
   - 백업 후 수정 권장&lt;br /&gt;
&lt;br /&gt;
2. 파일 업로드 시&lt;br /&gt;
   - 파일 크기 제한 확인 (기본 16MB)&lt;br /&gt;
   - 업로드 폴더 권한 확인&lt;br /&gt;
&lt;br /&gt;
3. 프론트엔드 수정 시&lt;br /&gt;
   - JavaScript 함수명 중복 주의&lt;br /&gt;
   - HTML ID 중복 주의&lt;br /&gt;
&lt;br /&gt;
4. API 수정 시&lt;br /&gt;
   - 응답 형식 일관성 유지&lt;br /&gt;
   - 에러 처리 추가 권장&lt;br /&gt;
&lt;br /&gt;
### 8. 빠른 수정 예시&lt;br /&gt;
&lt;br /&gt;
#### 예시 1: 새 필드 추가&lt;br /&gt;
```python&lt;br /&gt;
# app.py - TuningRequest 클래스에 필드 추가&lt;br /&gt;
PRIORITY = db.Column(db.String(10))  # 우선순위&lt;br /&gt;
&lt;br /&gt;
# app.py - to_dict() 메서드에 추가&lt;br /&gt;
&#039;PRIORITY&#039;: self.PRIORITY,&lt;br /&gt;
&lt;br /&gt;
# index_ag.html - 컬럼 추가&lt;br /&gt;
{ headerName: &#039;우선순위&#039;, field: &#039;PRIORITY&#039;, width: 100, editable: true },&lt;br /&gt;
&lt;br /&gt;
# index_ag.html - 모달에 입력 필드 추가&lt;br /&gt;
&amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;label&amp;gt;우선순위&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;select id=&amp;quot;formPriority&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;option value=&amp;quot;높음&amp;quot;&amp;gt;높음&amp;lt;/option&amp;gt;&lt;br /&gt;
        &amp;lt;option value=&amp;quot;보통&amp;quot;&amp;gt;보통&amp;lt;/option&amp;gt;&lt;br /&gt;
        &amp;lt;option value=&amp;quot;낮음&amp;quot;&amp;gt;낮음&amp;lt;/option&amp;gt;&lt;br /&gt;
    &amp;lt;/select&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
#### 예시 2: 파일 타입 제한&lt;br /&gt;
```python&lt;br /&gt;
# app.py - upload_attachment 함수에 추가&lt;br /&gt;
ALLOWED_EXTENSIONS = {&#039;txt&#039;, &#039;pdf&#039;, &#039;sql&#039;, &#039;jpg&#039;, &#039;png&#039;}&lt;br /&gt;
file_extension = os.path.splitext(original_filename)[1][1:].lower()&lt;br /&gt;
&lt;br /&gt;
if file_extension not in ALLOWED_EXTENSIONS:&lt;br /&gt;
    return jsonify({&#039;error&#039;: f&#039;{file_extension} 파일은 허용되지 않습니다.&#039;}), 400&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
상세 가이드는 `프로그램_수정_가이드.md` 파일에 있습니다. 특정 부분을 더 설명해야 하면 알려주세요.&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%85%8D%EC%8A%A4%ED%8A%B8%ED%8C%8C%EC%9D%BC%EC%9D%84_%EC%9D%BD%EC%96%B4%EC%84%9C_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0_(%ED%8A%9C%EB%8B%9D%EB%B3%B4%EA%B3%A0%EC%84%9C%EB%A5%BC_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC)&amp;diff=2331</id>
		<title>텍스트파일을 읽어서 엑셀로 정리하기 (튜닝보고서를 엑셀로 정리)</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%85%8D%EC%8A%A4%ED%8A%B8%ED%8C%8C%EC%9D%BC%EC%9D%84_%EC%9D%BD%EC%96%B4%EC%84%9C_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0_(%ED%8A%9C%EB%8B%9D%EB%B3%B4%EA%B3%A0%EC%84%9C%EB%A5%BC_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC)&amp;diff=2331"/>
		<updated>2025-11-10T08:47:22Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: == 튜닝 보고서 파일들을 읽어서 엑셀로 정리하는 프로그램 == &amp;lt;source lang=python&amp;gt; import os import re import sys from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment  def extract_report_data(file_path):     &amp;quot;&amp;quot;&amp;quot;튜닝 보고서 파일에서 데이터 추출&amp;quot;&amp;quot;&amp;quot;     try:         # 인코딩 자동 감지         encodings = [&amp;#039;utf-8-sig&amp;#039;, &amp;#039;utf-8&amp;#039;, &amp;#039;cp949&amp;#039;, &amp;#039;euc-kr&amp;#039;]         content = None                  for encoding in encodin...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 튜닝 보고서 파일들을 읽어서 엑셀로 정리하는 프로그램 ==&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import re&lt;br /&gt;
import sys&lt;br /&gt;
from openpyxl import Workbook&lt;br /&gt;
from openpyxl.styles import Font, PatternFill, Alignment&lt;br /&gt;
&lt;br /&gt;
def extract_report_data(file_path):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;튜닝 보고서 파일에서 데이터 추출&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    try:&lt;br /&gt;
        # 인코딩 자동 감지&lt;br /&gt;
        encodings = [&#039;utf-8-sig&#039;, &#039;utf-8&#039;, &#039;cp949&#039;, &#039;euc-kr&#039;]&lt;br /&gt;
        content = None&lt;br /&gt;
        &lt;br /&gt;
        for encoding in encodings:&lt;br /&gt;
            try:&lt;br /&gt;
                with open(file_path, &#039;r&#039;, encoding=encoding) as f:&lt;br /&gt;
                    content = f.read()&lt;br /&gt;
                break&lt;br /&gt;
            except UnicodeDecodeError:&lt;br /&gt;
                continue&lt;br /&gt;
        &lt;br /&gt;
        if content is None:&lt;br /&gt;
            print(f&amp;quot;인코딩 오류: {file_path}&amp;quot;)&lt;br /&gt;
            return None&lt;br /&gt;
        &lt;br /&gt;
        data = {&lt;br /&gt;
            &#039;DB&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;식별자&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;업무구분&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;수집유형&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;프로그램명&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;서비스&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;온라인/배치&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;수행빈도&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;Dynamic SQL여부&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;개발담당자&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;담당자연락처&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;Logical Reads(전)&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;Logical Reads(후)&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;Elapsed Time(전)&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;Elapsed Time(후)&#039;: &#039;&#039;,&lt;br /&gt;
            &#039;파일명&#039;: os.path.basename(file_path)&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        # 각 항목 추출 (정규식 사용)&lt;br /&gt;
        patterns = {&lt;br /&gt;
            &#039;DB&#039;: r&#039;1\)\s*DB\s*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;식별자&#039;: r&#039;2\)\s*식별자\s*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;업무구분&#039;: r&#039;\(?\s*3\)?\s*업무\s*구분\s*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;수집유형&#039;: r&#039;4\)\s*수집\s*유형\s*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;프로그램명&#039;: r&#039;5\)\s*프로그램명\s*/\s*화면명\s*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;서비스&#039;: r&#039;6\)\s*서비스\s*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;온라인/배치&#039;: r&#039;7\)\s*온라인\s*/\s*배치\s*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;수행빈도&#039;: r&#039;8\)\s*수행\s*빈도.*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;Dynamic SQL여부&#039;: r&#039;9\)\s*Dynamic\s*S[OQ]L\s*여부.*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;개발담당자&#039;: r&#039;10\)\s*개발\s*담당자\s*[:：]\s*(.+)&#039;,&lt;br /&gt;
            &#039;담당자연락처&#039;: r&#039;11\)\s*담당자\s*연락처\s*[:：]\s*(.+)&#039;,&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        for key, pattern in patterns.items():&lt;br /&gt;
            match = re.search(pattern, content, re.IGNORECASE | re.MULTILINE)&lt;br /&gt;
            if match:&lt;br /&gt;
                data[key] = match.group(1).strip()&lt;br /&gt;
        &lt;br /&gt;
        # Logical Reads 추출&lt;br /&gt;
        logical_reads = re.search(r&#039;Logical\s*Reads.*[:：]\s*([0-9,]+)\s*/\s*([0-9,]+)&#039;, content, re.IGNORECASE)&lt;br /&gt;
        if logical_reads:&lt;br /&gt;
            data[&#039;Logical Reads(전)&#039;] = logical_reads.group(1).strip().replace(&#039;,&#039;, &#039;&#039;)&lt;br /&gt;
            data[&#039;Logical Reads(후)&#039;] = logical_reads.group(2).strip().replace(&#039;,&#039;, &#039;&#039;)&lt;br /&gt;
        &lt;br /&gt;
        # Elapsed Time 추출&lt;br /&gt;
        elapsed_time = re.search(r&#039;Elapsed\s*Time.*[:：]\s*([0-9.]+)\s*/\s*([0-9.]+)&#039;, content, re.IGNORECASE)&lt;br /&gt;
        if elapsed_time:&lt;br /&gt;
            data[&#039;Elapsed Time(전)&#039;] = elapsed_time.group(1).strip()&lt;br /&gt;
            data[&#039;Elapsed Time(후)&#039;] = elapsed_time.group(2).strip()&lt;br /&gt;
        &lt;br /&gt;
        return data&lt;br /&gt;
        &lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        print(f&amp;quot;파일 처리 오류 [{file_path}]: {str(e)}&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
&lt;br /&gt;
def create_excel_report(folder_path, output_file=&#039;튜닝보고서_정리.xlsx&#039;):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;폴더의 모든 튜닝 보고서를 읽어 엑셀로 정리&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    # 워크북 생성&lt;br /&gt;
    wb = Workbook()&lt;br /&gt;
    ws = wb.active&lt;br /&gt;
    ws.title = &amp;quot;튜닝보고서&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    # 헤더 작성&lt;br /&gt;
    headers = [&#039;No&#039;, &#039;DB&#039;, &#039;식별자&#039;, &#039;업무구분&#039;, &#039;수집유형&#039;, &#039;프로그램명&#039;, &#039;서비스&#039;, &lt;br /&gt;
               &#039;온라인/배치&#039;, &#039;수행빈도&#039;, &#039;Dynamic SQL여부&#039;, &#039;개발담당자&#039;, &#039;담당자연락처&#039;,&lt;br /&gt;
               &#039;Logical Reads(전)&#039;, &#039;Logical Reads(후)&#039;, &#039;개선율(%)&#039;, &lt;br /&gt;
               &#039;Elapsed Time(전)&#039;, &#039;Elapsed Time(후)&#039;, &#039;개선율(%)&#039;, &#039;파일명&#039;]&lt;br /&gt;
    &lt;br /&gt;
    # 헤더 스타일&lt;br /&gt;
    header_fill = PatternFill(start_color=&amp;quot;366092&amp;quot;, end_color=&amp;quot;366092&amp;quot;, fill_type=&amp;quot;solid&amp;quot;)&lt;br /&gt;
    header_font = Font(bold=True, color=&amp;quot;FFFFFF&amp;quot;, size=11)&lt;br /&gt;
    &lt;br /&gt;
    for col_idx, header in enumerate(headers, 1):&lt;br /&gt;
        cell = ws.cell(row=1, column=col_idx, value=header)&lt;br /&gt;
        cell.fill = header_fill&lt;br /&gt;
        cell.font = header_font&lt;br /&gt;
        cell.alignment = Alignment(horizontal=&#039;center&#039;, vertical=&#039;center&#039;)&lt;br /&gt;
    &lt;br /&gt;
    # 파일 읽기&lt;br /&gt;
    files = [f for f in os.listdir(folder_path) if f.endswith((&#039;.txt&#039;, &#039;.doc&#039;, &#039;.docx&#039;, &#039;.hwp&#039;))]&lt;br /&gt;
    &lt;br /&gt;
    if not files:&lt;br /&gt;
        print(f&amp;quot;폴더에 파일이 없습니다: {folder_path}&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    print(f&amp;quot;총 {len(files)}개 파일 처리 중...&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    row_idx = 2&lt;br /&gt;
    success_count = 0&lt;br /&gt;
    &lt;br /&gt;
    for idx, filename in enumerate(files, 1):&lt;br /&gt;
        file_path = os.path.join(folder_path, filename)&lt;br /&gt;
        print(f&amp;quot;[{idx}/{len(files)}] 처리 중: {filename}&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        data = extract_report_data(file_path)&lt;br /&gt;
        &lt;br /&gt;
        if data:&lt;br /&gt;
            # 개선율 계산&lt;br /&gt;
            logical_improve = &#039;&#039;&lt;br /&gt;
            elapsed_improve = &#039;&#039;&lt;br /&gt;
            &lt;br /&gt;
            try:&lt;br /&gt;
                if data[&#039;Logical Reads(전)&#039;] and data[&#039;Logical Reads(후)&#039;]:&lt;br /&gt;
                    before = float(data[&#039;Logical Reads(전)&#039;])&lt;br /&gt;
                    after = float(data[&#039;Logical Reads(후)&#039;])&lt;br /&gt;
                    if before &amp;gt; 0:&lt;br /&gt;
                        logical_improve = round((before - after) / before * 100, 2)&lt;br /&gt;
            except:&lt;br /&gt;
                pass&lt;br /&gt;
            &lt;br /&gt;
            try:&lt;br /&gt;
                if data[&#039;Elapsed Time(전)&#039;] and data[&#039;Elapsed Time(후)&#039;]:&lt;br /&gt;
                    before = float(data[&#039;Elapsed Time(전)&#039;])&lt;br /&gt;
                    after = float(data[&#039;Elapsed Time(후)&#039;])&lt;br /&gt;
                    if before &amp;gt; 0:&lt;br /&gt;
                        elapsed_improve = round((before - after) / before * 100, 2)&lt;br /&gt;
            except:&lt;br /&gt;
                pass&lt;br /&gt;
            &lt;br /&gt;
            # 데이터 쓰기&lt;br /&gt;
            ws.cell(row=row_idx, column=1, value=success_count + 1)&lt;br /&gt;
            ws.cell(row=row_idx, column=2, value=data[&#039;DB&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=3, value=data[&#039;식별자&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=4, value=data[&#039;업무구분&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=5, value=data[&#039;수집유형&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=6, value=data[&#039;프로그램명&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=7, value=data[&#039;서비스&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=8, value=data[&#039;온라인/배치&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=9, value=data[&#039;수행빈도&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=10, value=data[&#039;Dynamic SQL여부&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=11, value=data[&#039;개발담당자&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=12, value=data[&#039;담당자연락처&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=13, value=data[&#039;Logical Reads(전)&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=14, value=data[&#039;Logical Reads(후)&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=15, value=logical_improve)&lt;br /&gt;
            ws.cell(row=row_idx, column=16, value=data[&#039;Elapsed Time(전)&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=17, value=data[&#039;Elapsed Time(후)&#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=18, value=elapsed_improve)&lt;br /&gt;
            ws.cell(row=row_idx, column=19, value=data[&#039;파일명&#039;])&lt;br /&gt;
            &lt;br /&gt;
            row_idx += 1&lt;br /&gt;
            success_count += 1&lt;br /&gt;
    &lt;br /&gt;
    # 컬럼 너비 자동 조정&lt;br /&gt;
    for col in ws.columns:&lt;br /&gt;
        max_length = 0&lt;br /&gt;
        column = col[0].column_letter&lt;br /&gt;
        for cell in col:&lt;br /&gt;
            try:&lt;br /&gt;
                if len(str(cell.value)) &amp;gt; max_length:&lt;br /&gt;
                    max_length = len(str(cell.value))&lt;br /&gt;
            except:&lt;br /&gt;
                pass&lt;br /&gt;
        adjusted_width = min(max_length + 2, 50)&lt;br /&gt;
        ws.column_dimensions[column].width = adjusted_width&lt;br /&gt;
    &lt;br /&gt;
    # 엑셀 저장&lt;br /&gt;
    output_path = os.path.join(folder_path, output_file)&lt;br /&gt;
    wb.save(output_path)&lt;br /&gt;
    &lt;br /&gt;
    print(f&amp;quot;\n완료! {success_count}개 파일 처리 완료&amp;quot;)&lt;br /&gt;
    print(f&amp;quot;저장 위치: {output_path}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
if __name__ == &#039;__main__&#039;:&lt;br /&gt;
    if len(sys.argv) != 2:&lt;br /&gt;
        print(&amp;quot;사용법: python parse_tuning_reports.py &amp;lt;폴더경로&amp;gt;&amp;quot;)&lt;br /&gt;
        print(&amp;quot;예시: python parse_tuning_reports.py C:\\튜닝보고서&amp;quot;)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
    &lt;br /&gt;
    folder_path = sys.argv[1]&lt;br /&gt;
    &lt;br /&gt;
    if not os.path.exists(folder_path):&lt;br /&gt;
        print(f&amp;quot;폴더를 찾을 수 없습니다: {folder_path}&amp;quot;)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
    &lt;br /&gt;
    create_excel_report(folder_path)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 사용 방법: ===&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
python parse_tuning_reports.py C:\튜닝보고서폴더&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 주요 기능: ===&lt;br /&gt;
1. ✅ 폴더 내 모든 튜닝 보고서 파일 자동 처리&lt;br /&gt;
2. ✅ UTF-8, CP949(ANSI) 자동 인식&lt;br /&gt;
3. ✅ 11개 항목 자동 추출&lt;br /&gt;
4. ✅ 성능 개선율 자동 계산&lt;br /&gt;
5. ✅ 엑셀 파일로 자동 정리 (헤더 스타일 포함)&lt;br /&gt;
&lt;br /&gt;
혹시 파일 형식이 조금 다르면 샘플 파일을 보여주시면 패턴을 수정해드릴게요!&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%85%8C%EC%8A%AC%EB%9D%BC_%EB%B6%84%ED%95%A0%EB%A7%A4%EC%88%98_%EC%A0%84%EB%9E%B5&amp;diff=2330</id>
		<title>테슬라 분할매수 전략</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%85%8C%EC%8A%AC%EB%9D%BC_%EB%B6%84%ED%95%A0%EB%A7%A4%EC%88%98_%EC%A0%84%EB%9E%B5&amp;diff=2330"/>
		<updated>2025-11-06T07:41:16Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서:   * 📊 Tesla 최근 한 달 주가 분석 (2025.10.6~11.5) * 주요 지표 ** 최근 한 달간 Tesla는 최고가 $474.07, 최저가 $411.45로 약 15.2% 변동폭을 보였으며, 평균가는 $442.63입니다.  ** 11월 5일 종가는 $462.07로 전일 대비 4.01% 상승했습니다 .  === Tesla의 주요 패턴 ===  ==== 박스권 횡보 ($413~$470) ==== # 현재 $413~$470 사이에서 횡보하며 매집 구간 형성 중  # 10월 중순 이후 회복세 지속 # RSI 56,...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
* 📊 Tesla 최근 한 달 주가 분석 (2025.10.6~11.5)&lt;br /&gt;
* 주요 지표&lt;br /&gt;
** 최근 한 달간 Tesla는 최고가 $474.07, 최저가 $411.45로 약 15.2% 변동폭을 보였으며, 평균가는 $442.63입니다. &lt;br /&gt;
** 11월 5일 종가는 $462.07로 전일 대비 4.01% 상승했습니다 .&lt;br /&gt;
&lt;br /&gt;
=== Tesla의 주요 패턴 ===&lt;br /&gt;
&lt;br /&gt;
==== 박스권 횡보 ($413~$470) ====&lt;br /&gt;
# 현재 $413~$470 사이에서 횡보하며 매집 구간 형성 중 &lt;br /&gt;
# 10월 중순 이후 회복세 지속&lt;br /&gt;
# RSI 56, MACD 상승 크로스오버로 기술적 긍정 신호 &lt;br /&gt;
&lt;br /&gt;
==== 변동성 큼 (일일 3-5%) ====&lt;br /&gt;
# 11월 4일 -5.15% 급락 후 11월 5일 +4.01% 반등하는 등 변동성이 큰 편  &lt;br /&gt;
&lt;br /&gt;
==== 주요 리스크 요인 ====&lt;br /&gt;
# 일론 머스크의 1조 달러 보상 패키지 논란으로 주가 압박 &lt;br /&gt;
# 독일, 영국 등 유럽 시장 판매 급감 (-50%) &lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
=== Tesla 1000만원 분할매수 전략 ===&lt;br /&gt;
&lt;br /&gt;
==== 추천 전략: 개량형 3-3-4 전략 ====&lt;br /&gt;
* Tesla는 변동성이 크므로 분할 구간을 넓게 설정해야 합니다.&lt;br /&gt;
&lt;br /&gt;
* 현재가 기준 $460 (약 62만원) 가정*&lt;br /&gt;
&lt;br /&gt;
|차수|하락률    |매수가     |매수금액 |비중 |목적             |&lt;br /&gt;
|--|-------|--------|-----|---|---------------|&lt;br /&gt;
|1차|0%     |$460    |300만원|30%|초기 진입          |&lt;br /&gt;
|2차|-5~6%  |$433~437|300만원|30%|첫 지지선          |&lt;br /&gt;
|3차|-10~12%|$405~414|400만원|40%|강한 지지선 (최저가 근처)|&lt;br /&gt;
&lt;br /&gt;
* 예상 평단가: 약 $425 (원 평단가보다 -7.6% 낮춤)&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
=== 구체적 실행 계획 ===&lt;br /&gt;
==== 진입 시나리오 ====&lt;br /&gt;
# 시나리오 A: 상승 추세 유지&lt;br /&gt;
## $470 돌파 → 관망 (진입 기회 대기)&lt;br /&gt;
## $460~465 조정 → 1차 매수 (300만원)&lt;br /&gt;
## $440 터치 → 2차 매수 (300만원)&lt;br /&gt;
## $410 터치 → 3차 매수 (400만원)&lt;br /&gt;
# 시나리오 B: 하락 진입&lt;br /&gt;
## 현재 $462에서 1차 매수 (300만원)&lt;br /&gt;
## -6% 하락 $434 → 2차 매수 (300만원)&lt;br /&gt;
## -11% 하락 $410 → 3차 매수 (400만원)&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
==== 추가 하락 시 대책 ====&lt;br /&gt;
# -15% 이상 하락 시 ($391 이하)&lt;br /&gt;
## 원인 분석 우선&lt;br /&gt;
##:- 전체 시장 급락인지 Tesla 고유 악재인지 판단&lt;br /&gt;
##:- 52주 최저가 $214.25가 있으므로 큰 하락 가능성 존재 &lt;br /&gt;
# 대응 전략 (3가지 선택)&lt;br /&gt;
## 옵션 1: 추가 매수 (공격형)&lt;br /&gt;
##:- 예비 자금 200만원으로 $380~390에 추가 매수&lt;br /&gt;
##:- 평단가를 더 낮추는 전략&lt;br /&gt;
## 옵션 2: 손절 후 재진입 (중립형)&lt;br /&gt;
##:- -20% 도달 시 일부 손절 ($368)&lt;br /&gt;
##:- 바닥 확인 후 재진입&lt;br /&gt;
##:- *손절라인: -25% ($345)&lt;br /&gt;
## 옵션 3: 장기 보유 (보수형)&lt;br /&gt;
##:- 손절 없이 회복 대기&lt;br /&gt;
##:- Tesla의 펀더멘털 믿고 홀딩&lt;br /&gt;
##:- 평단가 낮추기 위해 DCA(정액 분할 매수) 지속&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
=== Tesla 특화 체크포인트 ===&lt;br /&gt;
==== 매수 전 확인사항 ====&lt;br /&gt;
# 실적 발표 일정 - 실적 전후 급등락 빈번&lt;br /&gt;
# 일론 머스크 트윗 - SNS 발언으로 주가 급변동&lt;br /&gt;
# 경쟁사 동향 - BYD, 중국 전기차 업체 실적&lt;br /&gt;
# 금리 정책 - 성장주 특성상 금리에 민감&lt;br /&gt;
&lt;br /&gt;
==== 손익 목표 ====&lt;br /&gt;
# 1차 익절 : +15% ($529) - 원금 회수&lt;br /&gt;
# 2차 익절 : +30% ($598) - 50% 물량 정리&lt;br /&gt;
# 장기 목표 : $600~650 (기술적 분석상 목표가)&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
=== 핵심 조언 ===&lt;br /&gt;
# Tesla는 변동성이 크므로 분할 매수가 필수&lt;br /&gt;
# 단기 악재에 흔들리지 말 것 - 유럽 판매 부진은 일시적&lt;br /&gt;
# 감정 배제 - 일론 머스크 이슈에 과민반응 금지&lt;br /&gt;
# 예비 자금 20% 확보 - 급락 시 추가 매수용&lt;br /&gt;
# 목표가 도달 시 일부 익절 - 욕심 부리지 않기&lt;br /&gt;
&lt;br /&gt;
* 현재 Tesla는 $470 저항선 돌파 시 $500 목표가 가능한 기술적 구조 이므로, 분할 매수로 리스크를 관리하면서 진입하는 것이 현명합니다.&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%95%B8%EB%93%9C%EC%98%A8%ED%85%8C%EC%9D%B4%EB%B8%94_%EA%B7%B8%EB%A6%AC%EB%93%9C_ORACLE_DB_%EC%97%B0%EA%B2%B0_%EC%98%88%EC%A0%9C&amp;diff=2329</id>
		<title>핸드온테이블 그리드 ORACLE DB 연결 예제</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%95%B8%EB%93%9C%EC%98%A8%ED%85%8C%EC%9D%B4%EB%B8%94_%EA%B7%B8%EB%A6%AC%EB%93%9C_ORACLE_DB_%EC%97%B0%EA%B2%B0_%EC%98%88%EC%A0%9C&amp;diff=2329"/>
		<updated>2025-10-17T07:23:22Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 서버 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
=== 기본 화면 ===&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Handsontable 데이터 그리드&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        * {&lt;br /&gt;
            margin: 0;&lt;br /&gt;
            padding: 0;&lt;br /&gt;
            box-sizing: border-box;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        body {&lt;br /&gt;
            font-family: &#039;Segoe UI&#039;, Tahoma, Geneva, Verdana, sans-serif;&lt;br /&gt;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);&lt;br /&gt;
            padding: 20px;&lt;br /&gt;
            min-height: 100vh;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .container {&lt;br /&gt;
            max-width: 1400px;&lt;br /&gt;
            margin: 0 auto;&lt;br /&gt;
            background: white;&lt;br /&gt;
            border-radius: 15px;&lt;br /&gt;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);&lt;br /&gt;
            padding: 30px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        h1 {&lt;br /&gt;
            color: #333;&lt;br /&gt;
            margin-bottom: 10px;&lt;br /&gt;
            font-size: 28px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .subtitle {&lt;br /&gt;
            color: #666;&lt;br /&gt;
            margin-bottom: 25px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .controls {&lt;br /&gt;
            display: flex;&lt;br /&gt;
            gap: 10px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            flex-wrap: wrap;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button {&lt;br /&gt;
            padding: 10px 20px;&lt;br /&gt;
            border: none;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            cursor: pointer;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
            transition: all 0.3s;&lt;br /&gt;
            color: white;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add {&lt;br /&gt;
            background: #10b981;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add:hover {&lt;br /&gt;
            background: #059669;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove {&lt;br /&gt;
            background: #ef4444;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove:hover {&lt;br /&gt;
            background: #dc2626;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export {&lt;br /&gt;
            background: #3b82f6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export:hover {&lt;br /&gt;
            background: #2563eb;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-clear {&lt;br /&gt;
            background: #f59e0b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-clear:hover {&lt;br /&gt;
            background: #d97706;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        #grid-container {&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            height: 500px;&lt;br /&gt;
            overflow: hidden;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info {&lt;br /&gt;
            margin-top: 20px;&lt;br /&gt;
            padding: 15px;&lt;br /&gt;
            background: #f3f4f6;&lt;br /&gt;
            border-radius: 8px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            color: #374151;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item {&lt;br /&gt;
            margin-bottom: 5px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item strong {&lt;br /&gt;
            color: #1f2937;&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;h1&amp;gt;📊 Handsontable 데이터 그리드&amp;lt;/h1&amp;gt;&lt;br /&gt;
        &amp;lt;p class=&amp;quot;subtitle&amp;quot;&amp;gt;실시간 데이터 편집 및 관리 시스템&amp;lt;/p&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;controls&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-add&amp;quot; onclick=&amp;quot;addRow()&amp;quot;&amp;gt;➕ 행 추가&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-remove&amp;quot; onclick=&amp;quot;removeRow()&amp;quot;&amp;gt;➖ 선택 행 삭제&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-export&amp;quot; onclick=&amp;quot;exportData()&amp;quot;&amp;gt;💾 CSV 내보내기&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-clear&amp;quot; onclick=&amp;quot;clearFilters()&amp;quot;&amp;gt;🔄 필터 초기화&amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;grid-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;info&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;🔍 기능:&amp;lt;/strong&amp;gt; 셀 편집, 정렬, 필터링, 행/열 크기 조정&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;💡 팁:&amp;lt;/strong&amp;gt; 컬럼 헤더를 클릭하여 정렬, 드롭다운에서 필터 선택 가능&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;⌨️ 단축키:&amp;lt;/strong&amp;gt; Ctrl+C(복사), Ctrl+V(붙여넣기), Delete(삭제)&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
        // 샘플 데이터 생성&lt;br /&gt;
        const data = [&lt;br /&gt;
            { id: 1, name: &#039;김철수&#039;, department: &#039;개발팀&#039;, position: &#039;팀장&#039;, salary: 5500, email: &#039;kim@example.com&#039;, joinDate: &#039;2020-01-15&#039; },&lt;br /&gt;
            { id: 2, name: &#039;이영희&#039;, department: &#039;영업팀&#039;, position: &#039;대리&#039;, salary: 4200, email: &#039;lee@example.com&#039;, joinDate: &#039;2021-03-20&#039; },&lt;br /&gt;
            { id: 3, name: &#039;박민수&#039;, department: &#039;개발팀&#039;, position: &#039;과장&#039;, salary: 4800, email: &#039;park@example.com&#039;, joinDate: &#039;2019-07-10&#039; },&lt;br /&gt;
            { id: 4, name: &#039;최지현&#039;, department: &#039;인사팀&#039;, position: &#039;사원&#039;, salary: 3500, email: &#039;choi@example.com&#039;, joinDate: &#039;2022-05-01&#039; },&lt;br /&gt;
            { id: 5, name: &#039;정수진&#039;, department: &#039;마케팅팀&#039;, position: &#039;차장&#039;, salary: 5200, email: &#039;jung@example.com&#039;, joinDate: &#039;2018-11-30&#039; },&lt;br /&gt;
            { id: 6, name: &#039;강동원&#039;, department: &#039;영업팀&#039;, position: &#039;부장&#039;, salary: 6000, email: &#039;kang@example.com&#039;, joinDate: &#039;2017-02-14&#039; },&lt;br /&gt;
            { id: 7, name: &#039;윤서영&#039;, department: &#039;개발팀&#039;, position: &#039;사원&#039;, salary: 3800, email: &#039;yoon@example.com&#039;, joinDate: &#039;2023-01-10&#039; },&lt;br /&gt;
            { id: 8, name: &#039;임하늘&#039;, department: &#039;마케팅팀&#039;, position: &#039;대리&#039;, salary: 4300, email: &#039;lim@example.com&#039;, joinDate: &#039;2021-08-25&#039; },&lt;br /&gt;
            { id: 9, name: &#039;송민호&#039;, department: &#039;인사팀&#039;, position: &#039;과장&#039;, salary: 4900, email: &#039;song@example.com&#039;, joinDate: &#039;2020-06-15&#039; },&lt;br /&gt;
            { id: 10, name: &#039;한지우&#039;, department: &#039;영업팀&#039;, position: &#039;사원&#039;, salary: 3600, email: &#039;han@example.com&#039;, joinDate: &#039;2022-09-01&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        const container = document.getElementById(&#039;grid-container&#039;);&lt;br /&gt;
        &lt;br /&gt;
        const hot = new Handsontable(container, {&lt;br /&gt;
            data: data,&lt;br /&gt;
            colHeaders: [&#039;ID&#039;, &#039;이름&#039;, &#039;부서&#039;, &#039;직급&#039;, &#039;급여(만원)&#039;, &#039;이메일&#039;, &#039;입사일&#039;],&lt;br /&gt;
            columns: [&lt;br /&gt;
                { data: &#039;id&#039;, type: &#039;numeric&#039;, readOnly: true },&lt;br /&gt;
                { data: &#039;name&#039;, type: &#039;text&#039; },&lt;br /&gt;
                { &lt;br /&gt;
                    data: &#039;department&#039;, &lt;br /&gt;
                    type: &#039;dropdown&#039;,&lt;br /&gt;
                    source: [&#039;개발팀&#039;, &#039;영업팀&#039;, &#039;인사팀&#039;, &#039;마케팅팀&#039;, &#039;재무팀&#039;]&lt;br /&gt;
                },&lt;br /&gt;
                { &lt;br /&gt;
                    data: &#039;position&#039;, &lt;br /&gt;
                    type: &#039;dropdown&#039;,&lt;br /&gt;
                    source: [&#039;사원&#039;, &#039;대리&#039;, &#039;과장&#039;, &#039;차장&#039;, &#039;부장&#039;, &#039;팀장&#039;]&lt;br /&gt;
                },&lt;br /&gt;
                { data: &#039;salary&#039;, type: &#039;numeric&#039;, numericFormat: { pattern: &#039;0,0&#039; } },&lt;br /&gt;
                { data: &#039;email&#039;, type: &#039;text&#039; },&lt;br /&gt;
                { data: &#039;joinDate&#039;, type: &#039;date&#039;, dateFormat: &#039;YYYY-MM-DD&#039; }&lt;br /&gt;
            ],&lt;br /&gt;
            rowHeaders: true,&lt;br /&gt;
            width: &#039;100%&#039;,&lt;br /&gt;
            height: 500,&lt;br /&gt;
            licenseKey: &#039;non-commercial-and-evaluation&#039;,&lt;br /&gt;
            dropdownMenu: true,&lt;br /&gt;
            filters: true,&lt;br /&gt;
            columnSorting: true,&lt;br /&gt;
            contextMenu: true,&lt;br /&gt;
            manualColumnResize: true,&lt;br /&gt;
            manualRowResize: true,&lt;br /&gt;
            stretchH: &#039;all&#039;,&lt;br /&gt;
            autoWrapRow: true,&lt;br /&gt;
            autoWrapCol: true,&lt;br /&gt;
            fillHandle: {&lt;br /&gt;
                direction: &#039;vertical&#039;,&lt;br /&gt;
                autoInsertRow: false&lt;br /&gt;
            },&lt;br /&gt;
            language: &#039;ko-KR&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        // 행 추가 함수&lt;br /&gt;
        function addRow() {&lt;br /&gt;
            const newId = hot.countRows() + 1;&lt;br /&gt;
            const newRow = {&lt;br /&gt;
                id: newId,&lt;br /&gt;
                name: &#039;&#039;,&lt;br /&gt;
                department: &#039;개발팀&#039;,&lt;br /&gt;
                position: &#039;사원&#039;,&lt;br /&gt;
                salary: 3000,&lt;br /&gt;
                email: &#039;&#039;,&lt;br /&gt;
                joinDate: new Date().toISOString().split(&#039;T&#039;)[0]&lt;br /&gt;
            };&lt;br /&gt;
            hot.alter(&#039;insert_row_below&#039;, hot.countRows(), 1);&lt;br /&gt;
            hot.setDataAtRow(hot.countRows() - 1, [&lt;br /&gt;
                newRow.id, &lt;br /&gt;
                newRow.name, &lt;br /&gt;
                newRow.department, &lt;br /&gt;
                newRow.position, &lt;br /&gt;
                newRow.salary, &lt;br /&gt;
                newRow.email, &lt;br /&gt;
                newRow.joinDate&lt;br /&gt;
            ]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 선택 행 삭제 함수&lt;br /&gt;
        function removeRow() {&lt;br /&gt;
            const selected = hot.getSelected();&lt;br /&gt;
            if (selected &amp;amp;&amp;amp; selected.length &amp;gt; 0) {&lt;br /&gt;
                const startRow = selected[0][0];&lt;br /&gt;
                const endRow = selected[0][2];&lt;br /&gt;
                const rowCount = endRow - startRow + 1;&lt;br /&gt;
                &lt;br /&gt;
                if (confirm(`선택된 ${rowCount}개의 행을 삭제하시겠습니까?`)) {&lt;br /&gt;
                    hot.alter(&#039;remove_row&#039;, startRow, rowCount);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                alert(&#039;삭제할 행을 선택해주세요.&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // CSV 내보내기 함수&lt;br /&gt;
        function exportData() {&lt;br /&gt;
            const exportPlugin = hot.getPlugin(&#039;exportFile&#039;);&lt;br /&gt;
            exportPlugin.downloadFile(&#039;csv&#039;, {&lt;br /&gt;
                bom: true,&lt;br /&gt;
                columnDelimiter: &#039;,&#039;,&lt;br /&gt;
                columnHeaders: true,&lt;br /&gt;
                exportHiddenColumns: false,&lt;br /&gt;
                exportHiddenRows: false,&lt;br /&gt;
                fileExtension: &#039;csv&#039;,&lt;br /&gt;
                filename: &#039;employee_data_[YYYY]-[MM]-[DD]&#039;,&lt;br /&gt;
                mimeType: &#039;text/csv&#039;,&lt;br /&gt;
                rowDelimiter: &#039;\r\n&#039;,&lt;br /&gt;
                rowHeaders: false&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 필터 초기화 함수&lt;br /&gt;
        function clearFilters() {&lt;br /&gt;
            const filtersPlugin = hot.getPlugin(&#039;filters&#039;);&lt;br /&gt;
            filtersPlugin.clearConditions();&lt;br /&gt;
            filtersPlugin.filter();&lt;br /&gt;
            hot.render();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 변경 이벤트&lt;br /&gt;
        hot.addHook(&#039;afterChange&#039;, function(changes, source) {&lt;br /&gt;
            if (source !== &#039;loadData&#039;) {&lt;br /&gt;
                console.log(&#039;데이터 변경:&#039;, changes);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Flask + 오라클 연결하여 데이터 조회/입력/삭제/수정 ===&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Oracle DB 연동 데이터 그리드&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        * {&lt;br /&gt;
            margin: 0;&lt;br /&gt;
            padding: 0;&lt;br /&gt;
            box-sizing: border-box;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        body {&lt;br /&gt;
            font-family: &#039;Segoe UI&#039;, Tahoma, Geneva, Verdana, sans-serif;&lt;br /&gt;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);&lt;br /&gt;
            padding: 20px;&lt;br /&gt;
            min-height: 100vh;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .container {&lt;br /&gt;
            max-width: 1400px;&lt;br /&gt;
            margin: 0 auto;&lt;br /&gt;
            background: white;&lt;br /&gt;
            border-radius: 15px;&lt;br /&gt;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);&lt;br /&gt;
            padding: 30px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        h1 {&lt;br /&gt;
            color: #333;&lt;br /&gt;
            margin-bottom: 10px;&lt;br /&gt;
            font-size: 28px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .subtitle {&lt;br /&gt;
            color: #666;&lt;br /&gt;
            margin-bottom: 25px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status {&lt;br /&gt;
            padding: 10px 15px;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status.success {&lt;br /&gt;
            background: #d1fae5;&lt;br /&gt;
            color: #065f46;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status.error {&lt;br /&gt;
            background: #fee2e2;&lt;br /&gt;
            color: #991b1b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status.info {&lt;br /&gt;
            background: #dbeafe;&lt;br /&gt;
            color: #1e40af;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .controls {&lt;br /&gt;
            display: flex;&lt;br /&gt;
            gap: 10px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            flex-wrap: wrap;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button {&lt;br /&gt;
            padding: 10px 20px;&lt;br /&gt;
            border: none;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            cursor: pointer;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
            transition: all 0.3s;&lt;br /&gt;
            color: white;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button:disabled {&lt;br /&gt;
            opacity: 0.5;&lt;br /&gt;
            cursor: not-allowed;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-load {&lt;br /&gt;
            background: #8b5cf6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-load:hover:not(:disabled) {&lt;br /&gt;
            background: #7c3aed;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-save {&lt;br /&gt;
            background: #10b981;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-save:hover:not(:disabled) {&lt;br /&gt;
            background: #059669;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add {&lt;br /&gt;
            background: #3b82f6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add:hover:not(:disabled) {&lt;br /&gt;
            background: #2563eb;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove {&lt;br /&gt;
            background: #ef4444;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove:hover:not(:disabled) {&lt;br /&gt;
            background: #dc2626;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export {&lt;br /&gt;
            background: #f59e0b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export:hover:not(:disabled) {&lt;br /&gt;
            background: #d97706;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        #grid-container {&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            height: 500px;&lt;br /&gt;
            overflow: hidden;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info {&lt;br /&gt;
            margin-top: 20px;&lt;br /&gt;
            padding: 15px;&lt;br /&gt;
            background: #f3f4f6;&lt;br /&gt;
            border-radius: 8px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            color: #374151;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item {&lt;br /&gt;
            margin-bottom: 5px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item strong {&lt;br /&gt;
            color: #1f2937;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .loading {&lt;br /&gt;
            display: inline-block;&lt;br /&gt;
            width: 20px;&lt;br /&gt;
            height: 20px;&lt;br /&gt;
            border: 3px solid rgba(255,255,255,.3);&lt;br /&gt;
            border-radius: 50%;&lt;br /&gt;
            border-top-color: #fff;&lt;br /&gt;
            animation: spin 1s ease-in-out infinite;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        @keyframes spin {&lt;br /&gt;
            to { transform: rotate(360deg); }&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;h1&amp;gt;🗄️ Oracle Database 연동 데이터 그리드&amp;lt;/h1&amp;gt;&lt;br /&gt;
        &amp;lt;p class=&amp;quot;subtitle&amp;quot;&amp;gt;실시간 CRUD 작업 지원 시스템&amp;lt;/p&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;status&amp;quot; class=&amp;quot;status info&amp;quot; style=&amp;quot;display: none;&amp;quot;&amp;gt;&lt;br /&gt;
            연결 상태를 확인하는 중...&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;controls&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-load&amp;quot; onclick=&amp;quot;loadData()&amp;quot;&amp;gt;&lt;br /&gt;
                🔄 데이터 불러오기&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-save&amp;quot; onclick=&amp;quot;saveChanges()&amp;quot; id=&amp;quot;saveBtn&amp;quot; disabled&amp;gt;&lt;br /&gt;
                💾 변경사항 저장&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-add&amp;quot; onclick=&amp;quot;addRow()&amp;quot; id=&amp;quot;addBtn&amp;quot;&amp;gt;&lt;br /&gt;
                ➕ 행 추가&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-remove&amp;quot; onclick=&amp;quot;removeRow()&amp;quot; id=&amp;quot;removeBtn&amp;quot;&amp;gt;&lt;br /&gt;
                ➖ 선택 행 삭제&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-export&amp;quot; onclick=&amp;quot;exportData()&amp;quot;&amp;gt;&lt;br /&gt;
                📥 CSV 내보내기&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;grid-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;info&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;🔌 연결:&amp;lt;/strong&amp;gt; Flask 백엔드(http://localhost:5000)를 통해 Oracle DB 연결&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;✏️ 편집:&amp;lt;/strong&amp;gt; 셀을 더블클릭하여 직접 수정 가능&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;💾 저장:&amp;lt;/strong&amp;gt; 변경 후 &#039;변경사항 저장&#039; 버튼 클릭으로 DB 반영&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;⚠️ 주의:&amp;lt;/strong&amp;gt; Flask 서버가 실행 중이어야 합니다 (python app.py)&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
        const API_URL = &#039;http://localhost:5000/api&#039;;&lt;br /&gt;
        let hot;&lt;br /&gt;
        let changedData = [];&lt;br /&gt;
        let originalData = [];&lt;br /&gt;
&lt;br /&gt;
        // Handsontable 초기화&lt;br /&gt;
        function initHandsontable() {&lt;br /&gt;
            const container = document.getElementById(&#039;grid-container&#039;);&lt;br /&gt;
            &lt;br /&gt;
            hot = new Handsontable(container, {&lt;br /&gt;
                data: [],&lt;br /&gt;
                colHeaders: [&#039;ID&#039;, &#039;이름&#039;, &#039;부서&#039;, &#039;직급&#039;, &#039;급여(만원)&#039;, &#039;이메일&#039;, &#039;입사일&#039;],&lt;br /&gt;
                columns: [&lt;br /&gt;
                    { data: &#039;id&#039;, type: &#039;numeric&#039;, readOnly: true },&lt;br /&gt;
                    { data: &#039;name&#039;, type: &#039;text&#039; },&lt;br /&gt;
                    { &lt;br /&gt;
                        data: &#039;department&#039;, &lt;br /&gt;
                        type: &#039;dropdown&#039;,&lt;br /&gt;
                        source: [&#039;개발팀&#039;, &#039;영업팀&#039;, &#039;인사팀&#039;, &#039;마케팅팀&#039;, &#039;재무팀&#039;]&lt;br /&gt;
                    },&lt;br /&gt;
                    { &lt;br /&gt;
                        data: &#039;position&#039;, &lt;br /&gt;
                        type: &#039;dropdown&#039;,&lt;br /&gt;
                        source: [&#039;사원&#039;, &#039;대리&#039;, &#039;과장&#039;, &#039;차장&#039;, &#039;부장&#039;, &#039;팀장&#039;]&lt;br /&gt;
                    },&lt;br /&gt;
                    { data: &#039;salary&#039;, type: &#039;numeric&#039;, numericFormat: { pattern: &#039;0,0.00&#039; } },&lt;br /&gt;
                    { data: &#039;email&#039;, type: &#039;text&#039; },&lt;br /&gt;
                    { data: &#039;join_date&#039;, type: &#039;date&#039;, dateFormat: &#039;YYYY-MM-DD&#039; }&lt;br /&gt;
                ],&lt;br /&gt;
                rowHeaders: true,&lt;br /&gt;
                width: &#039;100%&#039;,&lt;br /&gt;
                height: 500,&lt;br /&gt;
                licenseKey: &#039;non-commercial-and-evaluation&#039;,&lt;br /&gt;
                dropdownMenu: true,&lt;br /&gt;
                filters: true,&lt;br /&gt;
                columnSorting: true,&lt;br /&gt;
                contextMenu: true,&lt;br /&gt;
                manualColumnResize: true,&lt;br /&gt;
                manualRowResize: true,&lt;br /&gt;
                stretchH: &#039;all&#039;,&lt;br /&gt;
                afterChange: onDataChange,&lt;br /&gt;
                language: &#039;ko-KR&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 변경 감지&lt;br /&gt;
        function onDataChange(changes, source) {&lt;br /&gt;
            if (source === &#039;loadData&#039; || !changes) return;&lt;br /&gt;
            &lt;br /&gt;
            changes.forEach(([row, prop, oldValue, newValue]) =&amp;gt; {&lt;br /&gt;
                if (oldValue !== newValue) {&lt;br /&gt;
                    const rowData = hot.getSourceDataAtRow(row);&lt;br /&gt;
                    changedData.push({&lt;br /&gt;
                        id: rowData.id,&lt;br /&gt;
                        field: prop,&lt;br /&gt;
                        value: newValue&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            &lt;br /&gt;
            if (changedData.length &amp;gt; 0) {&lt;br /&gt;
                document.getElementById(&#039;saveBtn&#039;).disabled = false;&lt;br /&gt;
                showStatus(&#039;변경사항이 있습니다. 저장 버튼을 클릭하세요.&#039;, &#039;info&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 상태 메시지 표시&lt;br /&gt;
        function showStatus(message, type = &#039;info&#039;) {&lt;br /&gt;
            const statusEl = document.getElementById(&#039;status&#039;);&lt;br /&gt;
            statusEl.textContent = message;&lt;br /&gt;
            statusEl.className = `status ${type}`;&lt;br /&gt;
            statusEl.style.display = &#039;block&#039;;&lt;br /&gt;
            &lt;br /&gt;
            if (type === &#039;success&#039; || type === &#039;error&#039;) {&lt;br /&gt;
                setTimeout(() =&amp;gt; {&lt;br /&gt;
                    statusEl.style.display = &#039;none&#039;;&lt;br /&gt;
                }, 5000);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 불러오기&lt;br /&gt;
        async function loadData() {&lt;br /&gt;
            showStatus(&#039;데이터를 불러오는 중...&#039;, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                const response = await fetch(`${API_URL}/employees`);&lt;br /&gt;
                &lt;br /&gt;
                if (!response.ok) {&lt;br /&gt;
                    throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                const data = await response.json();&lt;br /&gt;
                originalData = JSON.parse(JSON.stringify(data));&lt;br /&gt;
                hot.loadData(data);&lt;br /&gt;
                changedData = [];&lt;br /&gt;
                document.getElementById(&#039;saveBtn&#039;).disabled = true;&lt;br /&gt;
                &lt;br /&gt;
                showStatus(`${data.length}개의 레코드를 불러왔습니다.`, &#039;success&#039;);&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`오류: ${error.message}. Flask 서버가 실행 중인지 확인하세요.`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;데이터 로드 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 변경사항 저장&lt;br /&gt;
        async function saveChanges() {&lt;br /&gt;
            if (changedData.length === 0) {&lt;br /&gt;
                showStatus(&#039;저장할 변경사항이 없습니다.&#039;, &#039;info&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            showStatus(`${changedData.length}개의 변경사항을 저장하는 중...`, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                const response = await fetch(`${API_URL}/employees/batch`, {&lt;br /&gt;
                    method: &#039;POST&#039;,&lt;br /&gt;
                    headers: {&lt;br /&gt;
                        &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                    },&lt;br /&gt;
                    body: JSON.stringify({ changes: changedData })&lt;br /&gt;
                });&lt;br /&gt;
                &lt;br /&gt;
                if (!response.ok) {&lt;br /&gt;
                    throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                const result = await response.json();&lt;br /&gt;
                showStatus(result.message, &#039;success&#039;);&lt;br /&gt;
                changedData = [];&lt;br /&gt;
                document.getElementById(&#039;saveBtn&#039;).disabled = true;&lt;br /&gt;
                &lt;br /&gt;
                // 데이터 새로고침&lt;br /&gt;
                await loadData();&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`저장 오류: ${error.message}`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;저장 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 행 추가&lt;br /&gt;
        async function addRow() {&lt;br /&gt;
            const newEmployee = {&lt;br /&gt;
                name: &#039;새 직원&#039;,&lt;br /&gt;
                department: &#039;개발팀&#039;,&lt;br /&gt;
                position: &#039;사원&#039;,&lt;br /&gt;
                salary: 3000,&lt;br /&gt;
                email: &#039;&#039;,&lt;br /&gt;
                join_date: new Date().toISOString().split(&#039;T&#039;)[0]&lt;br /&gt;
            };&lt;br /&gt;
            &lt;br /&gt;
            showStatus(&#039;새 직원을 추가하는 중...&#039;, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                const response = await fetch(`${API_URL}/employees`, {&lt;br /&gt;
                    method: &#039;POST&#039;,&lt;br /&gt;
                    headers: {&lt;br /&gt;
                        &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                    },&lt;br /&gt;
                    body: JSON.stringify(newEmployee)&lt;br /&gt;
                });&lt;br /&gt;
                &lt;br /&gt;
                if (!response.ok) {&lt;br /&gt;
                    throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                const result = await response.json();&lt;br /&gt;
                showStatus(result.message, &#039;success&#039;);&lt;br /&gt;
                &lt;br /&gt;
                // 데이터 새로고침&lt;br /&gt;
                await loadData();&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`추가 오류: ${error.message}`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;추가 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 선택 행 삭제&lt;br /&gt;
        async function removeRow() {&lt;br /&gt;
            const selected = hot.getSelected();&lt;br /&gt;
            if (!selected || selected.length === 0) {&lt;br /&gt;
                showStatus(&#039;삭제할 행을 선택해주세요.&#039;, &#039;error&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            const startRow = selected[0][0];&lt;br /&gt;
            const endRow = selected[0][2];&lt;br /&gt;
            const rowsToDelete = [];&lt;br /&gt;
            &lt;br /&gt;
            for (let i = startRow; i &amp;lt;= endRow; i++) {&lt;br /&gt;
                const rowData = hot.getSourceDataAtRow(i);&lt;br /&gt;
                if (rowData &amp;amp;&amp;amp; rowData.id) {&lt;br /&gt;
                    rowsToDelete.push(rowData.id);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            if (rowsToDelete.length === 0) {&lt;br /&gt;
                showStatus(&#039;삭제할 유효한 행이 없습니다.&#039;, &#039;error&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            if (!confirm(`선택된 ${rowsToDelete.length}개의 행을 삭제하시겠습니까?`)) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            showStatus(`${rowsToDelete.length}개의 행을 삭제하는 중...`, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                for (const id of rowsToDelete) {&lt;br /&gt;
                    const response = await fetch(`${API_URL}/employees/${id}`, {&lt;br /&gt;
                        method: &#039;DELETE&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                    &lt;br /&gt;
                    if (!response.ok) {&lt;br /&gt;
                        throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                showStatus(`${rowsToDelete.length}개의 행이 삭제되었습니다.`, &#039;success&#039;);&lt;br /&gt;
                &lt;br /&gt;
                // 데이터 새로고침&lt;br /&gt;
                await loadData();&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`삭제 오류: ${error.message}`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;삭제 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // CSV 내보내기&lt;br /&gt;
        function exportData() {&lt;br /&gt;
            const exportPlugin = hot.getPlugin(&#039;exportFile&#039;);&lt;br /&gt;
            exportPlugin.downloadFile(&#039;csv&#039;, {&lt;br /&gt;
                bom: true,&lt;br /&gt;
                columnDelimiter: &#039;,&#039;,&lt;br /&gt;
                columnHeaders: true,&lt;br /&gt;
                exportHiddenColumns: false,&lt;br /&gt;
                exportHiddenRows: false,&lt;br /&gt;
                fileExtension: &#039;csv&#039;,&lt;br /&gt;
                filename: &#039;employees_[YYYY]-[MM]-[DD]&#039;,&lt;br /&gt;
                mimeType: &#039;text/csv&#039;,&lt;br /&gt;
                rowDelimiter: &#039;\r\n&#039;,&lt;br /&gt;
                rowHeaders: false&lt;br /&gt;
            });&lt;br /&gt;
            &lt;br /&gt;
            showStatus(&#039;CSV 파일이 다운로드되었습니다.&#039;, &#039;success&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 페이지 로드 시 초기화&lt;br /&gt;
        window.onload = function() {&lt;br /&gt;
            initHandsontable();&lt;br /&gt;
            loadData();&lt;br /&gt;
        };&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%95%B8%EB%93%9C%EC%98%A8%ED%85%8C%EC%9D%B4%EB%B8%94_%EA%B7%B8%EB%A6%AC%EB%93%9C_ORACLE_DB_%EC%97%B0%EA%B2%B0_%EC%98%88%EC%A0%9C&amp;diff=2328</id>
		<title>핸드온테이블 그리드 ORACLE DB 연결 예제</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%95%B8%EB%93%9C%EC%98%A8%ED%85%8C%EC%9D%B4%EB%B8%94_%EA%B7%B8%EB%A6%AC%EB%93%9C_ORACLE_DB_%EC%97%B0%EA%B2%B0_%EC%98%88%EC%A0%9C&amp;diff=2328"/>
		<updated>2025-10-17T07:22:19Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 화면 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
=== 기본 화면 ===&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Handsontable 데이터 그리드&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        * {&lt;br /&gt;
            margin: 0;&lt;br /&gt;
            padding: 0;&lt;br /&gt;
            box-sizing: border-box;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        body {&lt;br /&gt;
            font-family: &#039;Segoe UI&#039;, Tahoma, Geneva, Verdana, sans-serif;&lt;br /&gt;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);&lt;br /&gt;
            padding: 20px;&lt;br /&gt;
            min-height: 100vh;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .container {&lt;br /&gt;
            max-width: 1400px;&lt;br /&gt;
            margin: 0 auto;&lt;br /&gt;
            background: white;&lt;br /&gt;
            border-radius: 15px;&lt;br /&gt;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);&lt;br /&gt;
            padding: 30px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        h1 {&lt;br /&gt;
            color: #333;&lt;br /&gt;
            margin-bottom: 10px;&lt;br /&gt;
            font-size: 28px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .subtitle {&lt;br /&gt;
            color: #666;&lt;br /&gt;
            margin-bottom: 25px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .controls {&lt;br /&gt;
            display: flex;&lt;br /&gt;
            gap: 10px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            flex-wrap: wrap;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button {&lt;br /&gt;
            padding: 10px 20px;&lt;br /&gt;
            border: none;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            cursor: pointer;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
            transition: all 0.3s;&lt;br /&gt;
            color: white;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add {&lt;br /&gt;
            background: #10b981;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add:hover {&lt;br /&gt;
            background: #059669;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove {&lt;br /&gt;
            background: #ef4444;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove:hover {&lt;br /&gt;
            background: #dc2626;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export {&lt;br /&gt;
            background: #3b82f6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export:hover {&lt;br /&gt;
            background: #2563eb;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-clear {&lt;br /&gt;
            background: #f59e0b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-clear:hover {&lt;br /&gt;
            background: #d97706;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        #grid-container {&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            height: 500px;&lt;br /&gt;
            overflow: hidden;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info {&lt;br /&gt;
            margin-top: 20px;&lt;br /&gt;
            padding: 15px;&lt;br /&gt;
            background: #f3f4f6;&lt;br /&gt;
            border-radius: 8px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            color: #374151;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item {&lt;br /&gt;
            margin-bottom: 5px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item strong {&lt;br /&gt;
            color: #1f2937;&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;h1&amp;gt;📊 Handsontable 데이터 그리드&amp;lt;/h1&amp;gt;&lt;br /&gt;
        &amp;lt;p class=&amp;quot;subtitle&amp;quot;&amp;gt;실시간 데이터 편집 및 관리 시스템&amp;lt;/p&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;controls&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-add&amp;quot; onclick=&amp;quot;addRow()&amp;quot;&amp;gt;➕ 행 추가&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-remove&amp;quot; onclick=&amp;quot;removeRow()&amp;quot;&amp;gt;➖ 선택 행 삭제&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-export&amp;quot; onclick=&amp;quot;exportData()&amp;quot;&amp;gt;💾 CSV 내보내기&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-clear&amp;quot; onclick=&amp;quot;clearFilters()&amp;quot;&amp;gt;🔄 필터 초기화&amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;grid-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;info&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;🔍 기능:&amp;lt;/strong&amp;gt; 셀 편집, 정렬, 필터링, 행/열 크기 조정&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;💡 팁:&amp;lt;/strong&amp;gt; 컬럼 헤더를 클릭하여 정렬, 드롭다운에서 필터 선택 가능&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;⌨️ 단축키:&amp;lt;/strong&amp;gt; Ctrl+C(복사), Ctrl+V(붙여넣기), Delete(삭제)&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
        // 샘플 데이터 생성&lt;br /&gt;
        const data = [&lt;br /&gt;
            { id: 1, name: &#039;김철수&#039;, department: &#039;개발팀&#039;, position: &#039;팀장&#039;, salary: 5500, email: &#039;kim@example.com&#039;, joinDate: &#039;2020-01-15&#039; },&lt;br /&gt;
            { id: 2, name: &#039;이영희&#039;, department: &#039;영업팀&#039;, position: &#039;대리&#039;, salary: 4200, email: &#039;lee@example.com&#039;, joinDate: &#039;2021-03-20&#039; },&lt;br /&gt;
            { id: 3, name: &#039;박민수&#039;, department: &#039;개발팀&#039;, position: &#039;과장&#039;, salary: 4800, email: &#039;park@example.com&#039;, joinDate: &#039;2019-07-10&#039; },&lt;br /&gt;
            { id: 4, name: &#039;최지현&#039;, department: &#039;인사팀&#039;, position: &#039;사원&#039;, salary: 3500, email: &#039;choi@example.com&#039;, joinDate: &#039;2022-05-01&#039; },&lt;br /&gt;
            { id: 5, name: &#039;정수진&#039;, department: &#039;마케팅팀&#039;, position: &#039;차장&#039;, salary: 5200, email: &#039;jung@example.com&#039;, joinDate: &#039;2018-11-30&#039; },&lt;br /&gt;
            { id: 6, name: &#039;강동원&#039;, department: &#039;영업팀&#039;, position: &#039;부장&#039;, salary: 6000, email: &#039;kang@example.com&#039;, joinDate: &#039;2017-02-14&#039; },&lt;br /&gt;
            { id: 7, name: &#039;윤서영&#039;, department: &#039;개발팀&#039;, position: &#039;사원&#039;, salary: 3800, email: &#039;yoon@example.com&#039;, joinDate: &#039;2023-01-10&#039; },&lt;br /&gt;
            { id: 8, name: &#039;임하늘&#039;, department: &#039;마케팅팀&#039;, position: &#039;대리&#039;, salary: 4300, email: &#039;lim@example.com&#039;, joinDate: &#039;2021-08-25&#039; },&lt;br /&gt;
            { id: 9, name: &#039;송민호&#039;, department: &#039;인사팀&#039;, position: &#039;과장&#039;, salary: 4900, email: &#039;song@example.com&#039;, joinDate: &#039;2020-06-15&#039; },&lt;br /&gt;
            { id: 10, name: &#039;한지우&#039;, department: &#039;영업팀&#039;, position: &#039;사원&#039;, salary: 3600, email: &#039;han@example.com&#039;, joinDate: &#039;2022-09-01&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        const container = document.getElementById(&#039;grid-container&#039;);&lt;br /&gt;
        &lt;br /&gt;
        const hot = new Handsontable(container, {&lt;br /&gt;
            data: data,&lt;br /&gt;
            colHeaders: [&#039;ID&#039;, &#039;이름&#039;, &#039;부서&#039;, &#039;직급&#039;, &#039;급여(만원)&#039;, &#039;이메일&#039;, &#039;입사일&#039;],&lt;br /&gt;
            columns: [&lt;br /&gt;
                { data: &#039;id&#039;, type: &#039;numeric&#039;, readOnly: true },&lt;br /&gt;
                { data: &#039;name&#039;, type: &#039;text&#039; },&lt;br /&gt;
                { &lt;br /&gt;
                    data: &#039;department&#039;, &lt;br /&gt;
                    type: &#039;dropdown&#039;,&lt;br /&gt;
                    source: [&#039;개발팀&#039;, &#039;영업팀&#039;, &#039;인사팀&#039;, &#039;마케팅팀&#039;, &#039;재무팀&#039;]&lt;br /&gt;
                },&lt;br /&gt;
                { &lt;br /&gt;
                    data: &#039;position&#039;, &lt;br /&gt;
                    type: &#039;dropdown&#039;,&lt;br /&gt;
                    source: [&#039;사원&#039;, &#039;대리&#039;, &#039;과장&#039;, &#039;차장&#039;, &#039;부장&#039;, &#039;팀장&#039;]&lt;br /&gt;
                },&lt;br /&gt;
                { data: &#039;salary&#039;, type: &#039;numeric&#039;, numericFormat: { pattern: &#039;0,0&#039; } },&lt;br /&gt;
                { data: &#039;email&#039;, type: &#039;text&#039; },&lt;br /&gt;
                { data: &#039;joinDate&#039;, type: &#039;date&#039;, dateFormat: &#039;YYYY-MM-DD&#039; }&lt;br /&gt;
            ],&lt;br /&gt;
            rowHeaders: true,&lt;br /&gt;
            width: &#039;100%&#039;,&lt;br /&gt;
            height: 500,&lt;br /&gt;
            licenseKey: &#039;non-commercial-and-evaluation&#039;,&lt;br /&gt;
            dropdownMenu: true,&lt;br /&gt;
            filters: true,&lt;br /&gt;
            columnSorting: true,&lt;br /&gt;
            contextMenu: true,&lt;br /&gt;
            manualColumnResize: true,&lt;br /&gt;
            manualRowResize: true,&lt;br /&gt;
            stretchH: &#039;all&#039;,&lt;br /&gt;
            autoWrapRow: true,&lt;br /&gt;
            autoWrapCol: true,&lt;br /&gt;
            fillHandle: {&lt;br /&gt;
                direction: &#039;vertical&#039;,&lt;br /&gt;
                autoInsertRow: false&lt;br /&gt;
            },&lt;br /&gt;
            language: &#039;ko-KR&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        // 행 추가 함수&lt;br /&gt;
        function addRow() {&lt;br /&gt;
            const newId = hot.countRows() + 1;&lt;br /&gt;
            const newRow = {&lt;br /&gt;
                id: newId,&lt;br /&gt;
                name: &#039;&#039;,&lt;br /&gt;
                department: &#039;개발팀&#039;,&lt;br /&gt;
                position: &#039;사원&#039;,&lt;br /&gt;
                salary: 3000,&lt;br /&gt;
                email: &#039;&#039;,&lt;br /&gt;
                joinDate: new Date().toISOString().split(&#039;T&#039;)[0]&lt;br /&gt;
            };&lt;br /&gt;
            hot.alter(&#039;insert_row_below&#039;, hot.countRows(), 1);&lt;br /&gt;
            hot.setDataAtRow(hot.countRows() - 1, [&lt;br /&gt;
                newRow.id, &lt;br /&gt;
                newRow.name, &lt;br /&gt;
                newRow.department, &lt;br /&gt;
                newRow.position, &lt;br /&gt;
                newRow.salary, &lt;br /&gt;
                newRow.email, &lt;br /&gt;
                newRow.joinDate&lt;br /&gt;
            ]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 선택 행 삭제 함수&lt;br /&gt;
        function removeRow() {&lt;br /&gt;
            const selected = hot.getSelected();&lt;br /&gt;
            if (selected &amp;amp;&amp;amp; selected.length &amp;gt; 0) {&lt;br /&gt;
                const startRow = selected[0][0];&lt;br /&gt;
                const endRow = selected[0][2];&lt;br /&gt;
                const rowCount = endRow - startRow + 1;&lt;br /&gt;
                &lt;br /&gt;
                if (confirm(`선택된 ${rowCount}개의 행을 삭제하시겠습니까?`)) {&lt;br /&gt;
                    hot.alter(&#039;remove_row&#039;, startRow, rowCount);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                alert(&#039;삭제할 행을 선택해주세요.&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // CSV 내보내기 함수&lt;br /&gt;
        function exportData() {&lt;br /&gt;
            const exportPlugin = hot.getPlugin(&#039;exportFile&#039;);&lt;br /&gt;
            exportPlugin.downloadFile(&#039;csv&#039;, {&lt;br /&gt;
                bom: true,&lt;br /&gt;
                columnDelimiter: &#039;,&#039;,&lt;br /&gt;
                columnHeaders: true,&lt;br /&gt;
                exportHiddenColumns: false,&lt;br /&gt;
                exportHiddenRows: false,&lt;br /&gt;
                fileExtension: &#039;csv&#039;,&lt;br /&gt;
                filename: &#039;employee_data_[YYYY]-[MM]-[DD]&#039;,&lt;br /&gt;
                mimeType: &#039;text/csv&#039;,&lt;br /&gt;
                rowDelimiter: &#039;\r\n&#039;,&lt;br /&gt;
                rowHeaders: false&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 필터 초기화 함수&lt;br /&gt;
        function clearFilters() {&lt;br /&gt;
            const filtersPlugin = hot.getPlugin(&#039;filters&#039;);&lt;br /&gt;
            filtersPlugin.clearConditions();&lt;br /&gt;
            filtersPlugin.filter();&lt;br /&gt;
            hot.render();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 변경 이벤트&lt;br /&gt;
        hot.addHook(&#039;afterChange&#039;, function(changes, source) {&lt;br /&gt;
            if (source !== &#039;loadData&#039;) {&lt;br /&gt;
                console.log(&#039;데이터 변경:&#039;, changes);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 서버 ===&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Oracle DB 연동 데이터 그리드&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        * {&lt;br /&gt;
            margin: 0;&lt;br /&gt;
            padding: 0;&lt;br /&gt;
            box-sizing: border-box;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        body {&lt;br /&gt;
            font-family: &#039;Segoe UI&#039;, Tahoma, Geneva, Verdana, sans-serif;&lt;br /&gt;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);&lt;br /&gt;
            padding: 20px;&lt;br /&gt;
            min-height: 100vh;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .container {&lt;br /&gt;
            max-width: 1400px;&lt;br /&gt;
            margin: 0 auto;&lt;br /&gt;
            background: white;&lt;br /&gt;
            border-radius: 15px;&lt;br /&gt;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);&lt;br /&gt;
            padding: 30px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        h1 {&lt;br /&gt;
            color: #333;&lt;br /&gt;
            margin-bottom: 10px;&lt;br /&gt;
            font-size: 28px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .subtitle {&lt;br /&gt;
            color: #666;&lt;br /&gt;
            margin-bottom: 25px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status {&lt;br /&gt;
            padding: 10px 15px;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status.success {&lt;br /&gt;
            background: #d1fae5;&lt;br /&gt;
            color: #065f46;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status.error {&lt;br /&gt;
            background: #fee2e2;&lt;br /&gt;
            color: #991b1b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status.info {&lt;br /&gt;
            background: #dbeafe;&lt;br /&gt;
            color: #1e40af;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .controls {&lt;br /&gt;
            display: flex;&lt;br /&gt;
            gap: 10px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            flex-wrap: wrap;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button {&lt;br /&gt;
            padding: 10px 20px;&lt;br /&gt;
            border: none;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            cursor: pointer;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
            transition: all 0.3s;&lt;br /&gt;
            color: white;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button:disabled {&lt;br /&gt;
            opacity: 0.5;&lt;br /&gt;
            cursor: not-allowed;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-load {&lt;br /&gt;
            background: #8b5cf6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-load:hover:not(:disabled) {&lt;br /&gt;
            background: #7c3aed;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-save {&lt;br /&gt;
            background: #10b981;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-save:hover:not(:disabled) {&lt;br /&gt;
            background: #059669;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add {&lt;br /&gt;
            background: #3b82f6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add:hover:not(:disabled) {&lt;br /&gt;
            background: #2563eb;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove {&lt;br /&gt;
            background: #ef4444;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove:hover:not(:disabled) {&lt;br /&gt;
            background: #dc2626;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export {&lt;br /&gt;
            background: #f59e0b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export:hover:not(:disabled) {&lt;br /&gt;
            background: #d97706;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        #grid-container {&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            height: 500px;&lt;br /&gt;
            overflow: hidden;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info {&lt;br /&gt;
            margin-top: 20px;&lt;br /&gt;
            padding: 15px;&lt;br /&gt;
            background: #f3f4f6;&lt;br /&gt;
            border-radius: 8px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            color: #374151;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item {&lt;br /&gt;
            margin-bottom: 5px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item strong {&lt;br /&gt;
            color: #1f2937;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .loading {&lt;br /&gt;
            display: inline-block;&lt;br /&gt;
            width: 20px;&lt;br /&gt;
            height: 20px;&lt;br /&gt;
            border: 3px solid rgba(255,255,255,.3);&lt;br /&gt;
            border-radius: 50%;&lt;br /&gt;
            border-top-color: #fff;&lt;br /&gt;
            animation: spin 1s ease-in-out infinite;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        @keyframes spin {&lt;br /&gt;
            to { transform: rotate(360deg); }&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;h1&amp;gt;🗄️ Oracle Database 연동 데이터 그리드&amp;lt;/h1&amp;gt;&lt;br /&gt;
        &amp;lt;p class=&amp;quot;subtitle&amp;quot;&amp;gt;실시간 CRUD 작업 지원 시스템&amp;lt;/p&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;status&amp;quot; class=&amp;quot;status info&amp;quot; style=&amp;quot;display: none;&amp;quot;&amp;gt;&lt;br /&gt;
            연결 상태를 확인하는 중...&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;controls&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-load&amp;quot; onclick=&amp;quot;loadData()&amp;quot;&amp;gt;&lt;br /&gt;
                🔄 데이터 불러오기&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-save&amp;quot; onclick=&amp;quot;saveChanges()&amp;quot; id=&amp;quot;saveBtn&amp;quot; disabled&amp;gt;&lt;br /&gt;
                💾 변경사항 저장&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-add&amp;quot; onclick=&amp;quot;addRow()&amp;quot; id=&amp;quot;addBtn&amp;quot;&amp;gt;&lt;br /&gt;
                ➕ 행 추가&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-remove&amp;quot; onclick=&amp;quot;removeRow()&amp;quot; id=&amp;quot;removeBtn&amp;quot;&amp;gt;&lt;br /&gt;
                ➖ 선택 행 삭제&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-export&amp;quot; onclick=&amp;quot;exportData()&amp;quot;&amp;gt;&lt;br /&gt;
                📥 CSV 내보내기&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;grid-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;info&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;🔌 연결:&amp;lt;/strong&amp;gt; Flask 백엔드(http://localhost:5000)를 통해 Oracle DB 연결&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;✏️ 편집:&amp;lt;/strong&amp;gt; 셀을 더블클릭하여 직접 수정 가능&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;💾 저장:&amp;lt;/strong&amp;gt; 변경 후 &#039;변경사항 저장&#039; 버튼 클릭으로 DB 반영&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;⚠️ 주의:&amp;lt;/strong&amp;gt; Flask 서버가 실행 중이어야 합니다 (python app.py)&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
        const API_URL = &#039;http://localhost:5000/api&#039;;&lt;br /&gt;
        let hot;&lt;br /&gt;
        let changedData = [];&lt;br /&gt;
        let originalData = [];&lt;br /&gt;
&lt;br /&gt;
        // Handsontable 초기화&lt;br /&gt;
        function initHandsontable() {&lt;br /&gt;
            const container = document.getElementById(&#039;grid-container&#039;);&lt;br /&gt;
            &lt;br /&gt;
            hot = new Handsontable(container, {&lt;br /&gt;
                data: [],&lt;br /&gt;
                colHeaders: [&#039;ID&#039;, &#039;이름&#039;, &#039;부서&#039;, &#039;직급&#039;, &#039;급여(만원)&#039;, &#039;이메일&#039;, &#039;입사일&#039;],&lt;br /&gt;
                columns: [&lt;br /&gt;
                    { data: &#039;id&#039;, type: &#039;numeric&#039;, readOnly: true },&lt;br /&gt;
                    { data: &#039;name&#039;, type: &#039;text&#039; },&lt;br /&gt;
                    { &lt;br /&gt;
                        data: &#039;department&#039;, &lt;br /&gt;
                        type: &#039;dropdown&#039;,&lt;br /&gt;
                        source: [&#039;개발팀&#039;, &#039;영업팀&#039;, &#039;인사팀&#039;, &#039;마케팅팀&#039;, &#039;재무팀&#039;]&lt;br /&gt;
                    },&lt;br /&gt;
                    { &lt;br /&gt;
                        data: &#039;position&#039;, &lt;br /&gt;
                        type: &#039;dropdown&#039;,&lt;br /&gt;
                        source: [&#039;사원&#039;, &#039;대리&#039;, &#039;과장&#039;, &#039;차장&#039;, &#039;부장&#039;, &#039;팀장&#039;]&lt;br /&gt;
                    },&lt;br /&gt;
                    { data: &#039;salary&#039;, type: &#039;numeric&#039;, numericFormat: { pattern: &#039;0,0.00&#039; } },&lt;br /&gt;
                    { data: &#039;email&#039;, type: &#039;text&#039; },&lt;br /&gt;
                    { data: &#039;join_date&#039;, type: &#039;date&#039;, dateFormat: &#039;YYYY-MM-DD&#039; }&lt;br /&gt;
                ],&lt;br /&gt;
                rowHeaders: true,&lt;br /&gt;
                width: &#039;100%&#039;,&lt;br /&gt;
                height: 500,&lt;br /&gt;
                licenseKey: &#039;non-commercial-and-evaluation&#039;,&lt;br /&gt;
                dropdownMenu: true,&lt;br /&gt;
                filters: true,&lt;br /&gt;
                columnSorting: true,&lt;br /&gt;
                contextMenu: true,&lt;br /&gt;
                manualColumnResize: true,&lt;br /&gt;
                manualRowResize: true,&lt;br /&gt;
                stretchH: &#039;all&#039;,&lt;br /&gt;
                afterChange: onDataChange,&lt;br /&gt;
                language: &#039;ko-KR&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 변경 감지&lt;br /&gt;
        function onDataChange(changes, source) {&lt;br /&gt;
            if (source === &#039;loadData&#039; || !changes) return;&lt;br /&gt;
            &lt;br /&gt;
            changes.forEach(([row, prop, oldValue, newValue]) =&amp;gt; {&lt;br /&gt;
                if (oldValue !== newValue) {&lt;br /&gt;
                    const rowData = hot.getSourceDataAtRow(row);&lt;br /&gt;
                    changedData.push({&lt;br /&gt;
                        id: rowData.id,&lt;br /&gt;
                        field: prop,&lt;br /&gt;
                        value: newValue&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            &lt;br /&gt;
            if (changedData.length &amp;gt; 0) {&lt;br /&gt;
                document.getElementById(&#039;saveBtn&#039;).disabled = false;&lt;br /&gt;
                showStatus(&#039;변경사항이 있습니다. 저장 버튼을 클릭하세요.&#039;, &#039;info&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 상태 메시지 표시&lt;br /&gt;
        function showStatus(message, type = &#039;info&#039;) {&lt;br /&gt;
            const statusEl = document.getElementById(&#039;status&#039;);&lt;br /&gt;
            statusEl.textContent = message;&lt;br /&gt;
            statusEl.className = `status ${type}`;&lt;br /&gt;
            statusEl.style.display = &#039;block&#039;;&lt;br /&gt;
            &lt;br /&gt;
            if (type === &#039;success&#039; || type === &#039;error&#039;) {&lt;br /&gt;
                setTimeout(() =&amp;gt; {&lt;br /&gt;
                    statusEl.style.display = &#039;none&#039;;&lt;br /&gt;
                }, 5000);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 불러오기&lt;br /&gt;
        async function loadData() {&lt;br /&gt;
            showStatus(&#039;데이터를 불러오는 중...&#039;, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                const response = await fetch(`${API_URL}/employees`);&lt;br /&gt;
                &lt;br /&gt;
                if (!response.ok) {&lt;br /&gt;
                    throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                const data = await response.json();&lt;br /&gt;
                originalData = JSON.parse(JSON.stringify(data));&lt;br /&gt;
                hot.loadData(data);&lt;br /&gt;
                changedData = [];&lt;br /&gt;
                document.getElementById(&#039;saveBtn&#039;).disabled = true;&lt;br /&gt;
                &lt;br /&gt;
                showStatus(`${data.length}개의 레코드를 불러왔습니다.`, &#039;success&#039;);&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`오류: ${error.message}. Flask 서버가 실행 중인지 확인하세요.`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;데이터 로드 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 변경사항 저장&lt;br /&gt;
        async function saveChanges() {&lt;br /&gt;
            if (changedData.length === 0) {&lt;br /&gt;
                showStatus(&#039;저장할 변경사항이 없습니다.&#039;, &#039;info&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            showStatus(`${changedData.length}개의 변경사항을 저장하는 중...`, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                const response = await fetch(`${API_URL}/employees/batch`, {&lt;br /&gt;
                    method: &#039;POST&#039;,&lt;br /&gt;
                    headers: {&lt;br /&gt;
                        &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                    },&lt;br /&gt;
                    body: JSON.stringify({ changes: changedData })&lt;br /&gt;
                });&lt;br /&gt;
                &lt;br /&gt;
                if (!response.ok) {&lt;br /&gt;
                    throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                const result = await response.json();&lt;br /&gt;
                showStatus(result.message, &#039;success&#039;);&lt;br /&gt;
                changedData = [];&lt;br /&gt;
                document.getElementById(&#039;saveBtn&#039;).disabled = true;&lt;br /&gt;
                &lt;br /&gt;
                // 데이터 새로고침&lt;br /&gt;
                await loadData();&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`저장 오류: ${error.message}`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;저장 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 행 추가&lt;br /&gt;
        async function addRow() {&lt;br /&gt;
            const newEmployee = {&lt;br /&gt;
                name: &#039;새 직원&#039;,&lt;br /&gt;
                department: &#039;개발팀&#039;,&lt;br /&gt;
                position: &#039;사원&#039;,&lt;br /&gt;
                salary: 3000,&lt;br /&gt;
                email: &#039;&#039;,&lt;br /&gt;
                join_date: new Date().toISOString().split(&#039;T&#039;)[0]&lt;br /&gt;
            };&lt;br /&gt;
            &lt;br /&gt;
            showStatus(&#039;새 직원을 추가하는 중...&#039;, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                const response = await fetch(`${API_URL}/employees`, {&lt;br /&gt;
                    method: &#039;POST&#039;,&lt;br /&gt;
                    headers: {&lt;br /&gt;
                        &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                    },&lt;br /&gt;
                    body: JSON.stringify(newEmployee)&lt;br /&gt;
                });&lt;br /&gt;
                &lt;br /&gt;
                if (!response.ok) {&lt;br /&gt;
                    throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                const result = await response.json();&lt;br /&gt;
                showStatus(result.message, &#039;success&#039;);&lt;br /&gt;
                &lt;br /&gt;
                // 데이터 새로고침&lt;br /&gt;
                await loadData();&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`추가 오류: ${error.message}`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;추가 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 선택 행 삭제&lt;br /&gt;
        async function removeRow() {&lt;br /&gt;
            const selected = hot.getSelected();&lt;br /&gt;
            if (!selected || selected.length === 0) {&lt;br /&gt;
                showStatus(&#039;삭제할 행을 선택해주세요.&#039;, &#039;error&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            const startRow = selected[0][0];&lt;br /&gt;
            const endRow = selected[0][2];&lt;br /&gt;
            const rowsToDelete = [];&lt;br /&gt;
            &lt;br /&gt;
            for (let i = startRow; i &amp;lt;= endRow; i++) {&lt;br /&gt;
                const rowData = hot.getSourceDataAtRow(i);&lt;br /&gt;
                if (rowData &amp;amp;&amp;amp; rowData.id) {&lt;br /&gt;
                    rowsToDelete.push(rowData.id);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            if (rowsToDelete.length === 0) {&lt;br /&gt;
                showStatus(&#039;삭제할 유효한 행이 없습니다.&#039;, &#039;error&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            if (!confirm(`선택된 ${rowsToDelete.length}개의 행을 삭제하시겠습니까?`)) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            showStatus(`${rowsToDelete.length}개의 행을 삭제하는 중...`, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                for (const id of rowsToDelete) {&lt;br /&gt;
                    const response = await fetch(`${API_URL}/employees/${id}`, {&lt;br /&gt;
                        method: &#039;DELETE&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                    &lt;br /&gt;
                    if (!response.ok) {&lt;br /&gt;
                        throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                showStatus(`${rowsToDelete.length}개의 행이 삭제되었습니다.`, &#039;success&#039;);&lt;br /&gt;
                &lt;br /&gt;
                // 데이터 새로고침&lt;br /&gt;
                await loadData();&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`삭제 오류: ${error.message}`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;삭제 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // CSV 내보내기&lt;br /&gt;
        function exportData() {&lt;br /&gt;
            const exportPlugin = hot.getPlugin(&#039;exportFile&#039;);&lt;br /&gt;
            exportPlugin.downloadFile(&#039;csv&#039;, {&lt;br /&gt;
                bom: true,&lt;br /&gt;
                columnDelimiter: &#039;,&#039;,&lt;br /&gt;
                columnHeaders: true,&lt;br /&gt;
                exportHiddenColumns: false,&lt;br /&gt;
                exportHiddenRows: false,&lt;br /&gt;
                fileExtension: &#039;csv&#039;,&lt;br /&gt;
                filename: &#039;employees_[YYYY]-[MM]-[DD]&#039;,&lt;br /&gt;
                mimeType: &#039;text/csv&#039;,&lt;br /&gt;
                rowDelimiter: &#039;\r\n&#039;,&lt;br /&gt;
                rowHeaders: false&lt;br /&gt;
            });&lt;br /&gt;
            &lt;br /&gt;
            showStatus(&#039;CSV 파일이 다운로드되었습니다.&#039;, &#039;success&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 페이지 로드 시 초기화&lt;br /&gt;
        window.onload = function() {&lt;br /&gt;
            initHandsontable();&lt;br /&gt;
            loadData();&lt;br /&gt;
        };&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%95%B8%EB%93%9C%EC%98%A8%ED%85%8C%EC%9D%B4%EB%B8%94_%EA%B7%B8%EB%A6%AC%EB%93%9C_ORACLE_DB_%EC%97%B0%EA%B2%B0_%EC%98%88%EC%A0%9C&amp;diff=2327</id>
		<title>핸드온테이블 그리드 ORACLE DB 연결 예제</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%95%B8%EB%93%9C%EC%98%A8%ED%85%8C%EC%9D%B4%EB%B8%94_%EA%B7%B8%EB%A6%AC%EB%93%9C_ORACLE_DB_%EC%97%B0%EA%B2%B0_%EC%98%88%EC%A0%9C&amp;diff=2327"/>
		<updated>2025-10-17T07:18:30Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
=== 화면 ===&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Handsontable 데이터 그리드&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        * {&lt;br /&gt;
            margin: 0;&lt;br /&gt;
            padding: 0;&lt;br /&gt;
            box-sizing: border-box;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        body {&lt;br /&gt;
            font-family: &#039;Segoe UI&#039;, Tahoma, Geneva, Verdana, sans-serif;&lt;br /&gt;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);&lt;br /&gt;
            padding: 20px;&lt;br /&gt;
            min-height: 100vh;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .container {&lt;br /&gt;
            max-width: 1400px;&lt;br /&gt;
            margin: 0 auto;&lt;br /&gt;
            background: white;&lt;br /&gt;
            border-radius: 15px;&lt;br /&gt;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);&lt;br /&gt;
            padding: 30px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        h1 {&lt;br /&gt;
            color: #333;&lt;br /&gt;
            margin-bottom: 10px;&lt;br /&gt;
            font-size: 28px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .subtitle {&lt;br /&gt;
            color: #666;&lt;br /&gt;
            margin-bottom: 25px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .controls {&lt;br /&gt;
            display: flex;&lt;br /&gt;
            gap: 10px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            flex-wrap: wrap;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button {&lt;br /&gt;
            padding: 10px 20px;&lt;br /&gt;
            border: none;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            cursor: pointer;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
            transition: all 0.3s;&lt;br /&gt;
            color: white;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add {&lt;br /&gt;
            background: #10b981;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add:hover {&lt;br /&gt;
            background: #059669;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove {&lt;br /&gt;
            background: #ef4444;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove:hover {&lt;br /&gt;
            background: #dc2626;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export {&lt;br /&gt;
            background: #3b82f6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export:hover {&lt;br /&gt;
            background: #2563eb;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-clear {&lt;br /&gt;
            background: #f59e0b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-clear:hover {&lt;br /&gt;
            background: #d97706;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        #grid-container {&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            height: 500px;&lt;br /&gt;
            overflow: hidden;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info {&lt;br /&gt;
            margin-top: 20px;&lt;br /&gt;
            padding: 15px;&lt;br /&gt;
            background: #f3f4f6;&lt;br /&gt;
            border-radius: 8px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            color: #374151;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item {&lt;br /&gt;
            margin-bottom: 5px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item strong {&lt;br /&gt;
            color: #1f2937;&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;h1&amp;gt;📊 Handsontable 데이터 그리드&amp;lt;/h1&amp;gt;&lt;br /&gt;
        &amp;lt;p class=&amp;quot;subtitle&amp;quot;&amp;gt;실시간 데이터 편집 및 관리 시스템&amp;lt;/p&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;controls&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-add&amp;quot; onclick=&amp;quot;addRow()&amp;quot;&amp;gt;➕ 행 추가&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-remove&amp;quot; onclick=&amp;quot;removeRow()&amp;quot;&amp;gt;➖ 선택 행 삭제&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-export&amp;quot; onclick=&amp;quot;exportData()&amp;quot;&amp;gt;💾 CSV 내보내기&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-clear&amp;quot; onclick=&amp;quot;clearFilters()&amp;quot;&amp;gt;🔄 필터 초기화&amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;grid-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;info&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;🔍 기능:&amp;lt;/strong&amp;gt; 셀 편집, 정렬, 필터링, 행/열 크기 조정&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;💡 팁:&amp;lt;/strong&amp;gt; 컬럼 헤더를 클릭하여 정렬, 드롭다운에서 필터 선택 가능&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;⌨️ 단축키:&amp;lt;/strong&amp;gt; Ctrl+C(복사), Ctrl+V(붙여넣기), Delete(삭제)&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
        // 샘플 데이터 생성&lt;br /&gt;
        const data = [&lt;br /&gt;
            { id: 1, name: &#039;김철수&#039;, department: &#039;개발팀&#039;, position: &#039;팀장&#039;, salary: 5500, email: &#039;kim@example.com&#039;, joinDate: &#039;2020-01-15&#039; },&lt;br /&gt;
            { id: 2, name: &#039;이영희&#039;, department: &#039;영업팀&#039;, position: &#039;대리&#039;, salary: 4200, email: &#039;lee@example.com&#039;, joinDate: &#039;2021-03-20&#039; },&lt;br /&gt;
            { id: 3, name: &#039;박민수&#039;, department: &#039;개발팀&#039;, position: &#039;과장&#039;, salary: 4800, email: &#039;park@example.com&#039;, joinDate: &#039;2019-07-10&#039; },&lt;br /&gt;
            { id: 4, name: &#039;최지현&#039;, department: &#039;인사팀&#039;, position: &#039;사원&#039;, salary: 3500, email: &#039;choi@example.com&#039;, joinDate: &#039;2022-05-01&#039; },&lt;br /&gt;
            { id: 5, name: &#039;정수진&#039;, department: &#039;마케팅팀&#039;, position: &#039;차장&#039;, salary: 5200, email: &#039;jung@example.com&#039;, joinDate: &#039;2018-11-30&#039; },&lt;br /&gt;
            { id: 6, name: &#039;강동원&#039;, department: &#039;영업팀&#039;, position: &#039;부장&#039;, salary: 6000, email: &#039;kang@example.com&#039;, joinDate: &#039;2017-02-14&#039; },&lt;br /&gt;
            { id: 7, name: &#039;윤서영&#039;, department: &#039;개발팀&#039;, position: &#039;사원&#039;, salary: 3800, email: &#039;yoon@example.com&#039;, joinDate: &#039;2023-01-10&#039; },&lt;br /&gt;
            { id: 8, name: &#039;임하늘&#039;, department: &#039;마케팅팀&#039;, position: &#039;대리&#039;, salary: 4300, email: &#039;lim@example.com&#039;, joinDate: &#039;2021-08-25&#039; },&lt;br /&gt;
            { id: 9, name: &#039;송민호&#039;, department: &#039;인사팀&#039;, position: &#039;과장&#039;, salary: 4900, email: &#039;song@example.com&#039;, joinDate: &#039;2020-06-15&#039; },&lt;br /&gt;
            { id: 10, name: &#039;한지우&#039;, department: &#039;영업팀&#039;, position: &#039;사원&#039;, salary: 3600, email: &#039;han@example.com&#039;, joinDate: &#039;2022-09-01&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        const container = document.getElementById(&#039;grid-container&#039;);&lt;br /&gt;
        &lt;br /&gt;
        const hot = new Handsontable(container, {&lt;br /&gt;
            data: data,&lt;br /&gt;
            colHeaders: [&#039;ID&#039;, &#039;이름&#039;, &#039;부서&#039;, &#039;직급&#039;, &#039;급여(만원)&#039;, &#039;이메일&#039;, &#039;입사일&#039;],&lt;br /&gt;
            columns: [&lt;br /&gt;
                { data: &#039;id&#039;, type: &#039;numeric&#039;, readOnly: true },&lt;br /&gt;
                { data: &#039;name&#039;, type: &#039;text&#039; },&lt;br /&gt;
                { &lt;br /&gt;
                    data: &#039;department&#039;, &lt;br /&gt;
                    type: &#039;dropdown&#039;,&lt;br /&gt;
                    source: [&#039;개발팀&#039;, &#039;영업팀&#039;, &#039;인사팀&#039;, &#039;마케팅팀&#039;, &#039;재무팀&#039;]&lt;br /&gt;
                },&lt;br /&gt;
                { &lt;br /&gt;
                    data: &#039;position&#039;, &lt;br /&gt;
                    type: &#039;dropdown&#039;,&lt;br /&gt;
                    source: [&#039;사원&#039;, &#039;대리&#039;, &#039;과장&#039;, &#039;차장&#039;, &#039;부장&#039;, &#039;팀장&#039;]&lt;br /&gt;
                },&lt;br /&gt;
                { data: &#039;salary&#039;, type: &#039;numeric&#039;, numericFormat: { pattern: &#039;0,0&#039; } },&lt;br /&gt;
                { data: &#039;email&#039;, type: &#039;text&#039; },&lt;br /&gt;
                { data: &#039;joinDate&#039;, type: &#039;date&#039;, dateFormat: &#039;YYYY-MM-DD&#039; }&lt;br /&gt;
            ],&lt;br /&gt;
            rowHeaders: true,&lt;br /&gt;
            width: &#039;100%&#039;,&lt;br /&gt;
            height: 500,&lt;br /&gt;
            licenseKey: &#039;non-commercial-and-evaluation&#039;,&lt;br /&gt;
            dropdownMenu: true,&lt;br /&gt;
            filters: true,&lt;br /&gt;
            columnSorting: true,&lt;br /&gt;
            contextMenu: true,&lt;br /&gt;
            manualColumnResize: true,&lt;br /&gt;
            manualRowResize: true,&lt;br /&gt;
            stretchH: &#039;all&#039;,&lt;br /&gt;
            autoWrapRow: true,&lt;br /&gt;
            autoWrapCol: true,&lt;br /&gt;
            fillHandle: {&lt;br /&gt;
                direction: &#039;vertical&#039;,&lt;br /&gt;
                autoInsertRow: false&lt;br /&gt;
            },&lt;br /&gt;
            language: &#039;ko-KR&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        // 행 추가 함수&lt;br /&gt;
        function addRow() {&lt;br /&gt;
            const newId = hot.countRows() + 1;&lt;br /&gt;
            const newRow = {&lt;br /&gt;
                id: newId,&lt;br /&gt;
                name: &#039;&#039;,&lt;br /&gt;
                department: &#039;개발팀&#039;,&lt;br /&gt;
                position: &#039;사원&#039;,&lt;br /&gt;
                salary: 3000,&lt;br /&gt;
                email: &#039;&#039;,&lt;br /&gt;
                joinDate: new Date().toISOString().split(&#039;T&#039;)[0]&lt;br /&gt;
            };&lt;br /&gt;
            hot.alter(&#039;insert_row_below&#039;, hot.countRows(), 1);&lt;br /&gt;
            hot.setDataAtRow(hot.countRows() - 1, [&lt;br /&gt;
                newRow.id, &lt;br /&gt;
                newRow.name, &lt;br /&gt;
                newRow.department, &lt;br /&gt;
                newRow.position, &lt;br /&gt;
                newRow.salary, &lt;br /&gt;
                newRow.email, &lt;br /&gt;
                newRow.joinDate&lt;br /&gt;
            ]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 선택 행 삭제 함수&lt;br /&gt;
        function removeRow() {&lt;br /&gt;
            const selected = hot.getSelected();&lt;br /&gt;
            if (selected &amp;amp;&amp;amp; selected.length &amp;gt; 0) {&lt;br /&gt;
                const startRow = selected[0][0];&lt;br /&gt;
                const endRow = selected[0][2];&lt;br /&gt;
                const rowCount = endRow - startRow + 1;&lt;br /&gt;
                &lt;br /&gt;
                if (confirm(`선택된 ${rowCount}개의 행을 삭제하시겠습니까?`)) {&lt;br /&gt;
                    hot.alter(&#039;remove_row&#039;, startRow, rowCount);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                alert(&#039;삭제할 행을 선택해주세요.&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // CSV 내보내기 함수&lt;br /&gt;
        function exportData() {&lt;br /&gt;
            const exportPlugin = hot.getPlugin(&#039;exportFile&#039;);&lt;br /&gt;
            exportPlugin.downloadFile(&#039;csv&#039;, {&lt;br /&gt;
                bom: true,&lt;br /&gt;
                columnDelimiter: &#039;,&#039;,&lt;br /&gt;
                columnHeaders: true,&lt;br /&gt;
                exportHiddenColumns: false,&lt;br /&gt;
                exportHiddenRows: false,&lt;br /&gt;
                fileExtension: &#039;csv&#039;,&lt;br /&gt;
                filename: &#039;employee_data_[YYYY]-[MM]-[DD]&#039;,&lt;br /&gt;
                mimeType: &#039;text/csv&#039;,&lt;br /&gt;
                rowDelimiter: &#039;\r\n&#039;,&lt;br /&gt;
                rowHeaders: false&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 필터 초기화 함수&lt;br /&gt;
        function clearFilters() {&lt;br /&gt;
            const filtersPlugin = hot.getPlugin(&#039;filters&#039;);&lt;br /&gt;
            filtersPlugin.clearConditions();&lt;br /&gt;
            filtersPlugin.filter();&lt;br /&gt;
            hot.render();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 변경 이벤트&lt;br /&gt;
        hot.addHook(&#039;afterChange&#039;, function(changes, source) {&lt;br /&gt;
            if (source !== &#039;loadData&#039;) {&lt;br /&gt;
                console.log(&#039;데이터 변경:&#039;, changes);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 서버 ===&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Oracle DB 연동 데이터 그리드&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        * {&lt;br /&gt;
            margin: 0;&lt;br /&gt;
            padding: 0;&lt;br /&gt;
            box-sizing: border-box;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        body {&lt;br /&gt;
            font-family: &#039;Segoe UI&#039;, Tahoma, Geneva, Verdana, sans-serif;&lt;br /&gt;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);&lt;br /&gt;
            padding: 20px;&lt;br /&gt;
            min-height: 100vh;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .container {&lt;br /&gt;
            max-width: 1400px;&lt;br /&gt;
            margin: 0 auto;&lt;br /&gt;
            background: white;&lt;br /&gt;
            border-radius: 15px;&lt;br /&gt;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);&lt;br /&gt;
            padding: 30px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        h1 {&lt;br /&gt;
            color: #333;&lt;br /&gt;
            margin-bottom: 10px;&lt;br /&gt;
            font-size: 28px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .subtitle {&lt;br /&gt;
            color: #666;&lt;br /&gt;
            margin-bottom: 25px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status {&lt;br /&gt;
            padding: 10px 15px;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status.success {&lt;br /&gt;
            background: #d1fae5;&lt;br /&gt;
            color: #065f46;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status.error {&lt;br /&gt;
            background: #fee2e2;&lt;br /&gt;
            color: #991b1b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .status.info {&lt;br /&gt;
            background: #dbeafe;&lt;br /&gt;
            color: #1e40af;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .controls {&lt;br /&gt;
            display: flex;&lt;br /&gt;
            gap: 10px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            flex-wrap: wrap;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button {&lt;br /&gt;
            padding: 10px 20px;&lt;br /&gt;
            border: none;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            cursor: pointer;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
            transition: all 0.3s;&lt;br /&gt;
            color: white;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button:disabled {&lt;br /&gt;
            opacity: 0.5;&lt;br /&gt;
            cursor: not-allowed;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-load {&lt;br /&gt;
            background: #8b5cf6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-load:hover:not(:disabled) {&lt;br /&gt;
            background: #7c3aed;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-save {&lt;br /&gt;
            background: #10b981;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-save:hover:not(:disabled) {&lt;br /&gt;
            background: #059669;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add {&lt;br /&gt;
            background: #3b82f6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add:hover:not(:disabled) {&lt;br /&gt;
            background: #2563eb;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove {&lt;br /&gt;
            background: #ef4444;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove:hover:not(:disabled) {&lt;br /&gt;
            background: #dc2626;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export {&lt;br /&gt;
            background: #f59e0b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export:hover:not(:disabled) {&lt;br /&gt;
            background: #d97706;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        #grid-container {&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            height: 500px;&lt;br /&gt;
            overflow: hidden;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info {&lt;br /&gt;
            margin-top: 20px;&lt;br /&gt;
            padding: 15px;&lt;br /&gt;
            background: #f3f4f6;&lt;br /&gt;
            border-radius: 8px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            color: #374151;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item {&lt;br /&gt;
            margin-bottom: 5px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item strong {&lt;br /&gt;
            color: #1f2937;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .loading {&lt;br /&gt;
            display: inline-block;&lt;br /&gt;
            width: 20px;&lt;br /&gt;
            height: 20px;&lt;br /&gt;
            border: 3px solid rgba(255,255,255,.3);&lt;br /&gt;
            border-radius: 50%;&lt;br /&gt;
            border-top-color: #fff;&lt;br /&gt;
            animation: spin 1s ease-in-out infinite;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        @keyframes spin {&lt;br /&gt;
            to { transform: rotate(360deg); }&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;h1&amp;gt;🗄️ Oracle Database 연동 데이터 그리드&amp;lt;/h1&amp;gt;&lt;br /&gt;
        &amp;lt;p class=&amp;quot;subtitle&amp;quot;&amp;gt;실시간 CRUD 작업 지원 시스템&amp;lt;/p&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;status&amp;quot; class=&amp;quot;status info&amp;quot; style=&amp;quot;display: none;&amp;quot;&amp;gt;&lt;br /&gt;
            연결 상태를 확인하는 중...&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;controls&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-load&amp;quot; onclick=&amp;quot;loadData()&amp;quot;&amp;gt;&lt;br /&gt;
                🔄 데이터 불러오기&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-save&amp;quot; onclick=&amp;quot;saveChanges()&amp;quot; id=&amp;quot;saveBtn&amp;quot; disabled&amp;gt;&lt;br /&gt;
                💾 변경사항 저장&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-add&amp;quot; onclick=&amp;quot;addRow()&amp;quot; id=&amp;quot;addBtn&amp;quot;&amp;gt;&lt;br /&gt;
                ➕ 행 추가&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-remove&amp;quot; onclick=&amp;quot;removeRow()&amp;quot; id=&amp;quot;removeBtn&amp;quot;&amp;gt;&lt;br /&gt;
                ➖ 선택 행 삭제&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-export&amp;quot; onclick=&amp;quot;exportData()&amp;quot;&amp;gt;&lt;br /&gt;
                📥 CSV 내보내기&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;grid-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;info&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;🔌 연결:&amp;lt;/strong&amp;gt; Flask 백엔드(http://localhost:5000)를 통해 Oracle DB 연결&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;✏️ 편집:&amp;lt;/strong&amp;gt; 셀을 더블클릭하여 직접 수정 가능&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;💾 저장:&amp;lt;/strong&amp;gt; 변경 후 &#039;변경사항 저장&#039; 버튼 클릭으로 DB 반영&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;⚠️ 주의:&amp;lt;/strong&amp;gt; Flask 서버가 실행 중이어야 합니다 (python app.py)&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
        const API_URL = &#039;http://localhost:5000/api&#039;;&lt;br /&gt;
        let hot;&lt;br /&gt;
        let changedData = [];&lt;br /&gt;
        let originalData = [];&lt;br /&gt;
&lt;br /&gt;
        // Handsontable 초기화&lt;br /&gt;
        function initHandsontable() {&lt;br /&gt;
            const container = document.getElementById(&#039;grid-container&#039;);&lt;br /&gt;
            &lt;br /&gt;
            hot = new Handsontable(container, {&lt;br /&gt;
                data: [],&lt;br /&gt;
                colHeaders: [&#039;ID&#039;, &#039;이름&#039;, &#039;부서&#039;, &#039;직급&#039;, &#039;급여(만원)&#039;, &#039;이메일&#039;, &#039;입사일&#039;],&lt;br /&gt;
                columns: [&lt;br /&gt;
                    { data: &#039;id&#039;, type: &#039;numeric&#039;, readOnly: true },&lt;br /&gt;
                    { data: &#039;name&#039;, type: &#039;text&#039; },&lt;br /&gt;
                    { &lt;br /&gt;
                        data: &#039;department&#039;, &lt;br /&gt;
                        type: &#039;dropdown&#039;,&lt;br /&gt;
                        source: [&#039;개발팀&#039;, &#039;영업팀&#039;, &#039;인사팀&#039;, &#039;마케팅팀&#039;, &#039;재무팀&#039;]&lt;br /&gt;
                    },&lt;br /&gt;
                    { &lt;br /&gt;
                        data: &#039;position&#039;, &lt;br /&gt;
                        type: &#039;dropdown&#039;,&lt;br /&gt;
                        source: [&#039;사원&#039;, &#039;대리&#039;, &#039;과장&#039;, &#039;차장&#039;, &#039;부장&#039;, &#039;팀장&#039;]&lt;br /&gt;
                    },&lt;br /&gt;
                    { data: &#039;salary&#039;, type: &#039;numeric&#039;, numericFormat: { pattern: &#039;0,0.00&#039; } },&lt;br /&gt;
                    { data: &#039;email&#039;, type: &#039;text&#039; },&lt;br /&gt;
                    { data: &#039;join_date&#039;, type: &#039;date&#039;, dateFormat: &#039;YYYY-MM-DD&#039; }&lt;br /&gt;
                ],&lt;br /&gt;
                rowHeaders: true,&lt;br /&gt;
                width: &#039;100%&#039;,&lt;br /&gt;
                height: 500,&lt;br /&gt;
                licenseKey: &#039;non-commercial-and-evaluation&#039;,&lt;br /&gt;
                dropdownMenu: true,&lt;br /&gt;
                filters: true,&lt;br /&gt;
                columnSorting: true,&lt;br /&gt;
                contextMenu: true,&lt;br /&gt;
                manualColumnResize: true,&lt;br /&gt;
                manualRowResize: true,&lt;br /&gt;
                stretchH: &#039;all&#039;,&lt;br /&gt;
                afterChange: onDataChange,&lt;br /&gt;
                language: &#039;ko-KR&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 변경 감지&lt;br /&gt;
        function onDataChange(changes, source) {&lt;br /&gt;
            if (source === &#039;loadData&#039; || !changes) return;&lt;br /&gt;
            &lt;br /&gt;
            changes.forEach(([row, prop, oldValue, newValue]) =&amp;gt; {&lt;br /&gt;
                if (oldValue !== newValue) {&lt;br /&gt;
                    const rowData = hot.getSourceDataAtRow(row);&lt;br /&gt;
                    changedData.push({&lt;br /&gt;
                        id: rowData.id,&lt;br /&gt;
                        field: prop,&lt;br /&gt;
                        value: newValue&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            &lt;br /&gt;
            if (changedData.length &amp;gt; 0) {&lt;br /&gt;
                document.getElementById(&#039;saveBtn&#039;).disabled = false;&lt;br /&gt;
                showStatus(&#039;변경사항이 있습니다. 저장 버튼을 클릭하세요.&#039;, &#039;info&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 상태 메시지 표시&lt;br /&gt;
        function showStatus(message, type = &#039;info&#039;) {&lt;br /&gt;
            const statusEl = document.getElementById(&#039;status&#039;);&lt;br /&gt;
            statusEl.textContent = message;&lt;br /&gt;
            statusEl.className = `status ${type}`;&lt;br /&gt;
            statusEl.style.display = &#039;block&#039;;&lt;br /&gt;
            &lt;br /&gt;
            if (type === &#039;success&#039; || type === &#039;error&#039;) {&lt;br /&gt;
                setTimeout(() =&amp;gt; {&lt;br /&gt;
                    statusEl.style.display = &#039;none&#039;;&lt;br /&gt;
                }, 5000);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 불러오기&lt;br /&gt;
        async function loadData() {&lt;br /&gt;
            showStatus(&#039;데이터를 불러오는 중...&#039;, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                const response = await fetch(`${API_URL}/employees`);&lt;br /&gt;
                &lt;br /&gt;
                if (!response.ok) {&lt;br /&gt;
                    throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                const data = await response.json();&lt;br /&gt;
                originalData = JSON.parse(JSON.stringify(data));&lt;br /&gt;
                hot.loadData(data);&lt;br /&gt;
                changedData = [];&lt;br /&gt;
                document.getElementById(&#039;saveBtn&#039;).disabled = true;&lt;br /&gt;
                &lt;br /&gt;
                showStatus(`${data.length}개의 레코드를 불러왔습니다.`, &#039;success&#039;);&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`오류: ${error.message}. Flask 서버가 실행 중인지 확인하세요.`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;데이터 로드 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 변경사항 저장&lt;br /&gt;
        async function saveChanges() {&lt;br /&gt;
            if (changedData.length === 0) {&lt;br /&gt;
                showStatus(&#039;저장할 변경사항이 없습니다.&#039;, &#039;info&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            showStatus(`${changedData.length}개의 변경사항을 저장하는 중...`, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                const response = await fetch(`${API_URL}/employees/batch`, {&lt;br /&gt;
                    method: &#039;POST&#039;,&lt;br /&gt;
                    headers: {&lt;br /&gt;
                        &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                    },&lt;br /&gt;
                    body: JSON.stringify({ changes: changedData })&lt;br /&gt;
                });&lt;br /&gt;
                &lt;br /&gt;
                if (!response.ok) {&lt;br /&gt;
                    throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                const result = await response.json();&lt;br /&gt;
                showStatus(result.message, &#039;success&#039;);&lt;br /&gt;
                changedData = [];&lt;br /&gt;
                document.getElementById(&#039;saveBtn&#039;).disabled = true;&lt;br /&gt;
                &lt;br /&gt;
                // 데이터 새로고침&lt;br /&gt;
                await loadData();&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`저장 오류: ${error.message}`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;저장 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 행 추가&lt;br /&gt;
        async function addRow() {&lt;br /&gt;
            const newEmployee = {&lt;br /&gt;
                name: &#039;새 직원&#039;,&lt;br /&gt;
                department: &#039;개발팀&#039;,&lt;br /&gt;
                position: &#039;사원&#039;,&lt;br /&gt;
                salary: 3000,&lt;br /&gt;
                email: &#039;&#039;,&lt;br /&gt;
                join_date: new Date().toISOString().split(&#039;T&#039;)[0]&lt;br /&gt;
            };&lt;br /&gt;
            &lt;br /&gt;
            showStatus(&#039;새 직원을 추가하는 중...&#039;, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                const response = await fetch(`${API_URL}/employees`, {&lt;br /&gt;
                    method: &#039;POST&#039;,&lt;br /&gt;
                    headers: {&lt;br /&gt;
                        &#039;Content-Type&#039;: &#039;application/json&#039;,&lt;br /&gt;
                    },&lt;br /&gt;
                    body: JSON.stringify(newEmployee)&lt;br /&gt;
                });&lt;br /&gt;
                &lt;br /&gt;
                if (!response.ok) {&lt;br /&gt;
                    throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                const result = await response.json();&lt;br /&gt;
                showStatus(result.message, &#039;success&#039;);&lt;br /&gt;
                &lt;br /&gt;
                // 데이터 새로고침&lt;br /&gt;
                await loadData();&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`추가 오류: ${error.message}`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;추가 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 선택 행 삭제&lt;br /&gt;
        async function removeRow() {&lt;br /&gt;
            const selected = hot.getSelected();&lt;br /&gt;
            if (!selected || selected.length === 0) {&lt;br /&gt;
                showStatus(&#039;삭제할 행을 선택해주세요.&#039;, &#039;error&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            const startRow = selected[0][0];&lt;br /&gt;
            const endRow = selected[0][2];&lt;br /&gt;
            const rowsToDelete = [];&lt;br /&gt;
            &lt;br /&gt;
            for (let i = startRow; i &amp;lt;= endRow; i++) {&lt;br /&gt;
                const rowData = hot.getSourceDataAtRow(i);&lt;br /&gt;
                if (rowData &amp;amp;&amp;amp; rowData.id) {&lt;br /&gt;
                    rowsToDelete.push(rowData.id);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            if (rowsToDelete.length === 0) {&lt;br /&gt;
                showStatus(&#039;삭제할 유효한 행이 없습니다.&#039;, &#039;error&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            if (!confirm(`선택된 ${rowsToDelete.length}개의 행을 삭제하시겠습니까?`)) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            &lt;br /&gt;
            showStatus(`${rowsToDelete.length}개의 행을 삭제하는 중...`, &#039;info&#039;);&lt;br /&gt;
            &lt;br /&gt;
            try {&lt;br /&gt;
                for (const id of rowsToDelete) {&lt;br /&gt;
                    const response = await fetch(`${API_URL}/employees/${id}`, {&lt;br /&gt;
                        method: &#039;DELETE&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                    &lt;br /&gt;
                    if (!response.ok) {&lt;br /&gt;
                        throw new Error(`HTTP error! status: ${response.status}`);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                showStatus(`${rowsToDelete.length}개의 행이 삭제되었습니다.`, &#039;success&#039;);&lt;br /&gt;
                &lt;br /&gt;
                // 데이터 새로고침&lt;br /&gt;
                await loadData();&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                showStatus(`삭제 오류: ${error.message}`, &#039;error&#039;);&lt;br /&gt;
                console.error(&#039;삭제 오류:&#039;, error);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // CSV 내보내기&lt;br /&gt;
        function exportData() {&lt;br /&gt;
            const exportPlugin = hot.getPlugin(&#039;exportFile&#039;);&lt;br /&gt;
            exportPlugin.downloadFile(&#039;csv&#039;, {&lt;br /&gt;
                bom: true,&lt;br /&gt;
                columnDelimiter: &#039;,&#039;,&lt;br /&gt;
                columnHeaders: true,&lt;br /&gt;
                exportHiddenColumns: false,&lt;br /&gt;
                exportHiddenRows: false,&lt;br /&gt;
                fileExtension: &#039;csv&#039;,&lt;br /&gt;
                filename: &#039;employees_[YYYY]-[MM]-[DD]&#039;,&lt;br /&gt;
                mimeType: &#039;text/csv&#039;,&lt;br /&gt;
                rowDelimiter: &#039;\r\n&#039;,&lt;br /&gt;
                rowHeaders: false&lt;br /&gt;
            });&lt;br /&gt;
            &lt;br /&gt;
            showStatus(&#039;CSV 파일이 다운로드되었습니다.&#039;, &#039;success&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 페이지 로드 시 초기화&lt;br /&gt;
        window.onload = function() {&lt;br /&gt;
            initHandsontable();&lt;br /&gt;
            loadData();&lt;br /&gt;
        };&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%95%B8%EB%93%9C%EC%98%A8%ED%85%8C%EC%9D%B4%EB%B8%94_%EA%B7%B8%EB%A6%AC%EB%93%9C_ORACLE_DB_%EC%97%B0%EA%B2%B0_%EC%98%88%EC%A0%9C&amp;diff=2326</id>
		<title>핸드온테이블 그리드 ORACLE DB 연결 예제</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%95%B8%EB%93%9C%EC%98%A8%ED%85%8C%EC%9D%B4%EB%B8%94_%EA%B7%B8%EB%A6%AC%EB%93%9C_ORACLE_DB_%EC%97%B0%EA%B2%B0_%EC%98%88%EC%A0%9C&amp;diff=2326"/>
		<updated>2025-10-17T07:05:09Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: &amp;lt;source lang=html&amp;gt;  &amp;lt;!DOCTYPE html&amp;gt; &amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt; &amp;lt;head&amp;gt;     &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;     &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;     &amp;lt;title&amp;gt;Handsontable 데이터 그리드&amp;lt;/title&amp;gt;     &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.css&amp;quot;&amp;gt;     &amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;     &amp;lt;style&amp;gt;         * {             m...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Handsontable 데이터 그리드&amp;lt;/title&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/handsontable/12.3.1/handsontable.full.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        * {&lt;br /&gt;
            margin: 0;&lt;br /&gt;
            padding: 0;&lt;br /&gt;
            box-sizing: border-box;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        body {&lt;br /&gt;
            font-family: &#039;Segoe UI&#039;, Tahoma, Geneva, Verdana, sans-serif;&lt;br /&gt;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);&lt;br /&gt;
            padding: 20px;&lt;br /&gt;
            min-height: 100vh;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .container {&lt;br /&gt;
            max-width: 1400px;&lt;br /&gt;
            margin: 0 auto;&lt;br /&gt;
            background: white;&lt;br /&gt;
            border-radius: 15px;&lt;br /&gt;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);&lt;br /&gt;
            padding: 30px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        h1 {&lt;br /&gt;
            color: #333;&lt;br /&gt;
            margin-bottom: 10px;&lt;br /&gt;
            font-size: 28px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .subtitle {&lt;br /&gt;
            color: #666;&lt;br /&gt;
            margin-bottom: 25px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .controls {&lt;br /&gt;
            display: flex;&lt;br /&gt;
            gap: 10px;&lt;br /&gt;
            margin-bottom: 20px;&lt;br /&gt;
            flex-wrap: wrap;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        button {&lt;br /&gt;
            padding: 10px 20px;&lt;br /&gt;
            border: none;&lt;br /&gt;
            border-radius: 5px;&lt;br /&gt;
            cursor: pointer;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            font-weight: 600;&lt;br /&gt;
            transition: all 0.3s;&lt;br /&gt;
            color: white;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add {&lt;br /&gt;
            background: #10b981;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-add:hover {&lt;br /&gt;
            background: #059669;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove {&lt;br /&gt;
            background: #ef4444;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-remove:hover {&lt;br /&gt;
            background: #dc2626;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export {&lt;br /&gt;
            background: #3b82f6;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-export:hover {&lt;br /&gt;
            background: #2563eb;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-clear {&lt;br /&gt;
            background: #f59e0b;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .btn-clear:hover {&lt;br /&gt;
            background: #d97706;&lt;br /&gt;
            transform: translateY(-2px);&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        #grid-container {&lt;br /&gt;
            width: 100%;&lt;br /&gt;
            height: 500px;&lt;br /&gt;
            overflow: hidden;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info {&lt;br /&gt;
            margin-top: 20px;&lt;br /&gt;
            padding: 15px;&lt;br /&gt;
            background: #f3f4f6;&lt;br /&gt;
            border-radius: 8px;&lt;br /&gt;
            font-size: 14px;&lt;br /&gt;
            color: #374151;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item {&lt;br /&gt;
            margin-bottom: 5px;&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        .info-item strong {&lt;br /&gt;
            color: #1f2937;&lt;br /&gt;
        }&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;h1&amp;gt;📊 Handsontable 데이터 그리드&amp;lt;/h1&amp;gt;&lt;br /&gt;
        &amp;lt;p class=&amp;quot;subtitle&amp;quot;&amp;gt;실시간 데이터 편집 및 관리 시스템&amp;lt;/p&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;controls&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-add&amp;quot; onclick=&amp;quot;addRow()&amp;quot;&amp;gt;➕ 행 추가&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-remove&amp;quot; onclick=&amp;quot;removeRow()&amp;quot;&amp;gt;➖ 선택 행 삭제&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-export&amp;quot; onclick=&amp;quot;exportData()&amp;quot;&amp;gt;💾 CSV 내보내기&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;btn-clear&amp;quot; onclick=&amp;quot;clearFilters()&amp;quot;&amp;gt;🔄 필터 초기화&amp;lt;/button&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div id=&amp;quot;grid-container&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
        &amp;lt;div class=&amp;quot;info&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;🔍 기능:&amp;lt;/strong&amp;gt; 셀 편집, 정렬, 필터링, 행/열 크기 조정&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;💡 팁:&amp;lt;/strong&amp;gt; 컬럼 헤더를 클릭하여 정렬, 드롭다운에서 필터 선택 가능&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;info-item&amp;quot;&amp;gt;&amp;lt;strong&amp;gt;⌨️ 단축키:&amp;lt;/strong&amp;gt; Ctrl+C(복사), Ctrl+V(붙여넣기), Delete(삭제)&amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
        // 샘플 데이터 생성&lt;br /&gt;
        const data = [&lt;br /&gt;
            { id: 1, name: &#039;김철수&#039;, department: &#039;개발팀&#039;, position: &#039;팀장&#039;, salary: 5500, email: &#039;kim@example.com&#039;, joinDate: &#039;2020-01-15&#039; },&lt;br /&gt;
            { id: 2, name: &#039;이영희&#039;, department: &#039;영업팀&#039;, position: &#039;대리&#039;, salary: 4200, email: &#039;lee@example.com&#039;, joinDate: &#039;2021-03-20&#039; },&lt;br /&gt;
            { id: 3, name: &#039;박민수&#039;, department: &#039;개발팀&#039;, position: &#039;과장&#039;, salary: 4800, email: &#039;park@example.com&#039;, joinDate: &#039;2019-07-10&#039; },&lt;br /&gt;
            { id: 4, name: &#039;최지현&#039;, department: &#039;인사팀&#039;, position: &#039;사원&#039;, salary: 3500, email: &#039;choi@example.com&#039;, joinDate: &#039;2022-05-01&#039; },&lt;br /&gt;
            { id: 5, name: &#039;정수진&#039;, department: &#039;마케팅팀&#039;, position: &#039;차장&#039;, salary: 5200, email: &#039;jung@example.com&#039;, joinDate: &#039;2018-11-30&#039; },&lt;br /&gt;
            { id: 6, name: &#039;강동원&#039;, department: &#039;영업팀&#039;, position: &#039;부장&#039;, salary: 6000, email: &#039;kang@example.com&#039;, joinDate: &#039;2017-02-14&#039; },&lt;br /&gt;
            { id: 7, name: &#039;윤서영&#039;, department: &#039;개발팀&#039;, position: &#039;사원&#039;, salary: 3800, email: &#039;yoon@example.com&#039;, joinDate: &#039;2023-01-10&#039; },&lt;br /&gt;
            { id: 8, name: &#039;임하늘&#039;, department: &#039;마케팅팀&#039;, position: &#039;대리&#039;, salary: 4300, email: &#039;lim@example.com&#039;, joinDate: &#039;2021-08-25&#039; },&lt;br /&gt;
            { id: 9, name: &#039;송민호&#039;, department: &#039;인사팀&#039;, position: &#039;과장&#039;, salary: 4900, email: &#039;song@example.com&#039;, joinDate: &#039;2020-06-15&#039; },&lt;br /&gt;
            { id: 10, name: &#039;한지우&#039;, department: &#039;영업팀&#039;, position: &#039;사원&#039;, salary: 3600, email: &#039;han@example.com&#039;, joinDate: &#039;2022-09-01&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        const container = document.getElementById(&#039;grid-container&#039;);&lt;br /&gt;
        &lt;br /&gt;
        const hot = new Handsontable(container, {&lt;br /&gt;
            data: data,&lt;br /&gt;
            colHeaders: [&#039;ID&#039;, &#039;이름&#039;, &#039;부서&#039;, &#039;직급&#039;, &#039;급여(만원)&#039;, &#039;이메일&#039;, &#039;입사일&#039;],&lt;br /&gt;
            columns: [&lt;br /&gt;
                { data: &#039;id&#039;, type: &#039;numeric&#039;, readOnly: true },&lt;br /&gt;
                { data: &#039;name&#039;, type: &#039;text&#039; },&lt;br /&gt;
                { &lt;br /&gt;
                    data: &#039;department&#039;, &lt;br /&gt;
                    type: &#039;dropdown&#039;,&lt;br /&gt;
                    source: [&#039;개발팀&#039;, &#039;영업팀&#039;, &#039;인사팀&#039;, &#039;마케팅팀&#039;, &#039;재무팀&#039;]&lt;br /&gt;
                },&lt;br /&gt;
                { &lt;br /&gt;
                    data: &#039;position&#039;, &lt;br /&gt;
                    type: &#039;dropdown&#039;,&lt;br /&gt;
                    source: [&#039;사원&#039;, &#039;대리&#039;, &#039;과장&#039;, &#039;차장&#039;, &#039;부장&#039;, &#039;팀장&#039;]&lt;br /&gt;
                },&lt;br /&gt;
                { data: &#039;salary&#039;, type: &#039;numeric&#039;, numericFormat: { pattern: &#039;0,0&#039; } },&lt;br /&gt;
                { data: &#039;email&#039;, type: &#039;text&#039; },&lt;br /&gt;
                { data: &#039;joinDate&#039;, type: &#039;date&#039;, dateFormat: &#039;YYYY-MM-DD&#039; }&lt;br /&gt;
            ],&lt;br /&gt;
            rowHeaders: true,&lt;br /&gt;
            width: &#039;100%&#039;,&lt;br /&gt;
            height: 500,&lt;br /&gt;
            licenseKey: &#039;non-commercial-and-evaluation&#039;,&lt;br /&gt;
            dropdownMenu: true,&lt;br /&gt;
            filters: true,&lt;br /&gt;
            columnSorting: true,&lt;br /&gt;
            contextMenu: true,&lt;br /&gt;
            manualColumnResize: true,&lt;br /&gt;
            manualRowResize: true,&lt;br /&gt;
            stretchH: &#039;all&#039;,&lt;br /&gt;
            autoWrapRow: true,&lt;br /&gt;
            autoWrapCol: true,&lt;br /&gt;
            fillHandle: {&lt;br /&gt;
                direction: &#039;vertical&#039;,&lt;br /&gt;
                autoInsertRow: false&lt;br /&gt;
            },&lt;br /&gt;
            language: &#039;ko-KR&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        // 행 추가 함수&lt;br /&gt;
        function addRow() {&lt;br /&gt;
            const newId = hot.countRows() + 1;&lt;br /&gt;
            const newRow = {&lt;br /&gt;
                id: newId,&lt;br /&gt;
                name: &#039;&#039;,&lt;br /&gt;
                department: &#039;개발팀&#039;,&lt;br /&gt;
                position: &#039;사원&#039;,&lt;br /&gt;
                salary: 3000,&lt;br /&gt;
                email: &#039;&#039;,&lt;br /&gt;
                joinDate: new Date().toISOString().split(&#039;T&#039;)[0]&lt;br /&gt;
            };&lt;br /&gt;
            hot.alter(&#039;insert_row_below&#039;, hot.countRows(), 1);&lt;br /&gt;
            hot.setDataAtRow(hot.countRows() - 1, [&lt;br /&gt;
                newRow.id, &lt;br /&gt;
                newRow.name, &lt;br /&gt;
                newRow.department, &lt;br /&gt;
                newRow.position, &lt;br /&gt;
                newRow.salary, &lt;br /&gt;
                newRow.email, &lt;br /&gt;
                newRow.joinDate&lt;br /&gt;
            ]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 선택 행 삭제 함수&lt;br /&gt;
        function removeRow() {&lt;br /&gt;
            const selected = hot.getSelected();&lt;br /&gt;
            if (selected &amp;amp;&amp;amp; selected.length &amp;gt; 0) {&lt;br /&gt;
                const startRow = selected[0][0];&lt;br /&gt;
                const endRow = selected[0][2];&lt;br /&gt;
                const rowCount = endRow - startRow + 1;&lt;br /&gt;
                &lt;br /&gt;
                if (confirm(`선택된 ${rowCount}개의 행을 삭제하시겠습니까?`)) {&lt;br /&gt;
                    hot.alter(&#039;remove_row&#039;, startRow, rowCount);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                alert(&#039;삭제할 행을 선택해주세요.&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // CSV 내보내기 함수&lt;br /&gt;
        function exportData() {&lt;br /&gt;
            const exportPlugin = hot.getPlugin(&#039;exportFile&#039;);&lt;br /&gt;
            exportPlugin.downloadFile(&#039;csv&#039;, {&lt;br /&gt;
                bom: true,&lt;br /&gt;
                columnDelimiter: &#039;,&#039;,&lt;br /&gt;
                columnHeaders: true,&lt;br /&gt;
                exportHiddenColumns: false,&lt;br /&gt;
                exportHiddenRows: false,&lt;br /&gt;
                fileExtension: &#039;csv&#039;,&lt;br /&gt;
                filename: &#039;employee_data_[YYYY]-[MM]-[DD]&#039;,&lt;br /&gt;
                mimeType: &#039;text/csv&#039;,&lt;br /&gt;
                rowDelimiter: &#039;\r\n&#039;,&lt;br /&gt;
                rowHeaders: false&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 필터 초기화 함수&lt;br /&gt;
        function clearFilters() {&lt;br /&gt;
            const filtersPlugin = hot.getPlugin(&#039;filters&#039;);&lt;br /&gt;
            filtersPlugin.clearConditions();&lt;br /&gt;
            filtersPlugin.filter();&lt;br /&gt;
            hot.render();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // 데이터 변경 이벤트&lt;br /&gt;
        hot.addHook(&#039;afterChange&#039;, function(changes, source) {&lt;br /&gt;
            if (source !== &#039;loadData&#039;) {&lt;br /&gt;
                console.log(&#039;데이터 변경:&#039;, changes);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%97%91%EC%85%80_%EC%89%AC%ED%8A%B8_%EA%B7%B8%EB%A6%AC%EB%93%9C_(excel_sheet_grid)&amp;diff=2325</id>
		<title>엑셀 쉬트 그리드 (excel sheet grid)</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%97%91%EC%85%80_%EC%89%AC%ED%8A%B8_%EA%B7%B8%EB%A6%AC%EB%93%9C_(excel_sheet_grid)&amp;diff=2325"/>
		<updated>2025-10-17T07:04:25Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* Hands On Table */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
=== Lucky Sheet ===&lt;br /&gt;
https://dream-num.github.io/LuckysheetDemo/&lt;br /&gt;
=== Hands On Table ===&lt;br /&gt;
https://handsontable.com/docs/javascript-data-grid/&lt;br /&gt;
&lt;br /&gt;
==== [[핸드온테이블 그리드 ORACLE DB 연결 예제]] ====&lt;br /&gt;
* https://devcafe.co.kr/hand.html&lt;br /&gt;
* https://devcafe.co.kr/excel.html&lt;br /&gt;
* https://devcafe.co.kr/ag.html&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%97%91%EC%85%80_%EC%89%AC%ED%8A%B8_%EA%B7%B8%EB%A6%AC%EB%93%9C_(excel_sheet_grid)&amp;diff=2324</id>
		<title>엑셀 쉬트 그리드 (excel sheet grid)</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%97%91%EC%85%80_%EC%89%AC%ED%8A%B8_%EA%B7%B8%EB%A6%AC%EB%93%9C_(excel_sheet_grid)&amp;diff=2324"/>
		<updated>2025-10-17T04:28:28Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서:  === Lucky Sheet === https://dream-num.github.io/LuckysheetDemo/ === Hands On Table === https://handsontable.com/docs/javascript-data-grid/  * https://devcafe.co.kr/hand.html * https://devcafe.co.kr/excel.html * https://devcafe.co.kr/ag.html&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
=== Lucky Sheet ===&lt;br /&gt;
https://dream-num.github.io/LuckysheetDemo/&lt;br /&gt;
=== Hands On Table ===&lt;br /&gt;
https://handsontable.com/docs/javascript-data-grid/&lt;br /&gt;
&lt;br /&gt;
* https://devcafe.co.kr/hand.html&lt;br /&gt;
* https://devcafe.co.kr/excel.html&lt;br /&gt;
* https://devcafe.co.kr/ag.html&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=Autoit_%ED%81%B4%EB%A6%BD%EB%B3%B4%EB%93%9C_%EC%9D%B4%EB%AF%B8%EC%A7%80_%EB%B7%B0%EC%96%B4&amp;diff=2323</id>
		<title>Autoit 클립보드 이미지 뷰어</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=Autoit_%ED%81%B4%EB%A6%BD%EB%B3%B4%EB%93%9C_%EC%9D%B4%EB%AF%B8%EC%A7%80_%EB%B7%B0%EC%96%B4&amp;diff=2323"/>
		<updated>2025-10-17T01:45:37Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: 새 문서: == 클립보드 이미지 뷰어 == &amp;lt;source lang=autoit&amp;gt;  #include &amp;lt;GUIConstantsEx.au3&amp;gt; #include &amp;lt;WindowsConstants.au3&amp;gt; #include &amp;lt;StaticConstants.au3&amp;gt; #include &amp;lt;ButtonConstants.au3&amp;gt; #include &amp;lt;GDIPlus.au3&amp;gt;  Global $hGUI, $hPic, $hImage = 0, $hBitmap = 0  ; GDI+ 초기화 _GDIPlus_Startup()  ; GUI 생성 $hGUI = GUICreate(&amp;quot;클립보드 이미지 뷰어&amp;quot;, 800, 600, -1, -1, $WS_OVERLAPPEDWINDOW)  ; 버튼 생성 $btnLoad = GUICtrlCreateButton(&amp;quot;클립보드에서 가져오기&amp;quot;, 10, 1...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== 클립보드 이미지 뷰어 ==&lt;br /&gt;
&amp;lt;source lang=autoit&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;GUIConstantsEx.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;WindowsConstants.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;StaticConstants.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;ButtonConstants.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;GDIPlus.au3&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Global $hGUI, $hPic, $hImage = 0, $hBitmap = 0&lt;br /&gt;
&lt;br /&gt;
; GDI+ 초기화&lt;br /&gt;
_GDIPlus_Startup()&lt;br /&gt;
&lt;br /&gt;
; GUI 생성&lt;br /&gt;
$hGUI = GUICreate(&amp;quot;클립보드 이미지 뷰어&amp;quot;, 800, 600, -1, -1, $WS_OVERLAPPEDWINDOW)&lt;br /&gt;
&lt;br /&gt;
; 버튼 생성&lt;br /&gt;
$btnLoad = GUICtrlCreateButton(&amp;quot;클립보드에서 가져오기&amp;quot;, 10, 10, 150, 30)&lt;br /&gt;
$btnSave = GUICtrlCreateButton(&amp;quot;이미지 저장&amp;quot;, 170, 10, 100, 30)&lt;br /&gt;
$btnClear = GUICtrlCreateButton(&amp;quot;지우기&amp;quot;, 280, 10, 80, 30)&lt;br /&gt;
$lblInfo = GUICtrlCreateLabel(&amp;quot;클립보드에 이미지가 없습니다&amp;quot;, 400, 15, 350, 20)&lt;br /&gt;
&lt;br /&gt;
; 이미지 표시 영역 (Pic 컨트롤)&lt;br /&gt;
$hPic = GUICtrlCreatePic(&amp;quot;&amp;quot;, 10, 50, 780, 540, $SS_NOTIFY)&lt;br /&gt;
&lt;br /&gt;
GUISetState(@SW_SHOW, $hGUI)&lt;br /&gt;
&lt;br /&gt;
While 1&lt;br /&gt;
    $nMsg = GUIGetMsg()&lt;br /&gt;
    Switch $nMsg&lt;br /&gt;
        Case $GUI_EVENT_CLOSE&lt;br /&gt;
            ExitLoop&lt;br /&gt;
            &lt;br /&gt;
        Case $btnLoad&lt;br /&gt;
            LoadImageFromClipboard()&lt;br /&gt;
            &lt;br /&gt;
        Case $btnSave&lt;br /&gt;
            SaveImage()&lt;br /&gt;
            &lt;br /&gt;
        Case $btnClear&lt;br /&gt;
            ClearImage()&lt;br /&gt;
    EndSwitch&lt;br /&gt;
WEnd&lt;br /&gt;
&lt;br /&gt;
; 종료 처리&lt;br /&gt;
Cleanup()&lt;br /&gt;
GUIDelete($hGUI)&lt;br /&gt;
_GDIPlus_Shutdown()&lt;br /&gt;
&lt;br /&gt;
; ===== 함수 정의 =====&lt;br /&gt;
&lt;br /&gt;
Func LoadImageFromClipboard()&lt;br /&gt;
    ; 기존 이미지 해제&lt;br /&gt;
    If $hBitmap &amp;lt;&amp;gt; 0 Then&lt;br /&gt;
        _GDIPlus_BitmapDispose($hBitmap)&lt;br /&gt;
        $hBitmap = 0&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    ; 클립보드에서 비트맵 가져오기&lt;br /&gt;
    $hBitmap = _ClipBoard_GetDataEx($CF_BITMAP)&lt;br /&gt;
    &lt;br /&gt;
    If $hBitmap = 0 Then&lt;br /&gt;
        GUICtrlSetData($lblInfo, &amp;quot;클립보드에 이미지가 없습니다&amp;quot;)&lt;br /&gt;
        MsgBox(48, &amp;quot;알림&amp;quot;, &amp;quot;클립보드에 이미지가 없습니다.&amp;quot;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    ; GDI+ 비트맵으로 변환&lt;br /&gt;
    $hImage = _GDIPlus_BitmapCreateFromHBITMAP($hBitmap)&lt;br /&gt;
    &lt;br /&gt;
    If $hImage = 0 Then&lt;br /&gt;
        GUICtrlSetData($lblInfo, &amp;quot;이미지 로드 실패&amp;quot;)&lt;br /&gt;
        MsgBox(16, &amp;quot;오류&amp;quot;, &amp;quot;이미지를 로드할 수 없습니다.&amp;quot;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    ; 이미지 크기 가져오기&lt;br /&gt;
    $iWidth = _GDIPlus_ImageGetWidth($hImage)&lt;br /&gt;
    $iHeight = _GDIPlus_ImageGetHeight($hImage)&lt;br /&gt;
    &lt;br /&gt;
    ; 임시 파일로 저장 후 Pic 컨트롤에 표시&lt;br /&gt;
    $sTempFile = @TempDir &amp;amp; &amp;quot;\clipboard_temp.bmp&amp;quot;&lt;br /&gt;
    _GDIPlus_ImageSaveToFile($hImage, $sTempFile)&lt;br /&gt;
    &lt;br /&gt;
    ; Pic 컨트롤 크기 조정 및 이미지 설정&lt;br /&gt;
    Local $picWidth = 780&lt;br /&gt;
    Local $picHeight = 540&lt;br /&gt;
    Local $ratio = 1&lt;br /&gt;
    &lt;br /&gt;
    If $iWidth &amp;gt; $picWidth Or $iHeight &amp;gt; $picHeight Then&lt;br /&gt;
        $ratio = ($iWidth / $picWidth &amp;gt; $iHeight / $picHeight) ? $picWidth / $iWidth : $picHeight / $iHeight&lt;br /&gt;
        $iWidth = Int($iWidth * $ratio)&lt;br /&gt;
        $iHeight = Int($iHeight * $ratio)&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    GUICtrlSetPos($hPic, 10 + Int(($picWidth - $iWidth) / 2), 50 + Int(($picHeight - $iHeight) / 2), $iWidth, $iHeight)&lt;br /&gt;
    GUICtrlSetImage($hPic, $sTempFile)&lt;br /&gt;
    &lt;br /&gt;
    GUICtrlSetData($lblInfo, &amp;quot;이미지 로드됨: &amp;quot; &amp;amp; _GDIPlus_ImageGetWidth($hImage) &amp;amp; &amp;quot; x &amp;quot; &amp;amp; _GDIPlus_ImageGetHeight($hImage))&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func SaveImage()&lt;br /&gt;
    If $hImage = 0 Then&lt;br /&gt;
        MsgBox(48, &amp;quot;알림&amp;quot;, &amp;quot;저장할 이미지가 없습니다.&amp;quot;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    $sFile = FileSaveDialog(&amp;quot;이미지 저장&amp;quot;, @MyDocumentsDir, &amp;quot;이미지 파일 (*.png;*.jpg;*.bmp)|모든 파일 (*.*)&amp;quot;, 16, &amp;quot;screenshot.png&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    If @error Then Return&lt;br /&gt;
    &lt;br /&gt;
    ; 파일 확장자 확인&lt;br /&gt;
    If StringRight($sFile, 4) &amp;lt;&amp;gt; &amp;quot;.png&amp;quot; And StringRight($sFile, 4) &amp;lt;&amp;gt; &amp;quot;.jpg&amp;quot; And StringRight($sFile, 4) &amp;lt;&amp;gt; &amp;quot;.bmp&amp;quot; Then&lt;br /&gt;
        $sFile &amp;amp;= &amp;quot;.png&amp;quot;&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    _GDIPlus_ImageSaveToFile($hImage, $sFile)&lt;br /&gt;
    &lt;br /&gt;
    If FileExists($sFile) Then&lt;br /&gt;
        MsgBox(64, &amp;quot;완료&amp;quot;, &amp;quot;이미지가 저장되었습니다:&amp;quot; &amp;amp; @CRLF &amp;amp; $sFile)&lt;br /&gt;
    Else&lt;br /&gt;
        MsgBox(16, &amp;quot;오류&amp;quot;, &amp;quot;이미지 저장에 실패했습니다.&amp;quot;)&lt;br /&gt;
    EndIf&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func ClearImage()&lt;br /&gt;
    GUICtrlSetImage($hPic, &amp;quot;&amp;quot;)&lt;br /&gt;
    GUICtrlSetData($lblInfo, &amp;quot;클립보드에 이미지가 없습니다&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    If $hImage &amp;lt;&amp;gt; 0 Then&lt;br /&gt;
        _GDIPlus_ImageDispose($hImage)&lt;br /&gt;
        $hImage = 0&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    If $hBitmap &amp;lt;&amp;gt; 0 Then&lt;br /&gt;
        _WinAPI_DeleteObject($hBitmap)&lt;br /&gt;
        $hBitmap = 0&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    ; 임시 파일 삭제&lt;br /&gt;
    FileDelete(@TempDir &amp;amp; &amp;quot;\clipboard_temp.bmp&amp;quot;)&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func Cleanup()&lt;br /&gt;
    ClearImage()&lt;br /&gt;
    &lt;br /&gt;
    ; 임시 파일 정리&lt;br /&gt;
    FileDelete(@TempDir &amp;amp; &amp;quot;\clipboard_temp.bmp&amp;quot;)&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
; 클립보드에서 데이터 가져오기 함수&lt;br /&gt;
Func _ClipBoard_GetDataEx($iFormat)&lt;br /&gt;
    If Not _ClipBoard_Open(0) Then Return SetError(1, 0, 0)&lt;br /&gt;
    &lt;br /&gt;
    Local $hData = _ClipBoard_GetData($iFormat)&lt;br /&gt;
    _ClipBoard_Close()&lt;br /&gt;
    &lt;br /&gt;
    Return $hData&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%9D%B8%EC%83%9D_%EB%A7%9B%EC%A7%91_%EA%B2%BD%EA%B8%B0/%EC%84%9C%EC%9A%B8&amp;diff=2322</id>
		<title>인생 맛집 경기/서울</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%9D%B8%EC%83%9D_%EB%A7%9B%EC%A7%91_%EA%B2%BD%EA%B8%B0/%EC%84%9C%EC%9A%B8&amp;diff=2322"/>
		<updated>2025-09-29T14:02:44Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 별 5개 14:26 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
== 인생 맛집 경기/서울 ==&lt;br /&gt;
&amp;lt;youtube&amp;gt;bZyphVPm21I&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 별 1개 00:20 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 마포양지설렁탕 (서울 공덕동)&lt;br /&gt;
2. 대한옥 (서울 영등포)&lt;br /&gt;
3. 장안정 (서울 을지로)&lt;br /&gt;
4. 풍남골뱅이 (서울 을지로)&lt;br /&gt;
5. 어제의 카레 (서울 이태원)&lt;br /&gt;
6. 홍이네 떡볶이 (서울 망우동)&lt;br /&gt;
7. 일미정 (인천 중앙동)&lt;br /&gt;
8. 대문점 (서울 영등포)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 별 2개 04:30 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 심원 (서울 회현역)&lt;br /&gt;
2. 고성막국수 (서울 방화동)&lt;br /&gt;
3. 꾸왁칼국수 ( 서울 주교동)&lt;br /&gt;
4. 복화루 (인천 부평)&lt;br /&gt;
5. 문배동육칼 (서울 문배동)&lt;br /&gt;
6. 부평막국수 (인천 부평)&lt;br /&gt;
7. 북성해장국 (서울 북아현동)&lt;br /&gt;
8. 장수보쌈 (서울 방산동)&lt;br /&gt;
9. 하정식당 (인천 용현동)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 별 3개 09:11 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 가고파설렁탕 (서울 망우동)&lt;br /&gt;
2. 단천면옥 (강원도 속초)&lt;br /&gt;
3. 사랑방칼국수 (서울 충무로)&lt;br /&gt;
4. 산동포자 (인천 백운역)&lt;br /&gt;
5. 옛집 (서울 삼각지)&lt;br /&gt;
6. 용문갈비집 (서울 용산)&lt;br /&gt;
7. 이문설농탕 (서울 종로)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== 별 4개 12:41 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 흥남집 (서울 오장동)&lt;br /&gt;
2. 원조마포껍데기집 (서울 공덕동)&lt;br /&gt;
3. 일산칼국수 (경기도 일산)&lt;br /&gt;
4. 중림장설렁탕 (서울 중림동)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 별 5개 14:26 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 덕수정 (서울 정동)&lt;br /&gt;
2. 동해반점 (서울 대림동)&lt;br /&gt;
3. 또순이네 (서울 양평동)&lt;br /&gt;
4. 명동교자 (서울 명동)&lt;br /&gt;
5. 방아다리감자국 (서울 종로5가)&lt;br /&gt;
6. 부일숯불갈비 (서울 영등포)&lt;br /&gt;
8. 일미집 (서울 후암동)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[category:맛집]] &lt;br /&gt;
[[category:경기도 맛집]] &lt;br /&gt;
[[category:서울 맛집]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%9D%B8%EC%83%9D_%EB%A7%9B%EC%A7%91_%EA%B2%BD%EA%B8%B0/%EC%84%9C%EC%9A%B8&amp;diff=2321</id>
		<title>인생 맛집 경기/서울</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%9D%B8%EC%83%9D_%EB%A7%9B%EC%A7%91_%EA%B2%BD%EA%B8%B0/%EC%84%9C%EC%9A%B8&amp;diff=2321"/>
		<updated>2025-09-29T14:01:49Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* 별 5개 14:26 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
== 인생 맛집 경기/서울 ==&lt;br /&gt;
&amp;lt;youtube&amp;gt;bZyphVPm21I&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 별 1개 00:20 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 마포양지설렁탕 (서울 공덕동)&lt;br /&gt;
2. 대한옥 (서울 영등포)&lt;br /&gt;
3. 장안정 (서울 을지로)&lt;br /&gt;
4. 풍남골뱅이 (서울 을지로)&lt;br /&gt;
5. 어제의 카레 (서울 이태원)&lt;br /&gt;
6. 홍이네 떡볶이 (서울 망우동)&lt;br /&gt;
7. 일미정 (인천 중앙동)&lt;br /&gt;
8. 대문점 (서울 영등포)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 별 2개 04:30 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 심원 (서울 회현역)&lt;br /&gt;
2. 고성막국수 (서울 방화동)&lt;br /&gt;
3. 꾸왁칼국수 ( 서울 주교동)&lt;br /&gt;
4. 복화루 (인천 부평)&lt;br /&gt;
5. 문배동육칼 (서울 문배동)&lt;br /&gt;
6. 부평막국수 (인천 부평)&lt;br /&gt;
7. 북성해장국 (서울 북아현동)&lt;br /&gt;
8. 장수보쌈 (서울 방산동)&lt;br /&gt;
9. 하정식당 (인천 용현동)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 별 3개 09:11 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 가고파설렁탕 (서울 망우동)&lt;br /&gt;
2. 단천면옥 (강원도 속초)&lt;br /&gt;
3. 사랑방칼국수 (서울 충무로)&lt;br /&gt;
4. 산동포자 (인천 백운역)&lt;br /&gt;
5. 옛집 (서울 삼각지)&lt;br /&gt;
6. 용문갈비집 (서울 용산)&lt;br /&gt;
7. 이문설농탕 (서울 종로)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== 별 4개 12:41 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 흥남집 (서울 오장동)&lt;br /&gt;
2. 원조마포껍데기집 (서울 공덕동)&lt;br /&gt;
3. 일산칼국수 (경기도 일산)&lt;br /&gt;
4. 중림장설렁탕 (서울 중림동)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 별 5개 14:26 ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. 덕수정 (서울 정동)&lt;br /&gt;
2. 동해반점 (서울 대림동)&lt;br /&gt;
3. 또순이네 (서울 양평동)&lt;br /&gt;
4. 명동교자 (서울 명동)&lt;br /&gt;
5. 방아다리감자국 (서울 종로5가)&lt;br /&gt;
6. 부일숯불갈비 (서울 영등포)&lt;br /&gt;
7. 빅토리통닭 (경기도 김포) &amp;lt;= 이건아니듯.&lt;br /&gt;
8. 일미집 (서울 후암동)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[category:맛집]] &lt;br /&gt;
[[category:경기도 맛집]] &lt;br /&gt;
[[category:서울 맛집]]&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%98%A4%ED%86%A0%EC%9E%87_%ED%8F%B4%EB%8D%94_%EB%82%B4%EB%B6%80_%ED%85%8D%EC%8A%A4%ED%8A%B8&amp;diff=2320</id>
		<title>오토잇 폴더 내부 텍스트</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%98%A4%ED%86%A0%EC%9E%87_%ED%8F%B4%EB%8D%94_%EB%82%B4%EB%B6%80_%ED%85%8D%EC%8A%A4%ED%8A%B8&amp;diff=2320"/>
		<updated>2025-09-26T07:10:01Z</updated>

		<summary type="html">&lt;p&gt;Devcafe: /* autoit 폴더 내부 텍스트 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== autoit 폴더 내부 텍스트==&lt;br /&gt;
&amp;lt;source lang=autoit&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****&lt;br /&gt;
#AutoIt3Wrapper_Icon=search.ico&lt;br /&gt;
#AutoIt3Wrapper_Res_Description=텍스트 파일 검색 프로그램&lt;br /&gt;
#AutoIt3Wrapper_Res_Fileversion=1.0.0.0&lt;br /&gt;
#AutoIt3Wrapper_Res_ProductVersion=1.0.0&lt;br /&gt;
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;ButtonConstants.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;EditConstants.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;GUIConstantsEx.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;GuiListView.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;GuiListBox.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;StaticConstants.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;WindowsConstants.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;File.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;Array.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;String.au3&amp;gt;&lt;br /&gt;
#include &amp;lt;StringConstants.au3&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; ===== 전역 변수 =====&lt;br /&gt;
Global $g_hGUI, $g_idFolderPath, $g_idSearchText, $g_idResultList&lt;br /&gt;
Global $g_idBtnBrowse, $g_idBtnSearch, $g_idBtnSave, $g_idBtnClear&lt;br /&gt;
Global $g_idChkRegex, $g_idChkCase, $g_idChkSubFolder&lt;br /&gt;
Global $g_idCmbFileType, $g_idProgressBar, $g_idStatusBar&lt;br /&gt;
Global $g_aResults[1][6] ; [파일경로, 상대경로, 라인번호, 라인내용, 파일크기, 수정일시]&lt;br /&gt;
Global $g_iResultCount = 0&lt;br /&gt;
Global $g_sBaseFolder = &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
; 지원할 파일 확장자&lt;br /&gt;
Global $g_aFileTypes[11][2] = [[&amp;quot;전체 텍스트 파일&amp;quot;, &amp;quot;*.txt;*.log;*.sql;*.ini;*.cfg;*.conf;*.properties&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;텍스트 파일 (*.txt)&amp;quot;, &amp;quot;*.txt&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;로그 파일 (*.log)&amp;quot;, &amp;quot;*.log&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;SQL 파일 (*.sql)&amp;quot;, &amp;quot;*.sql&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;설정 파일 (*.ini;*.cfg;*.conf)&amp;quot;, &amp;quot;*.ini;*.cfg;*.conf&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;스크립트 파일 (*.py;*.js;*.vbs;*.au3)&amp;quot;, &amp;quot;*.py;*.js;*.vbs;*.au3&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;웹 파일 (*.html;*.htm;*.xml;*.json)&amp;quot;, &amp;quot;*.html;*.htm;*.xml;*.json&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;배치 파일 (*.bat;*.cmd;*.ps1)&amp;quot;, &amp;quot;*.bat;*.cmd;*.ps1&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;오라클 추적 파일 (*.trc)&amp;quot;, &amp;quot;*.trc&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;마크다운 (*.md)&amp;quot;, &amp;quot;*.md&amp;quot;], _&lt;br /&gt;
                              [&amp;quot;모든 파일 (*.*)&amp;quot;, &amp;quot;*.*&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
Main()&lt;br /&gt;
&lt;br /&gt;
Func Main()&lt;br /&gt;
    CreateGUI()&lt;br /&gt;
    &lt;br /&gt;
    While 1&lt;br /&gt;
        Local $nMsg = GUIGetMsg()&lt;br /&gt;
        Switch $nMsg&lt;br /&gt;
            Case $GUI_EVENT_CLOSE&lt;br /&gt;
                ExitLoop&lt;br /&gt;
                &lt;br /&gt;
            Case $g_idBtnBrowse&lt;br /&gt;
                BrowseFolder()&lt;br /&gt;
                &lt;br /&gt;
            Case $g_idBtnSearch&lt;br /&gt;
                StartSearch()&lt;br /&gt;
                &lt;br /&gt;
            Case $g_idBtnSave&lt;br /&gt;
                SaveResults()&lt;br /&gt;
                &lt;br /&gt;
            Case $g_idBtnClear&lt;br /&gt;
                ClearResults()&lt;br /&gt;
                &lt;br /&gt;
            Case $g_idResultList&lt;br /&gt;
                If @extended = $NM_DBLCLK Then&lt;br /&gt;
                    OpenSelectedFile()&lt;br /&gt;
                EndIf&lt;br /&gt;
        EndSwitch&lt;br /&gt;
    WEnd&lt;br /&gt;
    &lt;br /&gt;
    GUIDelete()&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func CreateGUI()&lt;br /&gt;
    ; 메인 윈도우 생성&lt;br /&gt;
    $g_hGUI = GUICreate(&amp;quot;텍스트 파일 검색 프로그램 v1.0 - Oracle DBA Tools&amp;quot;, 900, 650, -1, -1, $WS_OVERLAPPEDWINDOW)&lt;br /&gt;
    &lt;br /&gt;
    ; 폴더 선택 그룹&lt;br /&gt;
    GUICtrlCreateGroup(&amp;quot;검색 경로&amp;quot;, 10, 10, 880, 60)&lt;br /&gt;
    GUICtrlCreateLabel(&amp;quot;폴더:&amp;quot;, 20, 35, 40, 20)&lt;br /&gt;
    $g_idFolderPath = GUICtrlCreateInput(&amp;quot;&amp;quot;, 65, 32, 730, 23)&lt;br /&gt;
    $g_idBtnBrowse = GUICtrlCreateButton(&amp;quot;찾기&amp;quot;, 805, 30, 80, 27)&lt;br /&gt;
    GUICtrlCreateGroup(&amp;quot;&amp;quot;, -99, -99, 1, 1) ; 그룹 종료&lt;br /&gt;
    &lt;br /&gt;
    ; 검색 옵션 그룹&lt;br /&gt;
    GUICtrlCreateGroup(&amp;quot;검색 옵션&amp;quot;, 10, 80, 880, 90)&lt;br /&gt;
    &lt;br /&gt;
    GUICtrlCreateLabel(&amp;quot;검색어:&amp;quot;, 20, 105, 50, 20)&lt;br /&gt;
    $g_idSearchText = GUICtrlCreateInput(&amp;quot;&amp;quot;, 75, 102, 300, 23)&lt;br /&gt;
    &lt;br /&gt;
    GUICtrlCreateLabel(&amp;quot;파일 형식:&amp;quot;, 390, 105, 60, 20)&lt;br /&gt;
    $g_idCmbFileType = GUICtrlCreateCombo(&amp;quot;&amp;quot;, 455, 102, 200, 200, $CBS_DROPDOWNLIST)&lt;br /&gt;
    &lt;br /&gt;
    ; 파일 형식 콤보박스 채우기&lt;br /&gt;
    Local $sFileTypes = &amp;quot;&amp;quot;&lt;br /&gt;
    For $i = 0 To UBound($g_aFileTypes) - 1&lt;br /&gt;
        $sFileTypes &amp;amp;= $g_aFileTypes[$i][0]&lt;br /&gt;
        If $i &amp;lt; UBound($g_aFileTypes) - 1 Then $sFileTypes &amp;amp;= &amp;quot;|&amp;quot;&lt;br /&gt;
    Next&lt;br /&gt;
    GUICtrlSetData($g_idCmbFileType, $sFileTypes, $g_aFileTypes[0][0])&lt;br /&gt;
    &lt;br /&gt;
    $g_idBtnSearch = GUICtrlCreateButton(&amp;quot;검색 시작&amp;quot;, 680, 100, 100, 27)&lt;br /&gt;
    $g_idBtnClear = GUICtrlCreateButton(&amp;quot;결과 지우기&amp;quot;, 790, 100, 90, 27)&lt;br /&gt;
    &lt;br /&gt;
    ; 체크박스 옵션들&lt;br /&gt;
    $g_idChkCase = GUICtrlCreateCheckbox(&amp;quot;대소문자 구분&amp;quot;, 20, 135, 100, 20)&lt;br /&gt;
    $g_idChkRegex = GUICtrlCreateCheckbox(&amp;quot;정규식 사용&amp;quot;, 130, 135, 100, 20)&lt;br /&gt;
    $g_idChkSubFolder = GUICtrlCreateCheckbox(&amp;quot;하위 폴더 포함&amp;quot;, 240, 135, 100, 20)&lt;br /&gt;
    GUICtrlSetState($g_idChkSubFolder, $GUI_CHECKED) ; 기본적으로 체크&lt;br /&gt;
    &lt;br /&gt;
    GUICtrlCreateGroup(&amp;quot;&amp;quot;, -99, -99, 1, 1) ; 그룹 종료&lt;br /&gt;
    &lt;br /&gt;
    ; 결과 리스트뷰&lt;br /&gt;
    GUICtrlCreateGroup(&amp;quot;검색 결과&amp;quot;, 10, 180, 880, 400)&lt;br /&gt;
    $g_idResultList = GUICtrlCreateListView(&amp;quot;파일명|상대 경로|라인|내용|크기|수정일시&amp;quot;, 20, 200, 860, 350, $LVS_REPORT + $LVS_SHOWSELALWAYS)&lt;br /&gt;
    _GUICtrlListView_SetColumnWidth($g_idResultList, 0, 150)  ; 파일명&lt;br /&gt;
    _GUICtrlListView_SetColumnWidth($g_idResultList, 1, 250)  ; 상대경로&lt;br /&gt;
    _GUICtrlListView_SetColumnWidth($g_idResultList, 2, 50)   ; 라인&lt;br /&gt;
    _GUICtrlListView_SetColumnWidth($g_idResultList, 3, 300)  ; 내용&lt;br /&gt;
    _GUICtrlListView_SetColumnWidth($g_idResultList, 4, 70)   ; 크기&lt;br /&gt;
    _GUICtrlListView_SetColumnWidth($g_idResultList, 5, 120)  ; 수정일시&lt;br /&gt;
    GUICtrlCreateGroup(&amp;quot;&amp;quot;, -99, -99, 1, 1) ; 그룹 종료&lt;br /&gt;
    &lt;br /&gt;
    ; 하단 버튼들&lt;br /&gt;
    $g_idBtnSave = GUICtrlCreateButton(&amp;quot;결과 저장&amp;quot;, 20, 590, 100, 30)&lt;br /&gt;
    GUICtrlCreateButton(&amp;quot;도움말&amp;quot;, 130, 590, 80, 30)&lt;br /&gt;
    &lt;br /&gt;
    ; 상태바&lt;br /&gt;
    $g_idStatusBar = GUICtrlCreateLabel(&amp;quot;준비됨&amp;quot;, 20, 630, 860, 15)&lt;br /&gt;
    &lt;br /&gt;
    GUISetState(@SW_SHOW)&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func BrowseFolder()&lt;br /&gt;
    Local $sFolder = FileSelectFolder(&amp;quot;검색할 폴더를 선택하세요&amp;quot;, &amp;quot;&amp;quot;, 1)&lt;br /&gt;
    If $sFolder &amp;lt;&amp;gt; &amp;quot;&amp;quot; Then&lt;br /&gt;
        GUICtrlSetData($g_idFolderPath, $sFolder)&lt;br /&gt;
        $g_sBaseFolder = $sFolder&lt;br /&gt;
    EndIf&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func StartSearch()&lt;br /&gt;
    Local $sFolder = GUICtrlRead($g_idFolderPath)&lt;br /&gt;
    Local $sSearchText = GUICtrlRead($g_idSearchText)&lt;br /&gt;
    &lt;br /&gt;
    ; 입력값 검증&lt;br /&gt;
    If $sFolder = &amp;quot;&amp;quot; Then&lt;br /&gt;
        MsgBox(16, &amp;quot;오류&amp;quot;, &amp;quot;검색할 폴더를 선택해주세요.&amp;quot;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    If Not FileExists($sFolder) Then&lt;br /&gt;
        MsgBox(16, &amp;quot;오류&amp;quot;, &amp;quot;선택한 폴더가 존재하지 않습니다.&amp;quot;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    If $sSearchText = &amp;quot;&amp;quot; Then&lt;br /&gt;
        MsgBox(16, &amp;quot;오류&amp;quot;, &amp;quot;검색어를 입력해주세요.&amp;quot;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    ; 검색 옵션 읽기&lt;br /&gt;
    Local $bCaseSensitive = (GUICtrlRead($g_idChkCase) = $GUI_CHECKED)&lt;br /&gt;
    Local $bUseRegex = (GUICtrlRead($g_idChkRegex) = $GUI_CHECKED)&lt;br /&gt;
    Local $bIncludeSubFolder = (GUICtrlRead($g_idChkSubFolder) = $GUI_CHECKED)&lt;br /&gt;
    &lt;br /&gt;
    Local $iFileTypeIndex = _GUICtrlComboBox_GetCurSel($g_idCmbFileType)&lt;br /&gt;
    Local $sFilePattern = $g_aFileTypes[$iFileTypeIndex][1]&lt;br /&gt;
    &lt;br /&gt;
    ; 결과 초기화&lt;br /&gt;
    ClearResults()&lt;br /&gt;
    &lt;br /&gt;
    ; 검색 시작&lt;br /&gt;
    GUICtrlSetData($g_idStatusBar, &amp;quot;검색 중...&amp;quot;)&lt;br /&gt;
    GUICtrlSetState($g_idBtnSearch, $GUI_DISABLE)&lt;br /&gt;
    &lt;br /&gt;
    $g_sBaseFolder = $sFolder&lt;br /&gt;
    SearchInFolder($sFolder, $sSearchText, $sFilePattern, $bCaseSensitive, $bUseRegex, $bIncludeSubFolder)&lt;br /&gt;
    &lt;br /&gt;
    GUICtrlSetState($g_idBtnSearch, $GUI_ENABLE)&lt;br /&gt;
    GUICtrlSetData($g_idStatusBar, &amp;quot;검색 완료: &amp;quot; &amp;amp; $g_iResultCount &amp;amp; &amp;quot;개 결과 발견&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    If $g_iResultCount = 0 Then&lt;br /&gt;
        MsgBox(64, &amp;quot;검색 완료&amp;quot;, &amp;quot;검색 결과가 없습니다.&amp;quot;)&lt;br /&gt;
    Else&lt;br /&gt;
        MsgBox(64, &amp;quot;검색 완료&amp;quot;, $g_iResultCount &amp;amp; &amp;quot;개의 결과를 찾았습니다.&amp;quot;)&lt;br /&gt;
    EndIf&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func SearchInFolder($sFolder, $sSearchText, $sFilePattern, $bCaseSensitive, $bUseRegex, $bIncludeSubFolder)&lt;br /&gt;
    Local $aFiles, $aFileList&lt;br /&gt;
    Local $sSearchPattern = $bIncludeSubFolder ? &amp;quot;*&amp;quot; : &amp;quot;&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    ; 파일 패턴을 배열로 분할&lt;br /&gt;
    Local $aPatterns = StringSplit($sFilePattern, &amp;quot;;&amp;quot;, $STR_NOCOUNT)&lt;br /&gt;
    &lt;br /&gt;
    For $sPattern In $aPatterns&lt;br /&gt;
        If $bIncludeSubFolder Then&lt;br /&gt;
            $aFiles = _FileListToArrayRec($sFolder, $sPattern, $FLTAR_FILES, $FLTAR_RECUR)&lt;br /&gt;
        Else&lt;br /&gt;
            $aFiles = _FileListToArray($sFolder, $sPattern, $FLTAR_FILES)&lt;br /&gt;
        EndIf&lt;br /&gt;
        &lt;br /&gt;
        If Not @error And IsArray($aFiles) Then&lt;br /&gt;
            For $i = 1 To $aFiles[0]&lt;br /&gt;
                Local $sFilePath = $bIncludeSubFolder ? $aFiles[$i] : $sFolder &amp;amp; &amp;quot;\&amp;quot; &amp;amp; $aFiles[$i]&lt;br /&gt;
                SearchInFile($sFilePath, $sSearchText, $bCaseSensitive, $bUseRegex)&lt;br /&gt;
                &lt;br /&gt;
                ; 상태 업데이트 (너무 자주 하지 않도록)&lt;br /&gt;
                If Mod($i, 50) = 0 Then&lt;br /&gt;
                    GUICtrlSetData($g_idStatusBar, &amp;quot;검색 중... (&amp;quot; &amp;amp; $i &amp;amp; &amp;quot;/&amp;quot; &amp;amp; $aFiles[0] &amp;amp; &amp;quot; 파일)&amp;quot;)&lt;br /&gt;
                EndIf&lt;br /&gt;
            Next&lt;br /&gt;
        EndIf&lt;br /&gt;
    Next&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func SearchInFile($sFilePath, $sSearchText, $bCaseSensitive, $bUseRegex)&lt;br /&gt;
    Local $hFile = FileOpen($sFilePath, $FO_READ + $FO_UTF8_NOBOM)&lt;br /&gt;
    If $hFile = -1 Then&lt;br /&gt;
        ; UTF-8로 실패하면 ANSI로 시도&lt;br /&gt;
        $hFile = FileOpen($sFilePath, $FO_READ)&lt;br /&gt;
        If $hFile = -1 Then Return&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    Local $sContent = FileRead($hFile)&lt;br /&gt;
    FileClose($hFile)&lt;br /&gt;
    &lt;br /&gt;
    If $sContent = &amp;quot;&amp;quot; Then Return&lt;br /&gt;
    &lt;br /&gt;
    ; 줄 단위로 분할&lt;br /&gt;
    Local $aLines = StringSplit($sContent, @CRLF, $STR_ENTIRESPLIT + $STR_NOCOUNT)&lt;br /&gt;
    &lt;br /&gt;
    ; 파일 정보 가져오기&lt;br /&gt;
    Local $iFileSize = FileGetSize($sFilePath)&lt;br /&gt;
    Local $sModifiedTime = FileGetTime($sFilePath, $FT_MODIFIED, $FT_STRING)&lt;br /&gt;
    &lt;br /&gt;
    ; 각 줄에서 검색&lt;br /&gt;
    For $i = 0 To UBound($aLines) - 1&lt;br /&gt;
        Local $sLine = $aLines[$i]&lt;br /&gt;
        Local $bFound = False&lt;br /&gt;
        &lt;br /&gt;
        If $bUseRegex Then&lt;br /&gt;
            ; 정규식 검색&lt;br /&gt;
            Local $aResult = StringRegExp($sLine, $sSearchText, $STR_REGEXPARRAYMATCH)&lt;br /&gt;
            If Not @error And IsArray($aResult) Then&lt;br /&gt;
                $bFound = True&lt;br /&gt;
            EndIf&lt;br /&gt;
        Else&lt;br /&gt;
            ; 일반 텍스트 검색&lt;br /&gt;
            Local $sSearchLine = $bCaseSensitive ? $sLine : StringLower($sLine)&lt;br /&gt;
            Local $sSearchTerm = $bCaseSensitive ? $sSearchText : StringLower($sSearchText)&lt;br /&gt;
            If StringInStr($sSearchLine, $sSearchTerm) &amp;gt; 0 Then&lt;br /&gt;
                $bFound = True&lt;br /&gt;
            EndIf&lt;br /&gt;
        EndIf&lt;br /&gt;
        &lt;br /&gt;
        If $bFound Then&lt;br /&gt;
            AddResult($sFilePath, $i + 1, $sLine, $iFileSize, $sModifiedTime)&lt;br /&gt;
        EndIf&lt;br /&gt;
    Next&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func AddResult($sFilePath, $iLineNumber, $sLineContent, $iFileSize, $sModifiedTime)&lt;br /&gt;
    ; 결과 배열 확장&lt;br /&gt;
    ReDim $g_aResults[$g_iResultCount + 1][6]&lt;br /&gt;
    &lt;br /&gt;
    ; 상대 경로 계산&lt;br /&gt;
    Local $sRelativePath = StringReplace($sFilePath, $g_sBaseFolder &amp;amp; &amp;quot;\&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    If $sRelativePath = $sFilePath Then $sRelativePath = StringReplace($sFilePath, $g_sBaseFolder, &amp;quot;&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    ; 파일명만 추출&lt;br /&gt;
    Local $aPathParts = StringSplit($sFilePath, &amp;quot;\&amp;quot;, $STR_NOCOUNT)&lt;br /&gt;
    Local $sFileName = $aPathParts[UBound($aPathParts) - 1]&lt;br /&gt;
    &lt;br /&gt;
    ; 결과 저장&lt;br /&gt;
    $g_aResults[$g_iResultCount][0] = $sFilePath&lt;br /&gt;
    $g_aResults[$g_iResultCount][1] = $sRelativePath&lt;br /&gt;
    $g_aResults[$g_iResultCount][2] = $iLineNumber&lt;br /&gt;
    $g_aResults[$g_iResultCount][3] = StringLeft($sLineContent, 200) ; 최대 200자만&lt;br /&gt;
    $g_aResults[$g_iResultCount][4] = Round($iFileSize / 1024, 1) &amp;amp; &amp;quot; KB&amp;quot;&lt;br /&gt;
    $g_aResults[$g_iResultCount][5] = $sModifiedTime&lt;br /&gt;
    &lt;br /&gt;
    ; 리스트뷰에 추가&lt;br /&gt;
    Local $sItem = $sFileName &amp;amp; &amp;quot;|&amp;quot; &amp;amp; $sRelativePath &amp;amp; &amp;quot;|&amp;quot; &amp;amp; $iLineNumber &amp;amp; &amp;quot;|&amp;quot; &amp;amp; _&lt;br /&gt;
                   StringReplace($sLineContent, @TAB, &amp;quot; &amp;quot;) &amp;amp; &amp;quot;|&amp;quot; &amp;amp; _&lt;br /&gt;
                   Round($iFileSize / 1024, 1) &amp;amp; &amp;quot; KB|&amp;quot; &amp;amp; $sModifiedTime&lt;br /&gt;
    &lt;br /&gt;
    GUICtrlCreateListViewItem($sItem, $g_idResultList)&lt;br /&gt;
    &lt;br /&gt;
    $g_iResultCount += 1&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func ClearResults()&lt;br /&gt;
    ; 리스트뷰 클리어&lt;br /&gt;
    _GUICtrlListView_DeleteAllItems($g_idResultList)&lt;br /&gt;
    &lt;br /&gt;
    ; 결과 배열 초기화&lt;br /&gt;
    ReDim $g_aResults[1][6]&lt;br /&gt;
    $g_iResultCount = 0&lt;br /&gt;
    &lt;br /&gt;
    GUICtrlSetData($g_idStatusBar, &amp;quot;결과가 초기화되었습니다.&amp;quot;)&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func SaveResults()&lt;br /&gt;
    If $g_iResultCount = 0 Then&lt;br /&gt;
        MsgBox(16, &amp;quot;오류&amp;quot;, &amp;quot;저장할 결과가 없습니다.&amp;quot;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
    &lt;br /&gt;
    Local $sSaveFile = FileSaveDialog(&amp;quot;검색 결과 저장&amp;quot;, @MyDocumentsDir, &amp;quot;텍스트 파일 (*.txt)|모든 파일 (*.*)&amp;quot;, $FD_PATHMUSTEXIST, &amp;quot;search_results.txt&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    If $sSaveFile &amp;lt;&amp;gt; &amp;quot;&amp;quot; Then&lt;br /&gt;
        Local $hFile = FileOpen($sSaveFile, $FO_OVERWRITE + $FO_CREATEPATH + $FO_UTF8_NOBOM)&lt;br /&gt;
        If $hFile = -1 Then&lt;br /&gt;
            MsgBox(16, &amp;quot;오류&amp;quot;, &amp;quot;파일을 생성할 수 없습니다.&amp;quot;)&lt;br /&gt;
            Return&lt;br /&gt;
        EndIf&lt;br /&gt;
        &lt;br /&gt;
        ; 헤더 정보&lt;br /&gt;
        FileWriteLine($hFile, &amp;quot;=== 텍스트 파일 검색 결과 ===&amp;quot;)&lt;br /&gt;
        FileWriteLine($hFile, &amp;quot;생성일시: &amp;quot; &amp;amp; @YEAR &amp;amp; &amp;quot;-&amp;quot; &amp;amp; @MON &amp;amp; &amp;quot;-&amp;quot; &amp;amp; @MDAY &amp;amp; &amp;quot; &amp;quot; &amp;amp; @HOUR &amp;amp; &amp;quot;:&amp;quot; &amp;amp; @MIN &amp;amp; &amp;quot;:&amp;quot; &amp;amp; @SEC)&lt;br /&gt;
        FileWriteLine($hFile, &amp;quot;검색 경로: &amp;quot; &amp;amp; $g_sBaseFolder)&lt;br /&gt;
        FileWriteLine($hFile, &amp;quot;총 결과: &amp;quot; &amp;amp; $g_iResultCount &amp;amp; &amp;quot;건&amp;quot;)&lt;br /&gt;
        FileWriteLine($hFile, &amp;quot;=&amp;quot; &amp;amp; StringRepeat(&amp;quot;=&amp;quot;, 80))&lt;br /&gt;
        FileWriteLine($hFile, &amp;quot;&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        ; 결과 데이터&lt;br /&gt;
        For $i = 0 To $g_iResultCount - 1&lt;br /&gt;
            FileWriteLine($hFile, &amp;quot;파일: &amp;quot; &amp;amp; $g_aResults[$i][0])&lt;br /&gt;
            FileWriteLine($hFile, &amp;quot;라인: &amp;quot; &amp;amp; $g_aResults[$i][2])&lt;br /&gt;
            FileWriteLine($hFile, &amp;quot;내용: &amp;quot; &amp;amp; $g_aResults[$i][3])&lt;br /&gt;
            FileWriteLine($hFile, &amp;quot;크기: &amp;quot; &amp;amp; $g_aResults[$i][4])&lt;br /&gt;
            FileWriteLine($hFile, &amp;quot;수정일: &amp;quot; &amp;amp; $g_aResults[$i][5])&lt;br /&gt;
            FileWriteLine($hFile, StringRepeat(&amp;quot;-&amp;quot;, 80))&lt;br /&gt;
        Next&lt;br /&gt;
        &lt;br /&gt;
        FileClose($hFile)&lt;br /&gt;
        &lt;br /&gt;
        MsgBox(64, &amp;quot;저장 완료&amp;quot;, &amp;quot;검색 결과가 저장되었습니다.&amp;quot; &amp;amp; @CRLF &amp;amp; $sSaveFile)&lt;br /&gt;
        &lt;br /&gt;
        ; 저장된 파일 열기 여부 확인&lt;br /&gt;
        Local $iResult = MsgBox(36, &amp;quot;파일 열기&amp;quot;, &amp;quot;저장된 파일을 열어보시겠습니까?&amp;quot;)&lt;br /&gt;
        If $iResult = 6 Then ; Yes&lt;br /&gt;
            ShellExecute($sSaveFile)&lt;br /&gt;
        EndIf&lt;br /&gt;
    EndIf&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func OpenSelectedFile()&lt;br /&gt;
    Local $iSelected = _GUICtrlListView_GetNextItem($g_idResultList)&lt;br /&gt;
    If $iSelected &amp;gt;= 0 Then&lt;br /&gt;
        Local $sFilePath = $g_aResults[$iSelected][0]&lt;br /&gt;
        Local $iLineNumber = $g_aResults[$iSelected][2]&lt;br /&gt;
        &lt;br /&gt;
        ; 파일 열기 옵션 제공&lt;br /&gt;
        Local $iChoice = MsgBox(35, &amp;quot;파일 열기&amp;quot;, &amp;quot;파일을 어떻게 열까요?&amp;quot; &amp;amp; @CRLF &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
                               &amp;quot;예(Y): 메모장으로 열기&amp;quot; &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
                               &amp;quot;아니오(N): 기본 프로그램으로 열기&amp;quot; &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
                               &amp;quot;취소: 파일 경로 복사&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        Switch $iChoice&lt;br /&gt;
            Case 6 ; Yes - 메모장&lt;br /&gt;
                Run(&amp;quot;notepad.exe &amp;quot; &amp;amp; &#039;&amp;quot;&#039; &amp;amp; $sFilePath &amp;amp; &#039;&amp;quot;&#039;)&lt;br /&gt;
                &lt;br /&gt;
            Case 7 ; No - 기본 프로그램&lt;br /&gt;
                ShellExecute($sFilePath)&lt;br /&gt;
                &lt;br /&gt;
            Case 2 ; Cancel - 경로 복사&lt;br /&gt;
                ClipPut($sFilePath)&lt;br /&gt;
                MsgBox(64, &amp;quot;복사 완료&amp;quot;, &amp;quot;파일 경로가 클립보드에 복사되었습니다.&amp;quot;)&lt;br /&gt;
        EndSwitch&lt;br /&gt;
    EndIf&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
; Oracle DBA를 위한 추가 유틸리티 함수들&lt;br /&gt;
&lt;br /&gt;
Func SearchOracleErrors()&lt;br /&gt;
    ; Oracle 에러 패턴들을 미리 정의&lt;br /&gt;
    Local $aOracleErrors[5] = [&amp;quot;ORA-\d{5}&amp;quot;, &amp;quot;TNS-\d{5}&amp;quot;, &amp;quot;RMAN-\d{5}&amp;quot;, &amp;quot;SP2-\d{4}&amp;quot;, &amp;quot;PLS-\d{5}&amp;quot;]&lt;br /&gt;
    &lt;br /&gt;
    ; 콤보박스에서 Oracle 관련 파일 선택&lt;br /&gt;
    ; 구현 생략...&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func SearchSQLPatterns()&lt;br /&gt;
    ; 자주 사용되는 SQL 패턴들&lt;br /&gt;
    Local $aSQLPatterns[6] = [&amp;quot;SELECT.*FROM&amp;quot;, &amp;quot;INSERT.*INTO&amp;quot;, &amp;quot;UPDATE.*SET&amp;quot;, &amp;quot;DELETE.*FROM&amp;quot;, &amp;quot;CREATE.*TABLE&amp;quot;, &amp;quot;ALTER.*TABLE&amp;quot;]&lt;br /&gt;
    &lt;br /&gt;
    ; 구현 생략...&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
</feed>