欢迎来到安国润的个人网站


hive 分层设计文档

分层本质:解耦、复用、易排查、稳口径、提性能

通用分层链路:ODS原始层 → DWD明细清洗层 → DWS汇总宽表层 → ADS指标应用层

一、隔离原始数据,保护源头,支持数据回溯

  1. ODS 原样同步业务库数据,不做任何清洗、不修改字段,完整留存原始快照;
  2. 下游 DWD/DWS/ADS 只读取 ODS,不会直接操作原始数据源;
  3. 好处:
  4. 业务系统字段改动、脏数据、乱码、缺失值全部隔离在 ODS,不会污染上层指标;
  5. 报表数据出错时,可拿 ODS 原始数据重跑、回溯修复,不会丢失原始记录;
  6. 禁止业务人员直接访问原始业务库,减轻业务数据库压力。

举例:新发地源头数据avg_price存在 “未知、空字符串、负数” 脏数据,只在 DWD 统一过滤清洗,ODS 完整保留原始脏数据用于核对。

二、分层拆分加工逻辑,复杂问题简单化,方便排错

不分层的弊端:一张 SQL 从原始表直接算出最终报表,清洗、去重、关联、聚合全部写在一段,逻辑臃肿,出问题无从定位。

分层拆解每一层只做一件事:

  1. ODS:同步数据;
  2. DWD:清洗脏数据、统一字段类型、规范日期、去重;
  3. DWS:按日期 / 分类轻度汇总,生成宽表;
  4. ADS:业务指标高度聚合(涨跌、价格分布、品类统计)。

排错链路(天然数据血缘)

ADS 报表数据异常 → 查 DWS 汇总逻辑 → 查 DWD 明细脏数据 → 核对 ODS 原始源头

逐层定位,不用通读几百行超长 SQL,排查效率提升 80%。

三、公共计算只做一次,减少重复开发、节省集群资源

不分层场景:市场报表、价格分布、品类统计三张报表,每个人都从 ODS 重新清洗、转换价格、分类,重复跑相同逻辑

分层方案:

  1. DWD 统一清洗一次干净商品明细;
  2. DWS 按天 / 分类汇总一次公共指标;
  3. 所有 ADS 报表直接复用 DWS 结果,不再重复清洗、重复聚合。

价值:大幅减少 MapReduce 任务数量,降低 HDFS 读写压力,解决重复计算资源浪费问题。

四、统一数据口径,杜绝 “数据打架”

最核心业务价值:一数一源

如果每个报表自己写清洗规则:

  • A 报表把价格 < 0 过滤掉;B 报表保留负数;

  • A 把 “未知产地” 剔除,B 不剔除;

最终两张报表商品总量、均价对不上,业务质疑数据不准。

分层统一规则:

所有清洗、过滤、区间划分逻辑只在 DWD 层定义一次,全平台所有指标统一复用同一套标准,口径完全一致。

五、分层适配查询性能,分层优化存储

  1. DWD:保留最细明细,适合明细查询、数据分析;
  2. DWS:提前按主题聚合宽表,减少多表 join,日常统计查询速度极快;
  3. ADS:高度聚合,单表直接给大屏、BI 报表使用,几乎无计算开销;
  4. 存储分层优化:ODS 可用 Text 存储;DWD/DWS/ADS 统一 ORC+SNAPPY 压缩,分层设置合并小文件参数,各司其职优化存储。

六、解耦变更,提高架构稳定性

  1. 源系统表结构变更(新增字段、改名):只修改 ODS 同步脚本,DWD 做兼容转换,上层 DWS/ADS 不用改动;
  2. 业务需求调整指标计算逻辑:仅修改对应 ADS 层,明细、汇总层不受影响;
  3. 多层单向依赖(下层供上层,禁止反向依赖),一层改动不会连锁污染全链路,维护成本极低。

1、总体分层架构

┌─────────────────────────────────── ─┐
│                   ads 应用层(可选)                                     │
│   面向业务输出:报表 / 大屏 / 仪表盘 / 接口数据              │
└─────────────────────────────────────────────────────────┘
                           ▲
┌─────────────────────────────────────────────────────────┐
│                   dws 汇总层(宽表层)                      │
│   共性指标汇总:品类汇总 / 产地汇总 / 价格区间汇总 / 综合宽表   │
└─────────────────────────────────────────────────────────┘
                           ▲
┌─────────────────────────────────────────────────────────┐
│                   dwd 明细层(事实层)                       │
│   清洗后的原子明细记录:二次过滤、类型转换、去重、补齐审计字段    │
└─────────────────────────────────────────────────────────┘
                           ▲
┌─────────────────────────────────────────────────────────┐
│                   ods 贴源层(操作数据层)                   │
│   原始 CSV 原样映射到 Hive 外部表,保持与源系统一致            │
└─────────────────────────────────────────────────────────┘
                           ▲
┌─────────────────────────────────────────────────────────┐
│                   HDFS 原始数据层                          │
│   /xinfadi/*.csv   每日采集 CSV 文件                        │
└─────────────────────────────────────────────────────────┘

2、各层功能作用

层级 中文名 核心目标 表类型 数据保留策略
ODS 操作数据层 / 贴源层 原样保存源数据,便于回溯与重跑 外部表 (EXTERNAL) 全量,长期保留
DWD 明细数据层 清洗、去重、标准化后的原子明细记录 内部表 (MANAGED) 全量有效明细
DWS 汇总数据层 按主题聚合,形成共性指标宽表 内部表 (MANAGED) 每日聚合快照
ADS 应用数据层 面向具体业务报表输出 内部表 / 视图 按需保留

2.1 ods 层:Operation Data Store

设计规则

  • 不做任何数据清洗,字段与源 csv一一对应
  • 使用 EXTERNAL TABLE 外部表,数据存放在 hdfs/xinfadi
  • 通过 tblproperties('skip.header.line.count'='1') 自动跳过 csv 表头
  • 价格类字段先用 string 保存原始值,避免类型转换失败导致丢数
  • 字段命名:英文驼峰(便于后续与 dwd 字段映射)

功能作用

  • 数据采集后第一站,数仓的"原始镜像"
  • 后续任何重跑、问题追溯,都可从 ods 重新计算
  • 保留数据原貌,不影响源系统或原始采集文件

2.2 dwd 层:Data Warehouse Detail

设计规则

  • 基于 ods 做清洗,输出"干净、可用的明细数据"
  • 关键动作:
  • 缺失值过滤:品名 / 分类 / 价格 / 发布时间 为空 → 删除
  • 类型转换:价格 string → DOUBLE;日期统一格式 YYYY-MM-DD
  • 逻辑校验最低价 ≤ 平均价 ≤ 最高价,且 > 0
  • 全局上下限0.1 ≤ 平均价 ≤ 2000
  • 分组 IQR 去极端值:按 (一级分类, 品名) 分组剔除 Q1-3·IQR 以外的异常值
  • 去重(分类, 品名, 产地, 规格, 单位, 日期, 价格) 组合键去重
  • 补齐审计字段id(业务唯一标识)、create_timeupdate_time

存储策略

  • 内部表(MANAGED TABLE),Hive 管理生命周期
  • stored as PARQUET + SNAPPY 压缩,列式存储、高效查询

功能作用

  • 数仓中最核心的"事实明细",所有后续统计的基础
  • 分析师、业务人员直接查 dwd 就能拿到可信数据,不用再关心 ods 的脏数据
  • 可以直接关联维度表(例如商品维度、产地维度、日期维度)做复杂分析

2.3 dws 层:Data Warehouse Summary

设计规则

  • dwd 明细层 为唯一输入,按不同主题做聚合
  • 每张 dws 表围绕"统计日期 + 聚合维度"形成宽表
  • 每个宽表都应当是"同一粒度下的完整业务视角",下游 ads / 报表无需再 join
  • 存储:PARQUET,便于 BI / Spark / Impala 等多种引擎高效读取
  • 命名:dws_<主题>_<时间粒度>,如 dws_category_day(品类日汇总)

功能作用

  • 共性指标沉淀,避免重复计算
  • 数据体量显著缩减(dwd 可能几十万行,dws 聚合后仅几千行)
  • 对外提供稳定的数据接口,业务报表、大屏直接消费 DWS

2.4、数仓分层设计原则总结(可放报告正文)

  1. 一次清洗,多次复用:清洗工作集中在 dwd 层完成,dws 直接从 dwd 取数,避免各层重复做清洗。
  2. 数据可回溯:ods 保留原始数据,任意时刻发现清洗逻辑错误,都可以从 ods 重跑 dwd → DWS,不影响业务。
  3. 逐层收敛:ODS(百万行)→ DWD(十万行级)→ DWS(万/千行级)→ ADS(报表级),数据越上层量越小,越贴近业务。
  4. 宽表思想:dws 每张表都把一个主题"做宽",把相关指标放在同一张表,下游查询不需要 join,性能和可维护性都更好。
  5. 统一命名规范<层前缀>_<主题>_<粒度>,如 dws_category_daydwd_xinfadi_data,任何人看到表名就能猜出它是什么、来自哪层。
  6. 审计字段:dwd 及以上所有表都带 id / create_time / update_time,支持数据血缘追溯与增量更新。
  7. 外部表 vs 内部表:ods 用外部表保护源数据;DWD/dws 用内部表(PARQUET+SNAPPY)享受 Hive 自动管理 + 列式存储性能。

2.5、数据流向示意(配图用)

  爬虫采集 (py1)
       │
       ▼
  data/YYYY-MM-DD.csv   (本地文件)
       │
       ▼  scp / sftp + hdfs dfs -put
       ▼
  HDFS /xinfadi/*.csv   (原始文件系统)
       │
       ▼  CREATE EXTERNAL TABLE LOCATION '/xinfadi'
       ▼
  ods_xinfadi_price     (外部表,string 原始字段)
       │
       ▼  清洗:类型转换 / 去重 / 异常值过滤 / 去重 / 补 ID
       ▼
  dwd_xinfadi_data     (内部表,PARQUET,原子明细)
       │
       ▼  按主题聚合
       ▼
  ┌─────────────┬──────────────┬───────────────┬──────────────┐
  │ 品类汇总表    │     │  价格区间汇总表   │  综合宽表      │
  │ dws_category │      │  dws_price_range│  dws_overall  │
  │     _day     │           │      _day       │     _day      │
  └─────────────┴───────────────┴─────────────────┴───────────────┘
       │
       ▼  (可选) 面向 BI / 报表 / 大屏
       ▼
  ads 应用层:报表表 / 接口表 / 大屏数据

3、ods 层:外部表


-- 1. 删除旧表(外部表,仅删元数据,HDFS 上的 CSV 不会丢)
drop database if exists xinfadi cascade;

-- 创建数据库(可选,分类管理)
create database if not exists xinfadi_db comment '新发地农产品价格数据仓库';

create database if not exists xinfadi comment '新发地农产品价格数据仓库';


-- 进入数据库
use xinfadi_db;
show databases;
show tables;

-- 创建外部表  location 指向你的HDFS文件目录
create external table if not exists  ods_xinfadi_data (
    prod_id        string comment 'id',
    prod_cat       string comment '一级分类',
    prod_pcat      string comment '二级分类',
    prod_name     string comment '品名',
    high_price     string comment '最高价',
    low_price      string comment '最低价',
    avg_price      string comment '平均价',
    spec_info      string comment '规格',
    place            string comment '产地',
    unit_info       string comment '单位',
    pub_date       string comment '发布时间'
)
comment '原始csv ods 层,未做任何数据清洗'
row  format  delimited 
fields  terminated by ','   -- 文本文件每行用逗号切分各个字段 csv逗号分隔
stored as textfile  -- 指定 Hive 表底层存储格式为纯文本文件
location '/xinfadi/' -- 指定 hdfs 数据存放目录
tblproperties (
    "skip.header.line.count"="1",
    "serialization.encoding" = "UTF-8"
); -- 忽略表头第一行


-- 3. 校验
desc ods_xinfadi_data;

select * from ods_xinfadi_data  where  pub_date ='2026-06-22 00:00:00'   limit 5

select * from ods_xinfadi_data limit 5;
select  count(*) from ods_xinfadi_data;

4、dwd 层:明细层内部表(清洗)

```mysql -- ===================================================== -- dwd 层:清洗后的明细数据(内部表,PARQUET 压缩存储) -- 做什么: -- 1) 删除关键字段缺失记录 -- 2) cast(字段 as 数据类型) 强制字段类型转换 价格 string -> decimal(总长度, 小数位数) , id string-> bigint ,日期转 id string-> date -- 3) 过滤异常值(价格非正值 / 逻辑矛盾 / 极端值) -- 4) current_timestamp 是时间戳 增加 create_time / update_time 审计字段 -- 5) distinct 去重 -- 6) trim 去除字段前后的空格、制表符,避免空格干扰正则判断。 -- 7) rlike '^[0-9]+$' 正则匹配规则 ^开头字符串结尾 只能是数字 0~9,+:数字至少出现 1 次 匹配小数点 .(Hive 里反斜杠要转义

-- 价格字段 两处正则 + 转换作用完全不一样,执行阶段不同 -- 第一次 1. WHERE 里的正则 + cast(前置过滤,筛掉整条脏行) cast 只判断 不完成类型转换 -- 第二次 2. SELECT 子查询的 case 正则 + cast(字段标准化)cast 判断+成类型转换 -- 建议两个都要写 只走select 会大量无效数据参与计算,拖慢 SQL 执行速度; 只where 又无法类型转换

-- =====================================================

drop table if exists dwd_xinfadi_data;

-- dwd 新发地清洗明细数据表 create table if not exists dwd_xinfadi_data ( prod_id bigint comment 'ID,纯数字有效,非法为null', prod_cat string comment '一级分类,已去首尾空格', prod_pcat string comment '二级分类,已去首尾空格', prod_name string comment '品名,已去首尾空格', high_price decimal(12,2) comment '最高价,合法正数数值', low_price decimal(12,2) comment '最低价,合法正数数值', avg_price decimal(12,2) comment '平均价,合法正数数值', spec_info string comment '规格,已去首尾空格', place string comment '产地,已去首尾空格', unit_info string comment '单位,已去首尾空格', pub_date date comment '发布日期,标准YYYY-MM-DD格式', create_time timestamp comment '数据入库创建时间', update_time timestamp comment '数据更新时间' ) comment '新发地商品价格清洗明细层DWD表,过滤脏数据、标准化字段类型' stored as orc tblproperties ( "orc.compress" = "SNAPPY", "comment" = "清洗后明细,过滤空值、非法价格、价格逻辑错乱、非法日期" );

insert overwrite table dwd_xinfadi_data select t.prod_id, t.prod_cat, t.prod_pcat, t.prod_name, t.high_price, t.low_price, t.avg_price, t.spec_info, t.place, t.unit_info, t.pub_date, current_timestamp as create_time, current_timestamp as update_time from ( select distinct -- 商品ID 如果 prod_id 是纯数字字符串,把字符串强转为大整数 bigint类型 case when trim(prod_id) rlike '^[0-9]+$' then cast(trim(prod_id) as bigint) else null end as prod_id, trim(prod_cat) as prod_cat, trim(prod_pcat) as prod_pcat, trim(prod_name) as prod_name, trim(spec_info) as spec_info, trim(place) as place, trim(unit_info) as unit_info,

    -- 最高价
    case when trim(high_price) rlike '^[0-9]+(\\.[0-9]+)?$'
        then cast(trim(high_price) as decimal(12,2))
    else null end as high_price,

    -- 最低价
    case when trim(low_price) rlike '^[0-9]+(\\.[0-9]+)?$'
        then cast(trim(low_price) as decimal(12,2))
    else null end as low_price,

    -- 平均价
    case when trim(avg_price) rlike '^[0-9]+(\\.[0-9]+)?$'
        then cast(trim(avg_price) as decimal(12,2))
    else null end as avg_price,

    -- 发布日期:兼容 2026-06-22 / 2026-06-22 00:00:00
    case when trim(pub_date) rlike '^[0-9]{4}-[0-9]{2}-[0-9]{2}( [0-9]{2}:[0-9]{2}:[0-9]{2})?$'
        then to_date(trim(pub_date))
    else null end as pub_date

from ods_xinfadi_data
where
    trim(prod_name) is not null and trim(prod_name) != ''
    and trim(prod_cat) is not null and trim(prod_cat) != ''
    and trim(prod_id) is not null and trim(prod_id) != ''
    and trim(pub_date) is not null and trim(pub_date) != ''

    and trim(avg_price) rlike '^[0-9]+(\\.[0-9]+)?$'
    and cast(trim(avg_price) as decimal(12,2)) > 0
    and cast(trim(avg_price) as decimal(12,2)) <= 100000

    and trim(high_price) rlike '^[0-9]+(\\.[0-9]+)?$'
    and cast(trim(high_price) as decimal(12,2)) > 0

    and trim(low_price) rlike '^[0-9]+(\\.[0-9]+)?$'
    and cast(trim(low_price) as decimal(12,2)) > 0

    and cast(trim(low_price) as decimal(12,2)) <= CAST(trim(avg_price) as decimal(12,2))
    and cast(trim(avg_price) as decimal(12,2)) <= CAST(trim(high_price) as decimal(12,2))

    -- 修复日期正则,兼容带时分秒
    and trim(pub_date) rlike '^[0-9]{4}-[0-9]{2}-[0-9]{2}( [0-9]{2}:[0-9]{2}:[0-9]{2})?$'

) t;

select * from dwd_xinfadi_data where pub_date ='2026-06-22' limit 5;

select * from ods_xinfadi_data where pub_date ='2026-06-22 00:00:00' limit 5

dwd_xinfadi_data

-- 看看清洗掉了多少条 select (select count() from ods_xinfadi_price) as ods_cnt, (select count() from dwd_xinfadi_data) as dwd_cnt;

```

5、dwd 层 基础指标查询

5.1 dwd 单表查询


--

-- 查 dwd 前 20 条
select * from dwd_xinfadi_data limit  20;

-- 查所有一级分类
select distinct prod_cat from dwd_xinfadi_data;

-- 日期覆盖范围
select min(pub_date_std) as min_date,
       max(pub_date_std) as max_date,
       count(distinct pub_date_std) as days
from dwd_xinfadi_data;


select count(*)   as dwd_total from dwd_xinfadi_data;

5.2 dwd 条件筛选 (where)


select * from dwd_xinfadi_data  where  pub_date ='2026-06-22'   limit 5;

-- 只看蔬菜类
select prod_name, avg_price, place, pub_date_std
from dwd_xinfadi_data
where prod_cat = '蔬菜'
  and pub_date_std = '2026-06-16'
limit 20;

-- 平均价 > 50 的高档商品
select prod_name, prod_cat, avg_price, place
from dwd_xinfadi_data
where avg_price > 50
order by  avg_price desc
limit 20;

-- 白菜相关商品
select prod_name, high_price, low_price, avg_price, place
from dwd_xinfadi_data
where prod_name LIKE '%白菜%'
order by  avg_price;

-- 多条件:蔬菜 and 产地山东
select prod_name, avg_price, place, pub_date_std
from dwd_xinfadi_data
where prod_cat = '蔬菜'
  and place LIKE '%山东%'
  and avg_price between 1 and 10
limit 20;

5.3 dwd group by 分组统计

-- 按一级分类统计商品数 & 平均价
select
    prod_cat,
    count(*) as cnt,
    round(avg(avg_price), 2) as avg_price
from dwd_xinfadi_data
group by prod_cat
order by  cnt desc;

-- 按 (一级分类, 二级分类) 统计
select
    prod_cat, prod_pcat,
    count(*) as cnt,
    round(avg(avg_price), 2) as avg_price,
    round(max(avg_price), 2) as max_price
from dwd_xinfadi_data
group by prod_cat, prod_pcat
order by  prod_cat, avg_price desc;

-- 按日统计:每天记录数
select
    pub_date_std,
    count(*) as total_rows,
    count(distinct prod_name) as product_kinds,
    round(avg(avg_price), 2) as avg_price
from dwd_xinfadi_data
group by pub_date_std
order by  pub_date_std;

-- having:筛选平均价超过 20 的分类
select
    prod_cat,
    count(*) as cnt,
    round(avg(avg_price), 2) as avg_price
from dwd_xinfadi_data
group by prod_cat
having avg(avg_price) > 20
order by  avg_price desc;

5.4 dwd order by 排序

-- 按平均价降序 Top20(最贵商品)
select prod_name, prod_cat, place, avg_price, pub_date_std
from dwd_xinfadi_data
order by  avg_price desc
limit 20;

-- 按平均价升序 Top20(最便宜商品)
select prod_name, prod_cat, place, avg_price
from dwd_xinfadi_data
order by  avg_price asc
limit 20;

-- 多字段排序:先按分类,再按价格
select prod_cat, prod_name, avg_price
from dwd_xinfadi_data
order by  prod_cat, avg_price desc;

5.5 dwd 基础指标计算

-- ===== 指标 1:各一级分类下 Top5 最贵商品(窗口函数)=====
--  Hive 窗口函数,作用:按商品大类分组,每组内部按均价从高到低排序,给每条数据生成组内自增序号。
-- over() 窗口函数
-- row_number () 无并列:就算数值一样,序号也依次递增(1、2、3)
-- dense_rank 规则:相同数值 → 同名次 下一名名次不跳号(1、1、2、3… 连续)
-- rank () 跳号:相同数值同名次,后面直接跳过占用的名次(1、1、3)

给分组排序后的行分配连续序号:1、2、3、4、5……
select first_category, product_name, avg_price
from (
    select
        prod_cat  as first_category,
        prod_name as product_name,
        avg_price,
        row_number() over(partition by prod_cat order by  avg_price desc) as rn
    from dwd_xinfadi_data
) t
where rn <= 5;

-- ===== 指标 2:每日价格波动(最高价 vs 最低价)=====
select
    pub_date_std,
    prod_cat,
    round(avg(high_price - low_price), 2) as avg_price_diff,
    round(max(high_price - low_price), 2) as max_price_diff
from dwd_xinfadi_data
group by pub_date_std, prod_cat
order by  pub_date_std, prod_cat;

-- ===== 指标 3:某品类价格趋势(按日期分组)=====
select
    pub_date_std,
    round(avg(avg_price), 2) as daily_avg
from dwd_xinfadi_data
where prod_name LIKE '%白菜%'
group by pub_date_std
order by  pub_date_std;

-- ===== 指标 4:各产地商品数量排名 =====
select
    place,
    count(*) as product_cnt,
    dense_rank() over(order by  count(*) desc) as rnk
from dwd_xinfadi_data
where place is not null and trim(place) != '未知'
group by place
order by  product_cnt desc
limit 20;



-- ===== 指标 5:价格带分布 =====
--  第一步 group by 先分组
-- 第二步 select 里的 case 生成别名
select
    case
        when avg_price < 5   then '0-5元'
        when avg_price < 10  then '5-10元'
        when avg_price < 20  then '10-20元'
        when avg_price < 50  then '20-50元'
        when avg_price < 100 then '50-100元'
        else '100元以上'
    end as price_band,
    count(*) as cnt
from dwd_xinfadi_data
group by
    case
        when avg_price < 5   then '0-5元'
        when avg_price < 10  then '5-10元'
        when avg_price < 20  then '10-20元'
        when avg_price < 50  then '20-50元'
        when avg_price < 100 then '50-100元'
        else '100元以上'
    end
order by  cnt desc;
--  相同结果不同写法
select
    price_band,
    count(*) as cnt
from (
    select
        case
            when avg_price < 5   then '0-5元'
            when avg_price < 10  then '5-10元'
            when avg_price < 20  then '10-20元'
            when avg_price < 50  then '20-50元'
            when avg_price < 100 then '50-100元'
            else '100元以上'
        end as price_band
    from dwd_xinfadi_data
) t
group by price_band
order by cnt desc;

5、dws 层:各类汇总宽表

-- stored as orc     -Hive 表底层文件存储格式为 ORC 列式存储文件。 查询速度快 自带索引
-- tblproperties   表配置额外属性 ORC 文件开启 SNAPPY 压缩算法 压缩速度快、解压开销极小
-- hive.exec.orc.split.strategy" = "BI"   ORC 文件切片拆分策略  

-- dws_product_info(商品信息表:商品维度表)
create table if not exists dws_product_info (
    prod_cat    string comment '一级分类',
    prod_pcat   string comment '二级分类',
    prod_name   string comment '品名'
)
comment 'DWS层-新发地商品维度信息表,去重后的商品基础维度'
stored as orc     -- Hive 表底层文件存储格式为 ORC 列式存储文件。
tblproperties (
    "orc.compress" = "SNAPPY",
    "hive.exec.orc.split.strategy" = "BI"
);

-- 全量覆盖维度数据
insert overwrite table dws_product_info
select distinct
    prod_cat,
    prod_pcat,
    prod_name
from dwd_xinfadi_data
where
    prod_name is not null
    and trim(prod_name) != '';

desc dws_product_info;
-- 检查
select * from dws_product_info  order by  `prod_cat`, `prod_pcat` limit 20;

-- 按一级分类统计商品数
select 
    prod_cat as cat, 
    count(*) as cnt
from dws_product_info group by prod_cat order by  cnt desc;

-- 看看"蔬菜"分类下有哪些商品
select * from dws_product_info where prod_cat = '蔬菜'
order by  prod_pcat, prod_name limit 30;


-- dws_xinfadi_price 品类汇总表
table if not exists dws_xinfadi_price (
    pub_date        date comment '统计日期',
    prod_cat        string comment '一级分类',
    prod_name       string comment '商品名',
    record_count    int comment '当天记录条数',
    avg_price       decimal(10,2) comment '平均价',
    min_low_price   decimal(10,2) comment '最低的最低价',
    max_high_price  decimal(10,2) comment '最高的最高价',
    avg_price_range decimal(10,2) comment '平均价格浮动',
    place_count     int comment '不同产地数量'
)
comment 'DWS层-新发地商品每日价格汇总表'
stored as orc
tblproperties (
    "orc.compress" = "SNAPPY"
);


insert overwrite table dws_xinfadi_price
select
    t.pub_date,
    t.prod_cat,
    t.prod_name,
    t.record_count,
    t.avg_price,
    t.min_low_price,
    t.max_high_price,
    t.avg_price_range,
    t.place_count
from (
    select
        pub_date,
        prod_cat,
        prod_name,
        count(*) as record_count,
        round(avg(avg_price), 2) as avg_price,
        round(min(low_price), 2) as min_low_price,
        round(max(high_price), 2) as max_high_price,
        round(avg(high_price - low_price), 2) as avg_price_range,
        count(distinct place) as place_count
    from dwd_xinfadi_data
    where pub_date is not null 
    group by pub_date, prod_cat, prod_name
) t;


desc dws_xinfadi_price;

select * from dws_xinfadi_price limit 5;
-- 看 2026-06-17 这一天平均价最高的 5 件商品
select * from dws_xinfadi_price where pub_date='2026-06-15' order by avg_price desc limit 5
-- 1) 商品名里包含"肉"的所有记录
select * from dws_xinfadi_price where prod_name LIKE '%肉%' limit  5;

5.1 dws_price_range_day(价格区间每日汇总表)

create table if not exists dws_price_range_day (
    pub_date      date comment '统计日期',
    price_range   string comment '价格区间,如 [0-5)',
    record_count  int comment '该区间内记录条数',
    avg_price     decimal(10,2) comment '该区间平均价'
)
comment 'DWS层-价格区间每日汇总表'
stored as orc
tblproperties (
    "orc.compress" = "SNAPPY"
);



insert overwrite table dws_price_range_day
select
    t.pub_date,
    t.price_range,
    t.record_count,
    t.avg_price
from (
    select
        pub_date,
        price_range,
        count(*) as record_count,
        round(avg(avg_price), 2) as avg_price
    from (
        select
            pub_date,
            avg_price,
            case
                when avg_price < 5 then '[0-5)'
                when avg_price < 10 then '[5-10)'
                when avg_price < 20 then '[10-20)'
                when avg_price < 50 then '[20-50)'
                when avg_price < 100 then '[50-100)'
                else '[100+)'
            end as price_range
        from dwd_xinfadi_data
        where pub_date is not null and avg_price is not null
    ) tmp
    group by pub_date, price_range
) t;


-- 检查
select count(*) cont from dws_price_range_day;

select * from dws_price_range_day group by pub_date   order by pub_date desc limit 6;

-- 看最近一天的价格区间分布
select pub_date, price_range, record_count, avg_price
from dws_price_range_day
where pub_date = (select max(pub_date) from dws_price_range_day)
order by  price_range;

5.2 dws_overall_day(综合每日二级分类汇总表)


-- 按「日期 + 一级分类 + 二级分类」分组的统计 SQL

create table if not exists dws_overall_day (
    pub_date       date comment '统计日期',
    prod_cat       string comment '一级分类',
    prod_pcat      string comment '二级分类',
    record_count   int comment '记录条数',
    product_count  int comment '不同商品数',
    avg_price      decimal(10,2) comment '平均价',
    min_price      decimal(10,2) comment '最低价',
    max_price      decimal(10,2) comment '最高价'
)
comment 'DWS层-综合每日汇总表(按日×一级×二级分类)'
stored as orc
tblproperties (
    "orc.compress" = "SNAPPY"
);

insert overwrite table dws_overall_day
select
    t.pub_date,
    t.prod_cat,
    t.prod_pcat,
    t.record_count,
    t.product_count,
    t.avg_price,
    t.min_price,
    t.max_price
from (
    select
        pub_date,
        prod_cat,
        prod_pcat,
        count(*) as record_count,
        count(distinct prod_name) as product_count,
        round(avg(avg_price), 2) as avg_price,
        round(min(avg_price), 2) as min_price,
        round(max(avg_price), 2) as max_price
    from dwd_xinfadi_data
    group by pub_date, prod_cat, prod_pcat
) t;


-- 检查
select pub_date,prod_cat,prod_pcat,record_count,product_count  from dws_overall_day limit 10;

select * from dws_overall_day limit 10;

select count(*) cont from dws_overall_day;

-- 看最近一天的一、二级分类汇总
select pub_date, prod_cat, prod_pcat,
       record_count, product_count, avg_price, min_price, max_price
from dws_overall_day
where pub_date = (select max(pub_date) from dws_overall_day)
order by  record_count desc
limit  30;

5.3 dws_15day_summary(15日综合指标表)

每天使用 INSERT OVERWRITE 数据是清空后 才 插入的 不用担心日期问题

-- datediff(current_date(), pub_date)  Hive 日期差值函数:返回 日期 1 − 日期 2 相差的天数



drop table if exists dws_15day_summary;

truncate table dws_15day_summary;

create table if not exists dws_15day_summary (
    prod_cat        string comment '一级分类',
    prod_pcat       string comment '二级分类',
    prod_name       string comment '品名',
    d_14            decimal(12,2) comment '往前第14天均价',
    d_13            decimal(12,2) comment '往前第13天均价',
    d_12            decimal(12,2) comment '往前第12天均价',
    d_11            decimal(12,2) comment '往前第11天均价',
    d_10            decimal(12,2) comment '往前第10天均价',
    d_9             decimal(12,2) comment '往前第9天均价',
    d_8             decimal(12,2) comment '往前第8天均价',
    d_7             decimal(12,2) comment '往前第7天均价',
    d_6             decimal(12,2) comment '往前第6天均价',
    d_5             decimal(12,2) comment '往前第5天均价',
    d_4             decimal(12,2) comment '往前第4天均价',
    d_3             decimal(12,2) comment '往前第3天均价',
    d_2             decimal(12,2) comment '往前第2天均价',
    d_1             decimal(12,2) comment '往前第1天均价',
    d_0             decimal(12,2) comment '统计当日均价',
    avg_15d         decimal(12,2) comment '15天综合平均价',
    days_have_data  int comment '15天内有价格记录的天数',
    create_time     timestamp comment '宽表生成时间',
    stat_date       string comment '统计基准日期,格式yyyy-MM-dd'
)
comment 'DWS层-商品近15日价格透视汇总宽表,每日动态滚动最近15天'
stored as orc
tblproperties ('orc.compress'='SNAPPY');


truncate table dws_15day_summary;

insert overwrite table dws_15day_summary
select
    prod_cat,
    prod_pcat,
    prod_name,
    round(avg(case when diff=14 then avg_price end), 2) as d_14,
    round(avg(case when diff=13 then avg_price end), 2) as d_13,
    round(avg(case when diff=12 then avg_price end), 2) as d_12,
    round(avg(case when diff=11 then avg_price end), 2) as d_11,
    round(avg(case when diff=10 then avg_price end), 2) as d_10,
    round(avg(case when diff=9  then avg_price end), 2) as d_9,
    round(avg(case when diff=8  then avg_price end), 2) as d_8,
    round(avg(case when diff=7  then avg_price end), 2) as d_7,
    round(avg(case when diff=6  then avg_price end), 2) as d_6,
    round(avg(case when diff=5  then avg_price end), 2) as d_5,
    round(avg(case when diff=4  then avg_price end), 2) as d_4,
    round(avg(case when diff=3  then avg_price end), 2) as d_3,
    round(avg(case when diff=2  then avg_price end), 2) as d_2,
    round(avg(case when diff=1  then avg_price end), 2) as d_1,
    round(avg(case when diff=0  then avg_price end), 2) as d_0,
    round(avg(case when diff BETWEEN 0 and 14 then avg_price end), 2) as avg_15d,
    count(distinct case when diff BETWEEN 0 and 14 then pub_date end) as days_have_data,
    current_timestamp as create_time,
    -- 基准统计日期:执行当天完整年月日
    date_format(current_date(), 'yyyy-MM-dd') as stat_date
from (
    select
        *,
        datediff(current_date(), pub_date) as diff
    from dwd_xinfadi_data
    where datediff(current_date(), pub_date) between 0 and 14
) tmp
group by prod_cat, prod_pcat, prod_name;


select count(*) as cont from dws_15day_summary;

select  * from dws_15day_summary where stat_date='2026-06-27'  limit 5;

select d_14,d_13,d_12,d_11,d_10,d_9,d_8,d_7,d_6,d_5,d_4,d_3,d_2,d_1,d_0 from dws_15day_summary limit 5;

select place,count(*) from dwd_xinfadi_data  group by place order by  place 

5.4 dws_overall_product_day (单品日汇总表)

create table if not exists dws_overall_product_day (
    pub_date DATE comment '交易日期',
    prod_cat string comment '一级品类',
    prod_pcat string comment '二级品类',
    prod_name string comment '单品名称',
    avg_price decimal(10,2) comment '当日单品平均价格',
    min_price decimal(10,2) comment '当日单品最低价格',
    max_price decimal(10,2) comment '当日单品最高价格'
)
comment 'DWS-每日单品维度汇总宽表(含单品名,供涨跌柱状图使用)'
stored as orc
tblproperties ('orc.compress'='SNAPPY');

insert overwrite table dws_overall_product_day
select
    pub_date,
    prod_cat,
    prod_pcat,
    prod_name,
    round(avg(avg_price), 2) as avg_price,
    round(min(avg_price), 2) as min_price,
    round(max(avg_price), 2) as max_price
from dwd_xinfadi_data
group by pub_date, prod_cat, prod_pcat, prod_name;

select * from dws_overall_product_day  where pub_date= '2026-06-21'  limit 5;

6、ads 应用层:报表表 / 接口表 / 大屏数据

6.1 ads_market_today_index 当日核心数字指标(3 个大屏顶部数字)

-- date_format(日期, 格式模板)
-- concat(字段1,字段2) 是 Hive 字符串拼接函数,把两个字段的值拼成一整条字符串。

-- 1.1 建表语句
create table if not exists ads_market_today_index (
    stat_date string comment '统计日期 yyyy-MM-dd',
    total_category_cnt int comment '当日一级商品总类别总数',
    second_category_cnt int comment '当日二级商品类别总数',
    market_avg_price decimal(12,2) comment '全市场当日平均价格指数',
    create_time timestamp comment '数据生成时间'
)
comment 'ADS-当日市场核心数字指标(大屏顶部数字卡片)'
stored as orc;

truncate table ads_market_today_index;

-- 插入 SQL(不限制单天,读取全部存在的数据,自动按日期分组)
insert overwrite table ads_market_today_index
select
    date_format(t.pub_date, 'yyyy-MM-dd') as stat_date,
    count(distinct t.prod_cat) as total_category_cnt,
    count(distinct concat(t.prod_cat, t.prod_pcat)) as second_category_cnt,
    round(avg(t.avg_price), 2) as market_avg_price,
    current_timestamp as create_time
from dws_overall_day t
group by date_format(t.pub_date, 'yyyy-MM-dd');

-- 增量插入 重复传入相同日期的数据,会报错  直接OVERWRITE
insert into table ads_market_today_index
select
    '2026-06-22' as stat_date,
    count(distinct t.prod_cat) as total_category_cnt,
    count(distinct concat(t.prod_cat, t.prod_pcat)) as second_category_cnt,
    round(avg(t.avg_price), 2) as market_avg_price,
    current_timestamp as create_time
from dws_overall_day t
where date_format(t.pub_date, 'yyyy-MM-dd') = '2026-06-22'
group by date_format(t.pub_date, 'yyyy-MM-dd');

select cont(*) from ads_market_today_index;
select * from ads_market_today_index limit 5
select * from ads_market_today_index where stat_date='2026-06-22';

6.2 ads_market_price_distribution 价格区间饼图

create table if not exists ads_market_price_distribution (
    stat_date string comment '统计日期',
    price_range string comment '价格区间',
    record_count int comment '区间供货记录数',
    product_count int comment '区间商品种类数',
    ratio decimal(5,2) comment '区间占比%',
    create_time timestamp
)
comment 'ADS-每日价格区间分布 饼图数据源'
stored as orc;


insert overwrite table ads_market_price_distribution
select
    date_format(pub_date, 'yyyy-MM-dd') as stat_date,
    price_range,
    record_count,
    sum(record_count) over(partition by  date_format(pub_date, 'yyyy-MM-dd')) as record_total,
    round(record_count / sum(record_count) over(partition by date_format(pub_date, 'yyyy-MM-dd')) * 100, 2) as ratio,
    current_timestamp as create_time
from dws_price_range_day;


insert overwrite table ads_market_price_distribution
select
    '2026-06-22' as stat_date,
    price_range,
    record_count,
    sum(record_count) over() as record_total,
    round(record_count / sum(record_count) over() * 100, 2) as ratio,
    current_timestamp as create_time
from dws_price_range_day
where date_format(pub_date, 'yyyy-MM-dd') = '2026-06-22';

select * from ads_market_price_distribution limit 20 ;

6.3 ads_market_day_rise_fall 当日涨幅 / 跌幅柱状图共用表

create table if not exists ads_market_day_rise_fall (
    stat_date string comment '统计日期',
    prod_cat string comment '一级分类',
    prod_pcat string comment '二级分类',
    prod_name string comment '商品名称',
    yesterday_avg decimal(12,2) comment '前一日均价',
    today_avg decimal(12,2) comment '当日均价',
    price_diff decimal(12,2) comment '涨跌额',
    rise_ratio decimal(5,2) comment '涨跌幅百分比',
    flag string comment 'rise涨幅 / fall跌幅',
    create_time timestamp
)
comment 'ADS-当日商品涨跌明细,涨幅、跌幅柱状图共用'
stored as orc;



insert overwrite table ads_market_day_rise_fall
select
    '2026-06-22' as stat_date,
    today.prod_cat,
    today.prod_pcat,
    today.prod_name,
    yesterday.avg_price as yesterday_avg,
    today.avg_price as today_avg,
    round(today.avg_price - yesterday.avg_price, 2) as price_diff,
    case
        when yesterday.avg_price = 0 then 0
        else round((today.avg_price - yesterday.avg_price) / yesterday.avg_price * 100, 2)
    end as rise_ratio,
    case
        when today.avg_price > yesterday.avg_price then 'rise'
        else 'fall'
    end as flag,
    current_timestamp as create_time
from dws_overall_product_day today
left join dws_overall_product_day yesterday
    on today.prod_cat = yesterday.prod_cat
    and today.prod_pcat = yesterday.prod_pcat
    and today.prod_name = yesterday.prod_name
    and yesterday.pub_date = date_sub('2026-06-22', 1)
where today.pub_date = '2026-06-22'
  and yesterday.avg_price is not null ;


  select * from ads_market_day_rise_fall limit 5;

6.4 ads_market_trend_line 所有折线趋势统一宽表

-- 4.1 建表语句
create table if not exists ads_market_trend_line (
    stat_date string comment '统计基准日(15天窗口终点)',
    line_name string comment '折线名称',
    day_0 decimal(12,2),
    day_1 decimal(12,2),
    day_2 decimal(12,2),
    day_3 decimal(12,2),
    day_4 decimal(12,2),
    day_5 decimal(12,2),
    day_6 decimal(12,2),
    day_7 decimal(12,2),
    day_8 decimal(12,2),
    day_9 decimal(12,2),
    day_10 decimal(12,2),
    day_11 decimal(12,2),
    day_12 decimal(12,2),
    day_13 decimal(12,2),
    day_14 decimal(12,2),
    create_time timestamp
)
comment 'ADS-7日/15日全部折线图时序宽表'
stored as orc;


insert overwrite table ads_market_trend_line
select
    '2026-06-27' as stat_date,
    prod_cat as line_name,
    round(avg(d_0), 2) as day_0,
    round(avg(d_1), 2) as day_1,
    round(avg(d_2), 2) as day_2,
    round(avg(d_3), 2) as day_3,
    round(avg(d_4), 2) as day_4,
    round(avg(d_5), 2) as day_5,
    round(avg(d_6), 2) as day_6,
    round(avg(d_7), 2) as day_7,
    round(avg(d_8), 2) as day_8,
    round(avg(d_9), 2) as day_9,
    round(avg(d_10), 2) as day_10,
    round(avg(d_11), 2) as day_11,
    round(avg(d_12), 2) as day_12,
    round(avg(d_13), 2) as day_13,
    round(avg(d_14), 2) as day_14,
    current_timestamp as create_time
from dws_15day_summary
where stat_date = '2026-06-27'
  and prod_cat IN ('蔬菜','肉禽蛋','水果')
group by prod_cat;

select * from ads_market_trend_line limit 3;