从 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 文件。与第一个实验一样,Shell 的 here-document 将结束 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

这些是 DDL(数据定义语言,Data Definition Language)语句,也就是定义表等对象的 SQL 语句。成功的 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 目录上注册外部表。你同时检查了元数据和实际记录,再修改表位置,没有移动任何源文件。

最后,你在明确的保留设置下移除了临时外部表定义,并确认文件和主表仍然可用。接下来,你将在可信映射的基础上选择行并处理缺失值。