테마
📌 『튜너의 1년』 독자는 먼저
../README.md(고객사 카드)를 본다. 이 문서는 컨테이너를 하나만 쓰던 시절에 썼다. 컨테이너oracle-free는 모두ora-dist로 바꿔 읽는다. 그 밖에는 다를 게 없다.data/data_csv.zip을 같은 폴더에 압축 해제한 뒤 3단계부터 진행한다. 쓰는 화: 03 · 09.
『오라클 SQL 파워업』 실습 데이터셋 — 설치부터 실습까지 (전용 계정 pup)
이 폴더 하나만 보고 누구나 따라 할 수 있도록 만든 안내서다. Oracle이나 Docker를 처음 다뤄도 위에서부터 차례로 복사해 붙여 넣으면 된다. 도메인: 유통/리테일(상품 · 매장 · 판매 · 발주 · 입출고 · 재고), 테이블 18개. 대용량 테이블인 판매(CF10, 약 50만 건)와 판매상품(CF11, 약 110만 건)으로 조인, 인덱스, 집계 튜닝을 실습한다. 데이터 출처: 『오라클 SQL 파워업』(유일환) 실습 데이터(CSV). 책의 연습 쿼리 167개도 들어 있다(
03_연습쿼리_해설.sql, 쿼리마다 위에 설명과 의도 주석을 달았다). 🗺 스키마 전체 그림(ERD·조감도·인덱스): 데이터_조감도_ERD_인덱스.md
0. 이 폴더 구성
오라클SQL파워업/
├── README.md ← (지금 이 문서) 전체 안내·실행 순서
├── 01_pup_계정생성.sql ← [1단계] 전용 계정 pup 만들기 (sys로 실행)
├── 02_테이블생성.sql ← [2단계] 18개 테이블(CF01~CF18) 만들기 (pup로 실행)
├── 03_연습쿼리_해설.sql ← [실습] 책 연습쿼리 167개 + 쿼리별 설명·의도 주석 (pup로 골라 실행)
├── 참고_데이터모델_개념.md ← 18테이블 의미·배경 설명(읽을거리)
├── data/ ← [3·4단계] 적재용 데이터 (한 폴더에 모음)
│ ├── CF01.csv ~ CF18.csv · 실제 데이터(CP949 인코딩)
│ ├── CF01.ctl ~ CF18.ctl · SQL*Loader 적재 설정(인코딩/줄바꿈 자동 처리)
│ └── load_all.sh · 18개 한 번에 적재하는 스크립트
└── _원본백업/ ← 받은 원본 zip 보관(손대지 않아도 됨)핵심 흐름 한 줄:
pup 계정 생성(1) → 빈 테이블 18개 생성(2) → data 폴더를 DB 컨테이너로 복사(3) → SQL\*Loader로 CSV 적재(4) → 통계·검증(5) → 실습(6).
1. 미리 준비할 것 (전제)
- Docker Desktop이 실행 중이고 Oracle 컨테이너
oracle-free가 떠 있어야 한다.- 아직 없으면 00. Oracle 실습 환경 구축가이드의 옵션 B로 컨테이너부터 만든다.
- 확인:bash
docker ps --filter name=oracle-freeSTATUS가Up ... (healthy)면 된다. 멈춰 있으면docker start oracle-free로 띄운다.
- 접속 정보(이 문서 전체에서 사용):
항목 값 컨테이너 이름 oracle-free관리자(SYS) sys/oracleDB 서비스(PDB) FREEPDB1실습 계정(이 데이터셋) pup/pup(1단계에서 생성) - (선택) GUI로 쿼리하고 싶으면 SQL Developer나 SQLcl을 쓴다. 없어도 아래 명령줄(CLI)만으로 끝까지 할 수 있다.
💡 왜 CSV를 SQL*Loader로 넣나? 데이터가 INSERT문이 아니라 CSV 파일이라서 오라클 전용 고속 적재기인 SQL*Loader(
sqlldr)로 넣는다.sqlldr는 컨테이너 안에 있으므로 데이터 파일을 컨테이너 안으로 복사한 뒤 컨테이너 안에서 실행한다.
2. 1단계 — 전용 계정 pup 만들기 (SYS로)
명령 프롬프트(PowerShell/터미널)에서 호스트의 파일을 컨테이너의 sqlplus에 그대로 흘려보낸다.
bash
docker exec -i oracle-free sqlplus sys/oracle as sysdba < "01_pup_계정생성.sql"- 이 스크립트가 하는 일:
FREEPDB1로 전환 →pup/pup계정 생성 → 기본 권한 + 튜닝 측정 권한(SELECT_CATALOG_ROLE등, 실행계획/AUTOTRACE/V$조회용) 부여. - 정상이면
User created.,Grant succeeded.가 보인다. - (대안) SQL Developer에서
sys로 접속 →01_pup_계정생성.sql열기 → 전체 실행.
위 명령은 이 폴더 안에서 실행해야 한다(파일명을 상대 경로로 쓰기 때문이다). 폴더는 다음처럼 이동한다.
cd "<이 README가 있는 폴더>"
3. 2단계 — 빈 테이블 18개 만들기 (pup로)
bash
docker exec -i oracle-free sqlplus pup/pup@localhost:1521/FREEPDB1 < "02_테이블생성.sql"CF01(상품)~CF18(일별재고) 18개 테이블과 각 PK를 만든다. 보조 인덱스, 외래키, 뷰는 일부러 만들지 않았다. 인덱스는 실습에서 직접 추가한다.- 확인: PowerShell에서는
<<EOF(heredoc)를 쓸 수 없으므로 pup로 접속해 직접 조회한다.powershell접속되어docker exec -it oracle-free sqlplus pup/pup@localhost:1521/FREEPDB1SQL>가 뜨면 아래를 붙여 넣는다.sqlSELECT table_name FROM user_tables ORDER BY 1; -- CF01 ~ CF18 (18행)이면 성공 EXIT - (대안) SQL Developer에서
pup로 접속 →02_테이블생성.sql열기 → 전체 실행(F5).
📌 이 문서의 명령은 모두 PowerShell 기준이다. 파일 실행은 2단계처럼
... < "파일.sql"로 하면 된다. 다만 여러 줄 SQL을 즉석에서 넣을 때는<<EOF대신 ① 접속 후 붙여 넣기나 ②@" ... "@ | docker exec -i ...(PowerShell here-string)를 쓴다(아래 단계에 예시가 있다).
4. 3단계 — 데이터 파일을 컨테이너 안으로 복사
CSV와 ctl, load_all.sh는 지금 내 PC(호스트)의 data/ 폴더에 있다. 하지만 적재 도구 sqlldr는 DB 컨테이너 안에 있으므로 데이터를 컨테이너로 옮긴 뒤 거기서 실행한다. 그래서 복사 → 권한 → 적재 순서로 진행한다.
4-1. (선택) 복사 전 호스트 파일 개수 확인(PowerShell):
powershell
# CSV 18 + ctl 18 + load_all.sh = 37 이면 정상
(Get-ChildItem "<이 README가 있는 폴더>\data").Count4-2. 컨테이너로 복사(경로는 내 PC의 실제 위치로 바꾼다):
powershell
docker cp "<이 README가 있는 폴더>\data" oracle-free:/opt/oracle/pup_data- 호스트
data폴더의 내용물이 컨테이너의/opt/oracle/pup_data/로 들어간다. - 성공하면
Successfully copied ...가 뜬다. - ⚠️ 경로 주의: 우리가 쓰는 gvenzl 이미지의 oracle 홈은
/opt/oracle경로다./home/oracle이 아니다. 그 경로로 복사하면Could not find the file /home/oracle에러가 난다. 다른 이미지를 쓴다면docker exec oracle-free bash -lc 'echo $HOME'으로 홈을 확인해 그 아래에 복사한다.
4-3. 잘 들어갔는지 확인:
powershell
docker exec oracle-free ls /opt/oracle/pup_dataCF01.csv CF01.ctl ... CF18.csv CF18.ctl load_all.sh가 보이면 된다.
4-4. ★ 소유권 변경(빠뜨리면 5단계 적재가 실패한다):
powershell
docker exec -u 0 oracle-free chown -R oracle:oinstall /opt/oracle/pup_data- 왜 필요한가:
docker cp로 들어온 파일은 root 소유다. 그런데sqlldr는 DB의 oracle 계정으로 동작해서 root 파일을 읽지 못하고SQL*Loader-704 / KUP-04...오류를 낸다. 소유권을 oracle로 바꿔야 읽을 수 있다. - 확인:powershell각 줄의 소유자가
docker exec oracle-free ls -l /opt/oracle/pup_dataoracle oinstall이면 된다.
흔한 문제
no such file or directory(복사 원본 경로 오타): 경로를 내 PC의 실제 위치로 고친다. 현재 위치는 PowerShell에서pwd(=Get-Location)로 확인한다.- 예전에 한 번 복사해서
/opt/oracle/pup_data가 이미 있고 그 안에data/폴더가 또 생겼다면, 먼저 비우고 다시 복사한다.docker exec -u 0 oracle-free rm -rf /opt/oracle/pup_data를 실행한 뒤 4-2부터 다시 한다.
5. 4단계 — 데이터 적재 (SQL*Loader 실행)
5-1. 컨테이너 안으로 들어가서 폴더로 이동:
powershell
docker exec -it oracle-free bash프롬프트가 bash-4.4$나 [oracle@xxxx pup_data]$처럼 바뀐다. 지금부터는 컨테이너 안의 리눅스 셸이다(PowerShell이 아니다). 이어서 폴더로 이동한다.
bash
cd /opt/oracle/pup_data5-2. 일괄 적재 실행: 인자 pup은 pup 계정의 비밀번호다(1단계에서 정한 값).
bash
sh load_all.sh pupCF01→CF18순서로sqlldr가 18번 자동으로 실행된다. 비밀번호를 인자로 줬으니 따로 묻지 않는다.
5-3. 정상 출력 예시: 테이블마다 이렇게 지나간다.
================= loading CF01 =================
Path used: Direct
Table CF01:
1098 Rows successfully loaded.
...
================= loading CF11 ================= ← 가장 큼(약 110만), 수십 초~1분+
Table CF11:
1108080 Rows successfully loaded.
...
=== 완료. 상세는 각 CFxx.log, 거부행은 CFxx.bad 확인 ===핵심은 테이블마다 나오는 NNN Rows successfully loaded. 줄이다. 숫자가 0이 아니면 적재된 것이다.
5-4. 적재 결과 한 번에 점검(아직 컨테이너 셸 안에서):
bash
grep -E "successfully loaded|not loaded" CF*.log- 모든 줄이
... Rows successfully loaded.이고0 Rows not loaded이면 완벽하다. - 거부된 행이 있으면(예:
... not loaded) 그 테이블의 로그에서 사유를 본다(tail -30 CF11.log). 거부된 원본 줄은CF11.bad에 있다.
5-5. 컨테이너 셸에서 나오기:
bash
exit다시 PowerShell 프롬프트(PS C:\...>)로 돌아온다.
⏱ 소요 시간: 작은 테이블은 순식간에 끝나고, CF10(50만)과 CF11(110만), CF15는 수십 초 걸린다. 전체는 1~2분이다. DIRECT=TRUE(직접 경로 적재)라서 일반 INSERT보다 훨씬 빠르다.
흔한 에러
| 메시지 | 원인 / 해결 |
|---|---|
SQL*Loader-704 ... KUP-04... | 4-4 chown 누락 → exit 후 4-4 실행, 다시 5단계 |
KUP-04063: unable to open file | 위치/파일 문제. pwd가 /opt/oracle/pup_data인지, ls로 csv/ctl이 있는지 확인 |
ORA-01017 invalid username/password | pup 비번 틀림 → sh load_all.sh <진짜비번> |
특정 표만 0 Rows/다수 .bad | 그 테이블 CSV의 인코딩·구분자 문제. CFxx.log에서 사유 확인 |
💡 ctl이 자동으로 처리하는 것(그냥 INSERT가 아니라 sqlldr를 쓰는 이유): CSV는 한글 CP949(
CHARACTERSET KO16MSWIN949), 윈도우 CRLF 줄바꿈(STR x'0d0a'로 줄 끝\r제거), 첫 줄이 헤더(SKIP=1)이고, 값이"로 감싸여 있다(OPTIONALLY ENCLOSED BY '"'). 이 처리가 없으면 한글이 깨지거나 날짜 컬럼 적재가 실패한다. 🔁 다시 적재(중복 PK 에러 방지): pup로TRUNCATE TABLE CFxx;를 실행한 뒤 5단계를 다시 한다. 또는 11장대로 전체를 다시 설치한다.
6. 5단계 — 통계 수집 + 행수 검증 (필수)
적재가 끝나면 반드시 통계를 수집한다. 원본에는 통계가 없어서, 수집하지 않으면 옵티마이저가 행수를 모르는 채로 엉뚱한 실행계획을 세운다.
6-1. 통계 수집 + 행수 보기: 호스트(PowerShell)에서 pup로 접속해 실행한다. 둘 중 편한 방법을 고른다.
(a) 대화형(가장 쉬움): 접속한 뒤 붙여 넣는다.
powershell
docker exec -it oracle-free sqlplus pup/pup@localhost:1521/FREEPDB1SQL>가 뜨면 다음을 붙여 넣는다.
sql
EXEC DBMS_STATS.GATHER_SCHEMA_STATS('PUP', degree=>4);
SET PAGESIZE 50
COL table_name FORMAT A24
SELECT table_name, num_rows FROM user_tables ORDER BY num_rows DESC NULLS LAST;
EXIT(b) PowerShell here-string으로 한 번에(파일 없이):
powershell
@"
EXEC DBMS_STATS.GATHER_SCHEMA_STATS('PUP', degree=>4);
SET PAGESIZE 50
SELECT table_name, num_rows FROM user_tables ORDER BY num_rows DESC NULLS LAST;
EXIT
"@ | docker exec -i oracle-free sqlplus -s pup/pup@localhost:1521/FREEPDB1
@"와 닫는"@는 각각 줄 맨 앞에 와야 한다(PowerShell here-string 규칙).
6-2. 기대 행수(대략이며, 생성 옵션에 따라 조금씩 다르다):
| 테이블 | 의미 | 대략 행수 |
|---|---|---|
| CF11 | 판매상품 | ~110만 |
| CF10 | 판매 | ~50만 |
| CF15 | 입출고상품 | 수만 |
| CF13 | 발주상품 | 수천~수만 |
| CF18 | 일별상품재고 | 수천~수만 |
| CF01~CF09, CF16, CF17 | 상품·카테고리·거래처·매장·사원·코드 등 마스터 | 수~수백 |
CF11과 CF10이 압도적으로 크면 정상이다. 특정 테이블만 비어 있으면 5-4의 로그를 다시 확인한다. |
7. 6단계 — 접속해서 첫 쿼리 + 측정 켜기
7-1. 접속:
- CLI:
docker exec -it oracle-free sqlplus pup/pup@localhost:1521/FREEPDB1 - SQL Developer: 새 접속
PUP_local(Hostlocalhost, Port1521, ServiceFREEPDB1, 사용자pup/pup). 실행계획은 F6(Autotrace)으로 본다.
7-2. 데이터 확인: 상품 × 카테고리 조인
sql
SELECT i.ITM_NO, i.ITM_NM, c.CTG_NM
FROM CF01 i JOIN CF02 c ON c.CTG_NO = i.CTG_NO
WHERE ROWNUM <= 10;7-3. 측정 켜기: 1단계에서 pup에 측정 권한(SELECT_CATALOG_ROLE 등)을 줬으므로 바로 된다.
sql
SET TIMING ON
SET AUTOTRACE ON -- 실행계획 + consistent gets(논리 블록 읽기) 표시
SELECT COUNT(*) FROM CF11; -- 110만 풀스캔의 비용을 눈으로
SET AUTOTRACE OFF
User has no SELECT privilege on V$...가 뜨면 1단계 권한이 들어가지 않은 것이다.01_pup_계정생성.sql을 다시 실행한 뒤 다시 접속한다(자세한 내용은 00 구축가이드 B-4).
➡ 이제 준비가 끝났다. 9장 실습 메뉴(대용량 조인 NL vs HASH, 인덱스 손익, 형변환 함정, 계층 질의)로 넘어간다.
8. 데이터 모델 한눈에 (18테이블)
| 테이블 | 의미 | PK | 규모 | 관계(주요 연결) |
|---|---|---|---|---|
| CF01 | 상품 | ITM_NO | 소 | CTG_NO→CF02, PTNR_NO→CF06 |
| CF02 | 카테고리 | CTG_NO | 소 | UP_CTG_NO→CF02(계층) |
| CF03 | 상품원재료 | ITM_NO,MTRL_ITM_NO | 소 | ITM_NO→CF01 |
| CF04 | 상품옵션 | ITM_NO,OPT_ITM_NO | 소 | ITM_NO→CF01 |
| CF05 | 상품가격(적용일자별) | ITM_NO,UPRC_KN_CD,APL_DT | 소 | ITM_NO→CF01 |
| CF06 | 거래처 | PTNR_NO | 소 | |
| CF07 | 매장 | SHP_NO | 소 | |
| CF08 | 부서 | DEPT_NO | 소 | UP_DEPT_NO→CF08(계층) |
| CF09 | 사원 | EMP_NO | 소 | DEPT_NO→CF08 |
| CF10 | 판매(헤더) | SLE_NO | ~50만 | SHP_NO→CF07 |
| CF11 | 판매상품(상세) | SLE_NO,ITM_NO | ~110만 | SLE_NO→CF10, ITM_NO→CF01 |
| CF12 | 발주 | ORD_NO | 중 | PTNR_NO→CF06 |
| CF13 | 발주상품 | ORD_NO,ITM_NO | 중 | ORD_NO→CF12 |
| CF14 | 입출고 | INOUT_NO | 중 | ORD_NO→CF12 |
| CF15 | 입출고상품 | INOUT_NO,ITM_NO | 중 | INOUT_NO→CF14 |
| CF16 | 공통코드 | CLS_CD,CD | 소 | |
| CF17 | 재고집계(일자) | INV_BS_DT | 소 | |
| CF18 | 일별상품재고집계 | SHP_NO,ITM_NO,INV_BS_DT | 중 | SHP_NO→CF07, ITM_NO→CF01 |
자세한 컬럼 의미는 참고_데이터모델_개념.md에 있다. 날짜성 컬럼
SLE_DT/ORD_DT/APL_DT/INV_BS_DT/INOUT_DT는 문자형VARCHAR2(8)에 'YYYYMMDD' 형식으로 들어 있다(진짜 DATE는REG_DTT/MOD_DTT뿐이다).
9. ★ 실습 메뉴 (대용량 CF10/CF11로 직접 측정)
공통:
SET AUTOTRACE ON또는/*+ GATHER_PLAN_STATISTICS */+SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(FORMAT=>'ALLSTATS LAST')). 인덱스나 조인을 바꾸기 전과 후의consistent gets(Buffers)를 비교해야 튜닝 효과를 입증할 수 있다.
A. 대용량 조인 — NL vs HASH
sql
-- 판매(50만) ⋈ 판매상품(110만) → 일자별 매출. 대용량이라 해시 조인이 정석.
SET AUTOTRACE ON
SELECT /*+ USE_HASH(s d) */ s.SLE_DT, SUM(d.SLE_QTY * d.SLE_UPRC) AS amt
FROM CF10 s JOIN CF11 d ON d.SLE_NO = s.SLE_NO
WHERE s.SLE_DT BETWEEN '20230101' AND '20230131'
GROUP BY s.SLE_DT ORDER BY s.SLE_DT;
-- 비교: /*+ USE_NL(s d) */ 로 바꿔 재실행 → consistent gets 폭증(대용량 NL의 함정) 관찰.연계 학습: 딥다이브 강의5 조인 (공개 사이트에는 없음) · Hero Lab08 (공개 사이트에는 없음)
B. 인덱스 손익 — 상품별 판매 조회 (CF11 PK 선두가 아님)
sql
-- CF11 PK = (SLE_NO, ITM_NO). ITM_NO '단독' 조건은 PK 선두가 아니라 비효율(FULL/SKIP).
SET AUTOTRACE ON
SELECT COUNT(*), SUM(SLE_QTY) FROM CF11 WHERE ITM_NO = 1098; -- [전]
CREATE INDEX CF11_X_ITM ON CF11(ITM_NO); -- 보조 인덱스 직접 생성
EXEC DBMS_STATS.GATHER_TABLE_STATS('PUP','CF11');
SELECT COUNT(*), SUM(SLE_QTY) FROM CF11 WHERE ITM_NO = 1098; -- [후] INDEX RANGE SCAN
-- 관찰: consistent gets 수천 → 수십. 끝나면 DROP INDEX CF11_X_ITM; 로 원복 가능.연계 학습: 딥다이브 강의4 인덱스 튜닝 (공개 사이트에는 없음)
C. 날짜 문자열 범위 & 형변환 함정
sql
SELECT COUNT(*) FROM CF10 WHERE SLE_DT BETWEEN '20230601' AND '20230630'; -- 문자 비교(정상)
-- 함정: WHERE TO_NUMBER(SLE_DT) = ... 처럼 컬럼을 가공하면 인덱스를 못 탑니다(컬럼 가공 금지 원칙).
-- (CREATE INDEX CF10_X_DT ON CF10(SLE_DT); 후 위 범위조회 전후 비교)D. 계층 질의 (카테고리/부서 self join)
sql
SELECT LPAD(' ', 2*(LEVEL-1)) || CTG_NM AS tree, CTG_NO, UP_CTG_NO
FROM CF02
START WITH UP_CTG_NO IS NULL
CONNECT BY PRIOR CTG_NO = UP_CTG_NO;+ 책 연습쿼리 167개
03_연습쿼리_해설.sql에 책의 연습 쿼리 167개가 들어 있고, 쿼리마다 위에 [쿼리 N] 설명 / 의도 / 대상 주석을 달았다. pup으로 골라 실행하면서 위의 측정 도구로 실행계획과 Buffers를 확인한다.
+ 시험준비 8주차 커리큘럼 연계 (PUP 평행 실습)
시험준비 각 주차 폴더에 같은 PUP(유통) 데이터로 평행 실습하는 파일(0N_실습_PUP유통.sql)을 두었다. 주차 학습을 마친 뒤 이 파일로 유통 도메인에 적용해 보자(기존 0N_실습.sql은 스키마 A/B용이고, 이 파일은 PUP용이다).
| 주차 | 주제 | PUP 실습 파일 |
|---|---|---|
| 01 | 데이터모델링·SQL기본 | 01_실습_PUP유통.sql (공개 사이트에는 없음) |
| 02 | SQL활용 심화 | 02_실습_PUP유통.sql (공개 사이트에는 없음) |
| 03 | 수행구조·분석도구 | 03_실습_PUP유통.sql (공개 사이트에는 없음) |
| 04 | 인덱스 튜닝 | 04_실습_PUP유통.sql (공개 사이트에는 없음) |
| 05 | 조인 튜닝 | 05_실습_PUP유통.sql (공개 사이트에는 없음) |
| 06 | 옵티마이저·고급튜닝 | 06_실습_PUP유통.sql (공개 사이트에는 없음) |
| 07 | Lock·트랜잭션·실기 | 07_실습_PUP유통.sql (공개 사이트에는 없음) |
| 08 | 총정리·모의고사 | 08_실습_PUP유통.sql (공개 사이트에는 없음) |
10. 트러블슈팅
| 증상 | 원인 / 해결 |
|---|---|
docker: ... cannot connect | Docker Desktop이 안 떠 있음 → Docker Desktop 실행 후 docker start oracle-free. |
2단계 ORA-01017 invalid username/password | 1단계 pup 생성이 안 됨/비번 불일치 → 1단계 재실행, 또는 sys로 ALTER USER pup IDENTIFIED BY pup;. |
4단계 SQL*Loader-704 / KUP-04... 권한 | 3-(2) chown 누락 → docker exec -u 0 oracle-free chown -R oracle:oinstall /opt/oracle/pup_data 재실행. |
한글이 ???/깨짐 | ctl의 CHARACTERSET KO16MSWIN949(이미 적용). 조회 클라이언트 인코딩도 UTF-8/MSWIN949로. |
날짜 컬럼 ORA-01858/01861 | 줄바꿈(CRLF) 문제 → ctl의 INFILE '...' "STR x'0d0a'"(이미 적용). |
일부 행이 .bad로 빠짐 | CFxx.log에서 사유 확인(길이/형식). |
| 적재 후 실행계획이 이상 | 6단계 통계수집 누락 → GATHER_SCHEMA_STATS('PUP'). |
| 다시 적재하고 싶음(중복 PK) | pup로 TRUNCATE TABLE CFxx; 후 다시 4단계, 또는 전체 재설치(아래). |
11. 정리 / 다시 설치
sql
-- (pup) 데이터만 비우기: TRUNCATE TABLE CF01; ... CF18;bash
# (완전 삭제) sys로: 계정·테이블 통째 제거
docker exec -i oracle-free sqlplus sys/oracle as sysdba <<'EOF'
ALTER SESSION SET CONTAINER = FREEPDB1;
DROP USER pup CASCADE;
EXIT
EOF
# 이후 다시 1→5단계 반복하면 재설치됩니다. 원본 데이터는 _원본백업/ 에 zip으로 보관돼 있습니다.요약: 1)
01_pup_계정생성.sql(sys) → 2)02_테이블생성.sql(pup) → 3)docker cp data+chown→ 4)load_all.sh→ 5) 통계수집·검증 → 6) 실습. 막히면 10장 트러블슈팅을 본다.