共享单车骑行数据查询实验¶
本实验围绕某城市共享单车运营数据,完成 6 个查询任务,与课件《第 5 章 广电用户账单与订单数据查询进阶》的知识点一一对应,但业务场景与数据全部替换,可直接作为课堂实验或课后实训使用。
| 实验任务 | 对应知识点 |
|---|---|
| 任务 1 订单消费档次划分 | 条件函数、CAST 类型转换函数、DISTINCT、ORDER BY |
| 任务 2 统计用户每年骑行应付总额 | 字符函数(SUBSTR、CONCAT、LOWER 等)、分组聚合 |
| 任务 3 统计用户每月骑行应付总额 | 日期获取类与日期计算类函数 |
| 任务 4 统计用户月均骑行实付金额 | 数学函数(四则运算、ROUND、FLOOR、CEIL) |
| 任务 5 查询电助力订单的用户城市 | 内连接、外连接、左半开连接、UNION ALL |
| 任务 6 抽样统计用户骑行订单 | 桶表、TABLESAMPLE 抽样查询 |
一. 实验目的¶
- 掌握 Hive 内置函数的查看方法,能按需求选用条件、字符、日期、数学类函数。
- 能使用
CASE条件函数与CAST类型转换函数完成数据分档。 - 能使用日期函数从时间字符串中取出年、月并完成按年、按月的分组统计。
- 能使用数学函数计算实付金额与月均值。
- 理解内连接、外连接与左半开连接的区别,能按业务场景选用正确的连接类型。
- 能创建桶表并使用
TABLESAMPLE完成抽样查询。
二. 实验环境¶
- Hadoop 3.x 集群(或伪分布式),Hive 3.x,HiveServer2 已启动。
- 客户端:
beeline -u jdbc:hive2://localhost:10000 -n zhangsan。 - 数据文件存放于本地
/opt/data/目录(可自行调整,注意LOAD DATA LOCAL读的是客户端本地路径)。
三. 实验数据准备¶
1. 数据表及关系¶
实验共 5 张表,均存放在数据库 sharedbike 中。
| 表名 | 说明 | 主要字段 |
|---|---|---|
bike_users |
用户表 | user_id、user_name、member_level、city、reg_time |
bike_info |
单车信息表 | bike_id、model、park_area、put_date |
bike_orders |
骑行订单表(42 条) | order_id、user_id、bike_id、prod_type、prod_name、order_time、cost |
bike_bills |
骑行账单表(42 条) | bill_id、user_id、fee_code、bill_time、should_pay、favour_fee |
bike_recharge |
账户充值表(12 条) | user_id、recharge_amt、recharge_time |
bike_orders记录用户下单购买的产品,订单金额cost为STRING类型(模拟原始数据),使用前需要转换。bike_bills记录每次骑行产生的费用明细,实付金额 = 应付金额should_pay− 优惠金额favour_fee。同一用户同一月份可有多条不同费用科目(fee_code)的明细。- 订单表与账单表通过
user_id关联用户表,订单表通过bike_id关联单车表。 - 数据刻意设计为:用户
U011只充值、从未下单与骑行;用户U012有骑行账单但没有订单记录,供外连接与左半开连接实验对比。
2. 建库与建表¶
CREATE TABLE bike_users(
user_id STRING,
user_name STRING,
member_level STRING,
city STRING,
reg_time STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ',';
CREATE TABLE bike_info(
bike_id STRING,
model STRING,
park_area STRING,
put_date STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ',';
CREATE TABLE bike_orders(
order_id STRING,
user_id STRING,
bike_id STRING,
prod_type STRING,
prod_name STRING,
order_time STRING,
cost STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ',';
CREATE TABLE bike_bills(
bill_id STRING,
user_id STRING,
fee_code STRING,
bill_time STRING,
should_pay DOUBLE,
favour_fee DOUBLE)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ',';
CREATE TABLE bike_recharge(
user_id STRING,
recharge_amt DOUBLE,
recharge_time STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ',';
3. 数据文件¶
将下列内容分别保存为本地文件(文件末尾保留一个空行)。
/opt/data/bike_users.csv
U001,张伟,GOLD,杭州,2024-03-11 09:20:00
U002,李娜,SILVER,杭州,2024-05-02 10:05:00
U003,王强,NORMAL,杭州,2024-07-19 14:30:00
U004,刘洋,GOLD,宁波,2024-01-08 08:45:00
U005,陈静,SILVER,杭州,2024-09-23 16:10:00
U006,杨帆,NORMAL,宁波,2024-11-05 11:25:00
U007,赵敏,GOLD,杭州,2025-01-14 09:00:00
U008,孙浩,NORMAL,绍兴,2024-06-30 13:40:00
U009,周婷,SILVER,绍兴,2024-08-17 15:55:00
U010,吴磊,NORMAL,杭州,2024-12-01 10:30:00
U011,郑爽,GOLD,宁波,2025-02-20 09:15:00
U012,冯雷,NORMAL,杭州,2025-03-05 17:20:00
/opt/data/bike_info.csv
B01,EP-200,西湖景区,2024-05-10
B02,CL-100,西湖景区,2024-05-10
B03,EP-200,拱墅商圈,2024-06-18
B04,CL-100,拱墅商圈,2024-06-18
B05,EP-300,滨江园区,2024-08-02
B06,CL-100,滨江园区,2024-08-02
B07,EP-300,余杭科技城,2024-10-15
B08,CL-200,余杭科技城,2024-10-15
/opt/data/bike_orders.csv
O0001,U001,B01,E,电助力单车-单次骑行,2025-01-05 08:12:00,22.50
O0002,U002,B02,B,经典单车-单次骑行,2025-01-06 12:30:00,6.00
O0003,U003,B03,M,月卡-30天,2025-01-08 09:05:00,29.90
O0004,U001,B01,E,电助力单车-单次骑行,2025-01-12 18:40:00,25.00
O0005,U004,B04,M,月卡-30天,2025-01-15 10:20:00,29.90
O0006,U005,B05,B,经典单车-单次骑行,2025-01-18 07:55:00,5.50
O0007,U006,B06,E,电助力单车-单次骑行,2025-01-20 19:10:00,21.00
O0008,U007,B07,S,季卡-90天,2025-01-22 08:00:00,79.00
O0009,U002,B02,B,经典单车-单次骑行,2025-01-25 13:15:00,7.50
O0010,U008,B08,S,季卡-90天,2025-01-28 16:45:00,79.00
O0011,U001,B03,M,月卡-30天,2025-02-03 09:30:00,29.90
O0012,U003,B01,E,电助力单车-单次骑行,2025-02-05 17:25:00,23.50
O0013,U004,B04,B,经典单车-单次骑行,2025-02-08 08:05:00,6.50
O0014,U005,B05,M,月卡-30天,2025-02-11 12:00:00,29.90
O0015,U006,B06,B,经典单车-单次骑行,2025-02-14 18:30:00,8.00
O0016,U007,B07,E,电助力单车-单次骑行,2025-02-17 07:40:00,20.50
O0017,U009,B08,S,季卡-90天,2025-02-20 15:10:00,79.00
O0018,U001,B01,M,月卡-30天,2025-02-23 10:55:00,29.90
O0019,U010,B02,B,经典单车-单次骑行,2025-02-26 20:05:00,9.50
O0020,U002,B03,B,经典单车-单次骑行,2025-02-28 11:35:00,6.00
O0021,U003,B04,E,电助力单车-单次骑行,2025-03-02 08:20:00,24.00
O0022,U004,B05,M,月卡-30天,2025-03-05 09:45:00,29.90
O0023,U005,B06,B,经典单车-单次骑行,2025-03-08 18:15:00,7.00
O0024,U006,B07,E,电助力单车-单次骑行,2025-03-11 07:30:00,22.00
O0025,U007,B08,S,季卡-90天,2025-03-14 14:25:00,79.00
O0026,U001,B01,B,经典单车-单次骑行,2025-03-17 12:40:00,5.50
O0027,U008,B02,M,月卡-30天,2025-03-20 10:10:00,29.90
O0028,U009,B03,E,电助力单车-单次骑行,2025-03-23 19:50:00,26.50
O0029,U010,B04,B,经典单车-单次骑行,2025-03-26 08:55:00,8.50
O0030,U002,B05,B,经典单车-单次骑行,2025-03-29 17:05:00,6.50
O0031,U001,B06,E,电助力单车-单次骑行,2025-04-03 09:15:00,23.00
O0032,U004,B07,M,月卡-30天,2025-04-07 11:20:00,29.90
O0033,U007,B08,S,季卡-90天,2025-04-11 16:00:00,79.00
O0034,U005,B01,B,经典单车-单次骑行,2025-04-15 18:45:00,9.00
O0035,U002,B02,M,月卡-30天,2025-05-06 08:35:00,29.90
O0036,U003,B03,E,电助力单车-单次骑行,2025-05-12 19:20:00,25.50
O0037,U006,B04,B,经典单车-单次骑行,2025-05-18 12:55:00,7.50
O0038,U009,B05,S,季卡-90天,2025-05-22 15:30:00,79.00
O0039,U001,B06,E,电助力单车-单次骑行,2025-06-05 07:45:00,21.50
O0040,U010,B07,B,经典单车-单次骑行,2025-06-14 20:10:00,8.00
O0041,U001,B01,M,月卡-30天,2024-12-18 09:10:00,29.90
O0042,U003,B03,E,电助力单车-单次骑行,2024-12-22 17:30:00,20.00
/opt/data/bike_bills.csv
B0001,U001,RIDE,2025-01-05 08:30:00,22.50,2.50
B0002,U001,INSURE,2025-01-05 08:30:00,1.00,0.00
B0003,U002,RIDE,2025-01-06 12:45:00,6.00,0.00
B0004,U003,RIDE,2025-01-08 09:20:00,12.50,1.50
B0005,U004,RIDE,2025-01-15 10:35:00,15.00,3.00
B0006,U005,RIDE,2025-01-18 08:10:00,5.50,0.00
B0007,U006,RIDE,2025-01-20 19:25:00,21.00,2.00
B0008,U007,RIDE,2025-01-22 08:20:00,18.00,4.00
B0009,U008,DISPATCH,2025-01-28 17:00:00,5.00,0.00
B0010,U002,RIDE,2025-01-25 13:30:00,7.50,0.50
B0011,U001,RIDE,2025-02-03 09:45:00,16.50,1.50
B0012,U003,RIDE,2025-02-05 17:40:00,23.50,2.50
B0013,U004,RIDE,2025-02-08 08:20:00,6.50,0.00
B0014,U005,RIDE,2025-02-11 12:15:00,13.00,1.00
B0015,U006,RIDE,2025-02-14 18:45:00,8.00,0.00
B0016,U007,RIDE,2025-02-17 07:55:00,20.50,3.50
B0017,U009,DISPATCH,2025-02-20 15:25:00,5.00,0.00
B0018,U001,RIDE,2025-02-23 11:10:00,11.00,1.00
B0019,U010,RIDE,2025-02-26 20:20:00,9.50,0.50
B0020,U012,RIDE,2025-02-27 08:05:00,7.00,0.00
B0021,U003,RIDE,2025-03-02 08:35:00,24.00,2.00
B0022,U004,RIDE,2025-03-05 10:00:00,17.50,1.50
B0023,U005,RIDE,2025-03-08 18:30:00,7.00,0.00
B0024,U006,RIDE,2025-03-11 07:45:00,22.00,2.00
B0025,U007,DISPATCH,2025-03-14 14:40:00,5.00,0.00
B0026,U001,RIDE,2025-03-17 12:55:00,5.50,0.00
B0027,U008,RIDE,2025-03-20 10:25:00,14.00,1.00
B0028,U009,RIDE,2025-03-23 20:05:00,26.50,3.50
B0029,U010,RIDE,2025-03-26 09:10:00,8.50,0.50
B0030,U001,RIDE,2025-04-03 09:30:00,23.00,2.00
B0031,U004,DISPATCH,2025-04-07 11:35:00,5.00,0.00
B0032,U007,RIDE,2025-04-11 16:15:00,19.00,3.00
B0033,U005,RIDE,2025-04-15 19:00:00,9.00,0.00
B0034,U002,RIDE,2025-04-19 08:40:00,12.00,1.00
B0035,U012,RIDE,2025-04-21 18:25:00,10.50,0.50
B0036,U003,RIDE,2025-05-12 19:35:00,25.50,2.50
B0037,U006,RIDE,2025-05-18 13:10:00,7.50,0.00
B0038,U009,DISPATCH,2025-05-22 15:45:00,5.00,0.00
B0039,U001,RIDE,2025-06-05 08:00:00,21.50,1.50
B0040,U010,RIDE,2025-06-14 20:25:00,8.00,0.00
B0041,U001,RIDE,2024-12-18 09:20:00,18.00,2.00
B0042,U003,RIDE,2024-12-22 17:45:00,20.00,1.00
/opt/data/bike_recharge.csv
U001,50.00,2025-01-04 10:00:00
U002,30.00,2025-01-06 11:00:00
U003,50.00,2025-01-08 09:00:00
U004,100.00,2025-02-05 10:00:00
U005,50.00,2025-02-11 12:00:00
U006,30.00,2025-03-11 08:00:00
U007,100.00,2025-03-14 15:00:00
U008,50.00,2025-03-20 10:00:00
U009,100.00,2025-05-22 16:00:00
U010,30.00,2025-06-14 20:00:00
U011,50.00,2025-02-20 09:00:00
U012,30.00,2025-04-21 18:00:00
4. 导入数据并检查¶
LOAD DATA LOCAL INPATH '/opt/data/bike_users.csv' OVERWRITE INTO TABLE bike_users;
LOAD DATA LOCAL INPATH '/opt/data/bike_info.csv' OVERWRITE INTO TABLE bike_info;
LOAD DATA LOCAL INPATH '/opt/data/bike_orders.csv' OVERWRITE INTO TABLE bike_orders;
LOAD DATA LOCAL INPATH '/opt/data/bike_bills.csv' OVERWRITE INTO TABLE bike_bills;
LOAD DATA LOCAL INPATH '/opt/data/bike_recharge.csv' OVERWRITE INTO TABLE bike_recharge;
导入后先核对每张表的条数,应为 12、8、42、42、12。
SELECT COUNT(*) FROM bike_users;
SELECT COUNT(*) FROM bike_info;
SELECT COUNT(*) FROM bike_orders;
SELECT COUNT(*) FROM bike_bills;
SELECT COUNT(*) FROM bike_recharge;
四. 实验任务¶
任务 1. 划分订单消费档次¶
任务描述
使用条件函数与类型转换函数,对骑行订单按订单金额划分消费档次。
- 若订单金额大于等于 20.0,则消费档次认定为“高消费”;
- 若订单金额大于等于 10.0 且小于 20.0,则认定为“中等消费”;
- 若订单金额小于 10.0,则认定为“低消费”。
- 消费档次列的列名为
consume_type,转换后的金额列名为cost_f。 - 查询结果按订单金额降序排列,并去除重复数据。
实现思路
bike_orders表中cost字段为STRING类型,需先用CAST显式转换为FLOAT,才能参与数值比较与排序。- 用
CASE…WHEN…THEN设置 3 个分支完成分档。 - 用
DISTINCT去除完全相同的重复行,用ORDER BY按转换后的金额降序排列。
参考代码
先用简单形式的 CASE 把产品类型编码翻译成中文。
SELECT order_id, prod_type,
CASE prod_type
WHEN 'E' THEN '电助力单车'
WHEN 'B' THEN '经典单车'
WHEN 'M' THEN '月卡'
WHEN 'S' THEN '季卡'
ELSE '未知类型'
END AS `产品类型`
FROM bike_orders
LIMIT 5;
再按金额划分消费档次。
SELECT DISTINCT user_id, order_id, prod_name, CAST(cost AS FLOAT) AS cost_f,
CASE
WHEN cost >= 20.0 THEN '高消费'
WHEN cost >= 10.0 AND cost < 20.0 THEN '中等消费'
ELSE '低消费'
END AS consume_type
FROM bike_orders
ORDER BY cost_f DESC;
预期结果
第二条语句返回 42 行,前几行形如:
| user_id | order_id | prod_name | cost_f | consume_type |
|---|---|---|---|---|
| U007 | O0008 | 季卡-90天 | 79.0 | 高消费 |
| U007 | O0025 | 季卡-90天 | 79.0 | 高消费 |
| U009 | O0038 | 季卡-90天 | 79.0 | 高消费 |
| U001 | O0041 | 月卡-30天 | 29.9 | 高消费 |
| U003 | O0028 | 电助力单车-单次骑行 | 26.5 | 高消费 |
| … | … | … | … | … |
| U001 | O0026 | 经典单车-单次骑行 | 5.5 | 低消费 |
中文列别名要用反单引号引起来;
WHEN后面的取值(20.0、10.0)是数值,与cost比较时 Hive 会先把cost隐式转换为DOUBLE,所以即便不做CAST也能比较,但金额列的展示与排序仍需显式CAST。
任务 2. 统计用户每年骑行应付总额¶
任务描述
使用字符函数统计每个用户每年的骑行应付总额。
实现思路
- 使用
SUBSTR从bill_time字段值中截取前 4 个字符,得到账单所属年份。 - 以“用户编号 + 年份”两个值分组,用
SUM(should_pay)汇总应付总额。 - 数据中包含 2024 年 12 月与 2025 年的账单,分组后应分别成行。
参考代码
先熟悉字符函数的用法。
SELECT LOWER('GOLD'), UPPER('gold'), LENGTH('EP-200'), CONCAT('杭州', '-', '西湖景区'), TRIM(' EP-200 ');
SELECT user_name, LOWER(member_level), CONCAT(user_name, '/', city) FROM bike_users LIMIT 5;
再统计每年应付总额。
SELECT user_id, SUBSTR(bill_time, 1, 4) AS bill_year, SUM(should_pay)
FROM bike_bills
GROUP BY user_id, SUBSTR(bill_time, 1, 4);
预期结果
共 13 行(U001、U003 各有 2024、2025 两行),形如:
| user_id | bill_year | 应付总额 |
|---|---|---|
| U001 | 2024 | 18.0 |
| U001 | 2025 | 101.0 |
| U002 | 2025 | 25.5 |
| U003 | 2024 | 20.0 |
| U003 | 2025 | 85.5 |
| U004 | 2025 | 44.0 |
| U005 | 2025 | 34.5 |
| U006 | 2025 | 58.5 |
| U007 | 2025 | 62.5 |
| U008 | 2025 | 19.0 |
| U009 | 2025 | 36.5 |
| U010 | 2025 | 26.0 |
| U012 | 2025 | 17.5 |
U011从未产生账单,因此不出现在结果中。这也说明:分组统计的结果集只包含事实表中真实存在的数据。
任务 3. 统计用户每月骑行应付总额¶
任务描述
使用日期函数统计每个用户每个月的骑行应付总额。
实现思路
- 用
YEAR、MONTH分别取出账单的年份与月份。 - 以“用户编号 + 年份 + 月份”三个值分组后再
SUM(should_pay),避免把 2024 年 12 月与 2025 年 12 月混为同一个月累加。 - 日期计算类函数用于分析账单间隔、到期日等衍生指标。
参考代码
日期获取类函数。
SELECT YEAR('2025-05-01 12:30:45'), MONTH('2025-05-01 12:30:45'), DAY('2025-05-01 12:30:45');
SELECT HOUR('2025-05-01 12:30:45'), MINUTE('2025-05-01 12:30:45'), SECOND('2025-05-01 12:30:45');
SELECT TO_DATE('2025-05-01 12:30:45'), WEEKOFYEAR('2025-05-01 12:30:45');
SELECT TRUNC('2025-05-10 12:30:45', 'MM'), TRUNC('2025-05-10 12:30:45', 'YYYY');
日期计算类函数。
SELECT DATEDIFF('2025-07-01', '2025-05-01'), DATEDIFF('2025-05-01', '2025-07-01');
SELECT DATE_ADD('2025-05-01 00:00:00', 10), DATE_SUB('2025-05-01 00:00:00', 10);
SELECT ADD_MONTHS('2025-01-31', 1), ADD_MONTHS('2025-05-01', -2);
统计每个用户骑行记录横跨的天数。
统计每个用户每个月的应付总额。
SELECT user_id, YEAR(bill_time), MONTH(bill_time), SUM(should_pay)
FROM bike_bills
GROUP BY user_id, YEAR(bill_time), MONTH(bill_time);
预期结果
最后一条语句共 39 行(各用户有账单的月份数之和),形如:
| user_id | 年 | 月 | 应付总额 |
|---|---|---|---|
| U001 | 2024 | 12 | 18.0 |
| U001 | 2025 | 1 | 23.5 |
| U001 | 2025 | 2 | 27.5 |
| U001 | 2025 | 3 | 5.5 |
| U001 | 2025 | 4 | 23.0 |
| U001 | 2025 | 6 | 21.5 |
| U002 | 2025 | 1 | 13.5 |
| … | … | … | … |
任务 4. 统计用户月均骑行实付金额¶
任务描述
利用数学函数统计 2025 年月均骑行实付金额最高的 5 个用户。
实现思路
- 实付金额为应付金额减去优惠金额,即
should_pay - favour_fee。 - 账单表记录的是每次骑行的费用明细,同一用户同一月份存在多条明细,因此月账单必须先用
SUM(should_pay - favour_fee)把明细合并,再对月份求平均。直接对明细行取AVG(should_pay - favour_fee),得到的是单条明细的均值,不是月均账单。 - 本任务按用户编号分组,月份数用
COUNT(DISTINCT MONTH(bill_time))单独统计,只计入有账单的月份。 - 年份是行级过滤条件,写在
WHERE中先过滤再聚合,不要写进HAVING。 - 用
ROUND(..., 2)保留 2 位小数,按均值降序排序后取前 5 条。
参考代码
数学函数的用法。
统计 2025 年月均骑行实付金额最高的 5 个用户。
SELECT user_id,
ROUND(SUM(should_pay - favour_fee) / COUNT(DISTINCT MONTH(bill_time)), 2) AS avg_fee
FROM bike_bills
WHERE YEAR(bill_time) = 2025
GROUP BY user_id
ORDER BY avg_fee DESC
LIMIT 5;
预期结果
| user_id | avg_fee |
|---|---|
| U003 | 19.25 |
| U001 | 18.5 |
| U006 | 13.63 |
| U007 | 13.0 |
| U002 | 12.0 |
- 结果恰好是 5 个用户,每人一行,因为只按
user_id单列分组。- 去掉
LIMIT 5后共 11 行,U011因无账单不会出现。- 若去掉
WHERE YEAR(bill_time) = 2025,U001、U003的 2024 年 12 月账单也会被计入分子与分母,结果随之改变——这正是年份过滤必须放在聚合之前的原因。- 若
favour_fee存在空值,should_pay - favour_fee的结果会变成NULL并被SUM忽略,此时需改写为should_pay - COALESCE(favour_fee, 0)。
拓展
对月均值分别向下、向上取整。
SELECT user_id,
FLOOR(SUM(should_pay - favour_fee) / COUNT(DISTINCT MONTH(bill_time))) AS floor_fee,
CEIL(SUM(should_pay - favour_fee) / COUNT(DISTINCT MONTH(bill_time))) AS ceil_fee
FROM bike_bills
WHERE YEAR(bill_time) = 2025
GROUP BY user_id;
任务 5. 查询电助力订单的用户所在城市¶
任务描述
通过连接查询获取电助力单车订单的用户城市信息。
实现思路
bike_orders表用prod_name字段记录产品名称,电助力订单可用LIKE '%电助力%'模糊匹配筛选。- 订单表与用户表以
user_id为连接条件内连接,从用户表取出订单表里没有的city字段。 - 分别改用左外连接、左半开连接,观察结果集的变化。
参考代码
内连接:查询电助力订单的用户城市。
SELECT o.order_id, o.prod_name, u.user_name, u.city
FROM bike_orders o
INNER JOIN
bike_users u
ON o.user_id = u.user_id
WHERE o.prod_name LIKE '%电助力%';
内连接:查询产生订单的用户等级。
SELECT u.user_id, u.user_name, u.member_level, o.order_id
FROM bike_users u
INNER JOIN
bike_orders o
ON u.user_id = o.user_id;
左外连接:保留所有用户,从未下单的用户右表字段显示为 NULL。
SELECT u.user_id, u.user_name, o.order_id, o.prod_name
FROM bike_users u
LEFT JOIN
bike_orders o
ON u.user_id = o.user_id;
左半开连接:只取“有订单的用户”,结果中不能出现右表字段。
SELECT u.user_id, u.user_name, u.city
FROM bike_users u
LEFT SEMI JOIN
bike_orders o
ON u.user_id = o.user_id;
左半开连接错误示范:SELECT 中引用了右表字段。
SELECT u.user_id, u.user_name, o.order_id
FROM bike_users u
LEFT SEMI JOIN
bike_orders o
ON u.user_id = o.user_id;
用 UNION ALL 纵向合并订单记录与充值记录。
SELECT user_id, 'order' AS rec_type, CAST(cost AS STRING) AS amount
FROM bike_orders
UNION ALL
SELECT user_id, 'recharge' AS rec_type, CAST(recharge_amt AS STRING) AS amount
FROM bike_recharge;
预期结果
- 电助力订单共 11 条,涉及 U001、U003、U006、U007 四位用户,结果形如
O0001 | 电助力单车-单次骑行 | 张伟 | 杭州。 - 订单与用户的内连接返回 42 行。
- 用户表左外连接订单表返回 44 行:
U011、U012各多出一行,其order_id、prod_name为NULL。 - 左半开连接返回 10 行(U001—U010),
U011、U012被排除;把右表字段写进SELECT会直接报错。 UNION ALL返回 54 行(42 + 12),其中U011只有充值记录。
UNION ALL要求各子查询的列数、列名与列类型一致,因此DOUBLE类型的recharge_amt需要用CAST转成与cost相同的STRING类型。若要去重则改用UNION。
任务 6. 抽样统计用户骑行订单¶
任务描述
利用抽样查询对用户骑行订单进行抽样统计。
实现思路
- 创建桶表
bike_orders_bucket,按用户编号user_id分为 4 个桶。 - 从
bike_orders向桶表中导入数据。 - 使用
TABLESAMPLE(BUCKET x OUT OF y ON user_id)抽取指定桶的数据并统计条数。
参考代码
创建桶表。
CREATE TABLE bike_orders_bucket(
user_id STRING,
order_id STRING,
prod_name STRING,
order_time STRING,
cost STRING)
CLUSTERED BY(user_id) INTO 4 BUCKETS
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ',';
向桶表导入数据。
INSERT OVERWRITE TABLE bike_orders_bucket
SELECT user_id, order_id, prod_name, order_time, cost
FROM bike_orders;
抽取第 3 个桶的数据。
统计每个桶中的数据条数。
预期结果
- 桶表共 42 条数据,4 个桶的条数由
user_id的哈希值决定,不一定完全均分。 TABLESAMPLE(BUCKET 1 OUT OF 4 ON user_id)返回其中 1 个桶的数据,x换成 2、3、4 即抽取对应桶。- 由于只扫描单个桶,抽样查询避免了全表遍历,可用于快速估算与数据探查。
五. 拓展练习¶
-
三表连接:查询订单的用户姓名、单车型号与停放区域。
-
统计每个城市的订单总额,并按总额降序排列(提示:三表连接 +
GROUP BY u.city)。 -
用
CASE把用户按 2025 年实付总额分为“活跃用户”(≥ 60)、“一般用户”(≥ 30)、“低频用户”三档。 -
用
DATEDIFF统计每个用户首笔订单与最近一笔订单之间相隔的天数,找出骑行跨度最长的用户(提示:DATEDIFF(MAX(order_time), MIN(order_time)))。 -
把任务 5 的
UNION ALL改写为UNION,对比结果行数发生了什么变化,并说明原因。
六. 常见问题¶
| 现象 | 原因与处理 |
|---|---|
LOAD DATA LOCAL 报文件不存在 |
LOCAL 读的是 beeline 客户端本地路径,不是 HDFS 路径;若文件已在 HDFS 上,去掉 LOCAL |
CASE 报类型不匹配 |
WHEN 后的取值类型必须与判断字段完全一致,例如 prod_type 是 STRING,就要写成 WHEN 'E' |
| 中文列别名报错 | 中文别名必须用反单引号 ` 引起来 |
左半开连接报 SemanticException |
左半开连接的 SELECT 与 WHERE 中都不能引用右表字段 |
UNION ALL 报列数或类型不一致 |
保证各子查询列数相同、列名以第一条 SELECT 为准、对应列类型一致,必要时用 CAST 统一 |
| 抽样查询报桶数不匹配 | TABLESAMPLE(BUCKET x OUT OF y ON field) 中的 y 必须是桶数的倍数或因子 |
| 金额计算结果为空 | 参与运算的字段若存在 NULL,需先用 COALESCE(字段, 0) 兜底 |
七. 实验报告要求¶
- 记录 6 个任务的完整 SQL 语句,并附关键步骤的运行结果截图。
- 说明任务 4 中「先按月汇总、再对月份求平均」与「直接对明细行取
AVG」两种写法的区别,并给出实测结果对比。 - 说明任务 5 中内连接、左外连接、左半开连接三种写法的结果集差异,并指出各自的适用场景。