CSV 파일로 외부 테이블 만들기

빅 데이터Beginner
지금 연습하기

소개

첫 Hive 실습에서는 준비된 테이블로 주문 행을 바로 조회했습니다. 이제 소매업체가 CSV 파일을 제공했지만 저장소에 있는 파일이 곧 Hive가 조회할 수 있는 테이블은 아닙니다. 필드를 설명하고 데이터가 들어 있는 디렉터리를 지정해야 합니다.

**외부 테이블(external table)**은 테이블 정의와 별도로 관리하는 데이터를 참조하는 테이블 정의입니다. **스키마(schema)**는 열 이름과 자료형을 지정하고 LOCATION은 저장된 파일을 가리킵니다. 정의를 만들어도 파일이 업로드되거나 내용이 다시 쓰이지 않습니다. Hive는 Metastore에 테이블 메타데이터를 보관하고, 기록은 저장소에 그대로 둡니다.

간단한 CSV 원본 두 개를 업로드하고 첫 번째를 명시적 스키마로 연결한 뒤 두 번째로 연결을 바꾸며, 일회용 외부 테이블 정의만 삭제합니다. 마지막 실험은 이 과정의 Hive 버전에서 명시적인 데이터 보존 속성을 사용합니다. 가능한 모든 Hive 테이블을 삭제해도 파일이 보존된다는 약속이 아닙니다. 서비스와 로컬 입력은 독립적으로 준비되며 Hadoop 과정이나 프로그래밍 언어 지식은 필요하지 않습니다. 터미널을 사용하세요.

원본 파일을 확인하고 업로드하기

이 단계에서는 CSV 형식을 확인하고 제공된 파일 두 개를 별도의 HDFS 디렉터리에 복사합니다.

로컬 프로젝트에서 시작합니다.

cd /home/labex/project

CSV 파일은 쉼표로 필드를 구분합니다. 이 학습용 파일은 값이 단순하며 값 내부의 쉼표, 따옴표로 묶은 여러 줄 텍스트 또는 이스케이프된 구분자가 없습니다. 여기서 사용하는 간단한 테이블 형식은 이 입력에 맞게 선택한 것이며 모든 CSV 변형을 처리하는 범용 파서는 아닙니다.

로컬 head -n 4는 처음 네 줄을 표시합니다.

head -n 4 /home/labex/project/data/orders-a.csv

헤더 뒤에 주문 기록 세 개가 보여야 합니다.

order_id,customer_id,product_id,quantity,unit_price,order_status,order_date
o1,c1,p1,2,10.00,paid,2026-10-14
o2,c2,p2,1,25.00,paid,2026-10-14
o3,c1,p1,1,10.00,pending,2026-10-14

wc -l로 줄바꿈으로 끝나는 줄을 셉니다.

wc -l /home/labex/project/data/orders-a.csv /home/labex/project/data/orders-b.csv

원본 A는 일곱 줄이지만 업무 기록은 여섯 개이고 원본 B는 세 줄이지만 기록은 두 개입니다. 헤더가 주문 행이 되어서는 안 됩니다.

HDFS는 준비된 Hive 서비스가 사용하는 별도 파일 시스템입니다. hdfs dfs 클라이언트가 파일을 관리합니다. -mkdir -p는 지정된 HDFS 디렉터리와 없는 상위 디렉터리를 만듭니다.

hdfs dfs -mkdir -p /user/labex/hive-mapping/source-a /user/labex/hive-mapping/source-b

-put 연산은 로컬 원본을 HDFS 대상에 복사하고 원본은 그대로 둡니다.

hdfs dfs -put /home/labex/project/data/orders-a.csv /user/labex/hive-mapping/source-a/orders.csv

두 번째 원본을 별도 디렉터리에 업로드합니다.

hdfs dfs -put /home/labex/project/data/orders-b.csv /user/labex/hive-mapping/source-b/orders.csv

성공한 업로드는 대개 출력이 없습니다. 대상이 이미 있다면 덮어쓰기를 결정하기 전에 확인하세요. 명령을 반복하려고 원본 데이터를 삭제하지 마세요. -cat으로 첫 HDFS 파일을 읽습니다.

hdfs dfs -cat /user/labex/hive-mapping/source-a/orders.csv

헤더와 여섯 기록이 여전히 있습니다. 업로드는 바이트를 보존하고 테이블 정의는 Hive에 그 해석 방법을 알려 줍니다.

파일과 테이블의 연결 정의하기

이 단계에서는 필드, 자료형, 디렉터리 위치와 헤더 처리를 명시한 외부 테이블을 만듭니다.

원본 필드 순서와 테이블 열 순서는 같아야 합니다. 식별자와 상태에는 STRING, 정수 수량에는 INT, 고정 소수점 가격에는 DECIMAL(10,2), ISO 형식 날짜에는 DATE를 사용합니다. 십진수 가격은 이후 금액 계산에서 이진 부동 소수점 근삿값 문제를 피하게 해 줍니다.

CREATE DATABASE IF NOT EXISTS는 필요하면 이름 공간을 만듭니다. 이어서 CREATE EXTERNAL TABLE이 테이블 정의를 등록합니다. 형식 절은 쉼표로 구분된 텍스트를 지정합니다. LOCATION은 디렉터리를 가리키므로 관련 없는 파일과 백업은 밖에 두세요. 두 속성은 파일마다 헤더 한 줄을 건너뛰고, 이 환경에서 DROP 시 외부 테이블 데이터의 영구 삭제를 명시적으로 비활성화합니다.

SQL 파일 전체를 저장합니다. 첫 실습에서처럼 셸 히어 도큐먼트는 끝의 SQL 구분자까지 줄을 문자 그대로 씁니다.

cat > /home/labex/project/queries/create-orders.sql <<'SQL'
CREATE DATABASE IF NOT EXISTS retail_mapping;
USE retail_mapping;
CREATE EXTERNAL TABLE raw_orders (
  order_id STRING,
  customer_id STRING,
  product_id STRING,
  quantity INT,
  unit_price DECIMAL(10,2),
  order_status STRING,
  order_date DATE
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
STORED AS TEXTFILE
LOCATION 'hdfs://localhost:9000/user/labex/hive-mapping/source-a'
TBLPROPERTIES ('skip.header.line.count'='1', 'external.table.purge'='false');
SQL

Beeline으로 실행합니다. 연결 및 -f 파일 옵션은 첫 실습과 같습니다.

beeline -u 'jdbc:hive2://localhost:10000/default' -n labex --silent=true -f /home/labex/project/queries/create-orders.sql

이 문장들은 테이블 같은 객체를 정의하는 SQL인 **DDL(Data Definition Language, 데이터 정의 언어)**입니다. DDL이 성공하면 주문 행을 반환하는 대신 메타데이터가 만들어집니다. 이미 완료한 실행으로 테이블이 있다면 메시지를 없애려고 삭제하지 말고 확인하세요.

DESCRIBE FORMATTED에는 열뿐 아니라 테이블 유형과 위치도 포함됩니다.

beeline -u 'jdbc:hive2://localhost:10000/default' -n labex -e 'DESCRIBE FORMATTED retail_mapping.raw_orders;'

EXTERNAL_TABLE과 /hive-mapping/source-a로 끝나는 위치를 찾으세요. 이것은 메타데이터 관찰 결과이며 모든 기록이 올바르게 해석되었다는 증거는 아닙니다. 쿼리도 사용하세요. COUNT(*)는 테이블 행 수를 세므로 빠르게 연결을 확인할 수 있습니다. AS order_count는 계산 결과에 **별칭(alias)**이라는 열 이름을 붙입니다. 원본 필드 이름이나 저장된 기록을 바꾸지는 않습니다.

beeline -u 'jdbc:hive2://localhost:10000/default' -n labex -e 'SELECT COUNT(*) AS order_count FROM retail_mapping.raw_orders; SELECT order_id, quantity, unit_price, order_date FROM retail_mapping.raw_orders ORDER BY order_id LIMIT 2;'

개수는 파일의 텍스트 줄 수인 일곱이 아니라 6이어야 합니다. 처음 두 기록은 ID o1, o2, 수량 2, 1, 수치상 10.00, 25.00인 가격과 날짜 2026-10-14를 포함해야 합니다. 헤더 처리와 의미 있는 자료형의 열을 모두 검증했습니다.

같은 테이블을 새 원본에 연결하기

이 단계에서는 테이블 위치를 바꾸고 원본 파일을 옮기지 않아도 행이 어떻게 달라지는지 예상합니다.

원본 B는 필드가 같지만 주문 o7과 o8이 들어 있습니다. 메타데이터를 바꾸기 전에 저장된 파일을 읽습니다.

hdfs dfs -cat /user/labex/hive-mapping/source-b/orders.csv

헤더와 두 기록이 보여야 합니다. 테이블을 이 디렉터리로 연결한 뒤 행 수가 어떻게 될지 예상하세요. 여섯이 아니라 둘입니다. ALTER TABLE ... SET LOCATION은 테이블 정의의 디렉터리 참조를 바꿉니다. 이전 데이터를 새 디렉터리에 복사하거나 두 데이터를 합치지는 않습니다.

이 메타데이터 변경을 저장합니다.

cat > /home/labex/project/queries/map-second-source.sql <<'SQL'
ALTER TABLE retail_mapping.raw_orders SET LOCATION 'hdfs://localhost:9000/user/labex/hive-mapping/source-b';
SQL

실행합니다.

beeline -u 'jdbc:hive2://localhost:10000/default' -n labex --silent=true -f /home/labex/project/queries/map-second-source.sql

메타데이터와 데이터를 다시 확인합니다.

beeline -u 'jdbc:hive2://localhost:10000/default' -n labex -e 'DESCRIBE FORMATTED retail_mapping.raw_orders; SELECT COUNT(*) AS order_count FROM retail_mapping.raw_orders; SELECT order_id, quantity, unit_price FROM retail_mapping.raw_orders ORDER BY order_id;'

새 source-b 위치, 개수 2, o7과 o8 행을 찾으세요. 수량은 1과 2, 가격은 수치상 50.00과 15.00입니다. 열 정의는 바뀌지 않았고 그 정의로 읽는 파일만 바뀌었습니다.

이전 파일을 읽어 연결 변경으로 사라지지 않았는지 확인합니다.

hdfs dfs -cat /user/labex/hive-mapping/source-a/orders.csv

헤더와 여섯 기록이 여전히 있어야 합니다. 파일의 존재와 테이블에서 보이는지는 다른 사실입니다. 이 테이블이 이제 원본 B를 읽어도 원본 A는 여전히 존재합니다.

일회용 테이블 정의만 삭제하기

이 단계에서는 보존한 첫 원본에 외부 테이블 정의를 만들었다가 제거하되 데이터와 주 테이블은 유지합니다.

테이블 정의 삭제와 데이터 삭제는 다른 작업입니다. 이 실험에서는 별도 이름인 disposable_orders와 명시적인 외부 데이터 보존 속성을 사용합니다. 이제 원본 B를 읽는 raw_orders는 계속 사용할 수 있게 유지합니다.

같은 스키마로 원본 A에 일회용 정의를 만듭니다.

cat > /home/labex/project/queries/create-disposable.sql <<'SQL'
CREATE EXTERNAL TABLE retail_mapping.disposable_orders (
  order_id STRING,
  customer_id STRING,
  product_id STRING,
  quantity INT,
  unit_price DECIMAL(10,2),
  order_status STRING,
  order_date DATE
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ','
STORED AS TEXTFILE
LOCATION 'hdfs://localhost:9000/user/labex/hive-mapping/source-a'
TBLPROPERTIES ('skip.header.line.count'='1', 'external.table.purge'='false');
SQL

정의를 실행합니다.

beeline -u 'jdbc:hive2://localhost:10000/default' -n labex --silent=true -f /home/labex/project/queries/create-disposable.sql

두 테이블 이름이 모두 있는지 확인합니다.

beeline -u 'jdbc:hive2://localhost:10000/default' -n labex -e 'SHOW TABLES IN retail_mapping;'

disposable_orders와 raw_orders가 보여야 합니다. DROP TABLE은 테이블 정의를 제거합니다. 여기서는 purge 속성을 명시적으로 false로 둔 일회용 외부 테이블만 대상으로 합니다.

beeline -u 'jdbc:hive2://localhost:10000/default' -n labex -e 'DROP TABLE retail_mapping.disposable_orders; SHOW TABLES IN retail_mapping;'

raw_orders만 남아야 합니다. 이제 원본 파일을 별도로 확인합니다.

hdfs dfs -cat /user/labex/hive-mapping/source-a/orders.csv

헤더와 여섯 기록이 남아 있어야 합니다. 관찰한 이 동작은 이번 외부 테이블 구성에 해당합니다. 관리형 테이블이나 purge 속성이 다른 외부 테이블은 삭제 시 데이터를 제거할 수 있습니다. 이 실험을 보편적인 삭제 규칙으로 일반화하지 마세요.

주 테이블이 여전히 원본 B를 읽는지 확인합니다.

beeline -u 'jdbc:hive2://localhost:10000/default' -n labex -e 'SELECT order_id FROM retail_mapping.raw_orders ORDER BY order_id;'

o7과 o8이 여전히 보여야 합니다. 초보자가 자주 혼동하는 세 작업을 구분했습니다. 바이트 업로드, 메타데이터 등록 및 변경, 정의 제거입니다. 다음에는 올바르게 연결된 행으로 조건을 필터링하고 누락값을 처리합니다.

요약

간단한 CSV 원본을 업로드하고 필드 순서와 자료형을 선언했으며, 헤더를 건너뛰고 명시적인 HDFS 디렉터리에 외부 테이블을 등록했습니다. 메타데이터와 실제 기록을 모두 확인하고 원본 파일 어느 쪽도 옮기지 않은 채 테이블 위치를 변경했습니다.

마지막으로 명시적인 보존 설정 아래에서 일회용 외부 테이블 정의를 제거하고 파일과 주 테이블이 계속 사용 가능한지 확인했습니다. 다음에는 신뢰할 수 있는 연결을 바탕으로 행을 선택하고 누락값을 처리합니다.