Add one-skill

This commit is contained in:
Xin Wang
2026-05-13 11:03:00 +08:00
parent a4c8b29176
commit f9e36ef92d
34 changed files with 7656 additions and 0 deletions

View File

@@ -0,0 +1,209 @@
-- =====================================================================
-- @Name: DORIS-D-SQL-{表名}-CREATE
-- @Version: 1.0
-- @Desc: Apache Doris 建表模板OLAP 多模型)
-- @TargetDatabase: Apache Doris
-- =====================================================================
-- ============================================================================
-- 场景1Duplicate Key 模型(明细表)
-- ============================================================================
-- 适用:保留原始明细数据,不做预聚合,数据无冗余
-- 特点:数据按 Key 排序存储,支持所有列的查询和聚合
CREATE TABLE IF NOT EXISTS db_name.detail_table (
-- Key 列(排序字段)
order_id BIGINT COMMENT '订单ID',
order_date DATE COMMENT '订单日期',
user_id BIGINT COMMENT '用户ID',
-- Value 列
user_name VARCHAR(50) COMMENT '用户姓名',
product_id BIGINT COMMENT '商品ID',
product_name VARCHAR(200) COMMENT '商品名称',
quantity INT COMMENT '购买数量',
unit_price DECIMAL(18,2) COMMENT '单价',
total_amount DECIMAL(18,2) COMMENT '总金额',
status VARCHAR(20) COMMENT '订单状态',
create_time DATETIME COMMENT '创建时间'
)
DUPLICATE KEY(order_id, order_date, user_id)
COMMENT '订单明细表'
PARTITION BY RANGE(order_date) (
PARTITION p202601 VALUES LESS THAN ('2026-02-01'),
PARTITION p202602 VALUES LESS THAN ('2026-03-01'),
PARTITION p202603 VALUES LESS THAN ('2026-04-01')
)
DISTRIBUTED BY HASH(order_id) BUCKETS 8
PROPERTIES (
'replication_num' = '3',
'storage_format' = 'V2'
);
-- ============================================================================
-- 场景2Aggregate Key 模型(聚合表)
-- ============================================================================
-- 适用:预聚合场景,相同 Key 的数据自动合并
-- 特点Value 列必须指定聚合函数SUM, REPLACE, MAX, MIN, HLL_UNION, BITMAP_UNION
CREATE TABLE IF NOT EXISTS db_name.agg_table (
-- Key 列(聚合维度)
stat_date DATE COMMENT '统计日期',
department VARCHAR(100) COMMENT '部门名称',
region VARCHAR(100) COMMENT '地区',
-- Value 列(带聚合函数)
order_count BIGINT SUM COMMENT '订单总数',
total_amount DECIMAL(18,2) SUM COMMENT '总金额',
unique_users BIGINT REPLACE COMMENT '去重用户数(预计算值)',
max_amount DECIMAL(18,2) MAX COMMENT '最大金额',
last_update DATETIME REPLACE COMMENT '最后更新时间'
)
AGGREGATE KEY(stat_date, department, region)
COMMENT '部门销售聚合表'
PARTITION BY RANGE(stat_date) (
PARTITION p202601 VALUES LESS THAN ('2026-02-01'),
PARTITION p202602 VALUES LESS THAN ('2026-03-01')
)
DISTRIBUTED BY HASH(department) BUCKETS 8
PROPERTIES (
'replication_num' = '3',
'storage_format' = 'V2'
);
-- ============================================================================
-- 场景3Unique Key 模型(唯一主键表)
-- ============================================================================
-- 适用:需要按主键更新/去重的场景
-- 特点:相同主键的数据保留最新一条(整行替换)
CREATE TABLE IF NOT EXISTS db_name.unique_table (
-- Key 列(主键,必须唯一)
user_id BIGINT COMMENT '用户ID',
-- Value 列
user_name VARCHAR(50) COMMENT '用户姓名',
phone VARCHAR(20) COMMENT '手机号',
email VARCHAR(100) COMMENT '邮箱',
vip_level INT COMMENT 'VIP等级',
register_date DATE COMMENT '注册日期',
last_login DATETIME COMMENT '最后登录时间',
status VARCHAR(10) COMMENT '状态'
)
UNIQUE KEY(user_id)
COMMENT '用户信息表(按主键更新)'
DISTRIBUTED BY HASH(user_id) BUCKETS 16
PROPERTIES (
'replication_num' = '3',
'enable_unique_key_merge_based_on_replica' = 'true'
);
-- ============================================================================
-- 场景4带动态分区属性
-- ============================================================================
-- 适用:按日自动创建和管理分区
CREATE TABLE IF NOT EXISTS db_name.auto_partition_table (
stat_date DATE COMMENT '统计日期',
department VARCHAR(100) COMMENT '部门',
metric_value DECIMAL(18,2) SUM COMMENT '指标值',
record_count BIGINT SUM COMMENT '记录数'
)
AGGREGATE KEY(stat_date, department)
COMMENT '自动分区示例表'
PARTITION BY RANGE(stat_date) ()
DISTRIBUTED BY HASH(department) BUCKETS 8
PROPERTIES (
'replication_num' = '3',
'dynamic_partition.enable' = 'true',
'dynamic_partition.time_unit' = 'DAY',
'dynamic_partition.start' = '-30', -- 保留30天历史
'dynamic_partition.end' = '3', -- 预创建3天
'dynamic_partition.prefix' = 'p',
'dynamic_partition.buckets' = '8'
);
-- ============================================================================
-- 场景5多分区 + 多分桶
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.multi_partition_table (
stat_date DATE COMMENT '统计日期',
region VARCHAR(50) COMMENT '地区',
city VARCHAR(50) COMMENT '城市',
user_id BIGINT COMMENT '用户ID',
amount DECIMAL(18,2) SUM COMMENT '金额'
)
AGGREGATE KEY(stat_date, region, city, user_id)
COMMENT '多维度分区示例'
PARTITION BY RANGE(stat_date) (
PARTITION p202601 VALUES LESS THAN ('2026-02-01'),
PARTITION p202602 VALUES LESS THAN ('2026-03-01')
)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES (
'replication_num' = '3',
'in_memory' = 'false',
'storage_format' = 'V2',
'compression' = 'LZ4'
);
-- ============================================================================
-- 字段类型速查
-- ============================================================================
/*
| 类型 | 说明 | 适用场景 |
|---------------|----------------|------------------------|
| BOOLEAN | 布尔 | 状态标志 |
| TINYINT | 1字节整数 | 小范围枚举 |
| SMALLINT | 2字节整数 | 小范围数值 |
| INT | 4字节整数 | 数量、等级 |
| BIGINT | 8字节整数 | ID、计数、大数值 |
| LARGEINT | 16字节整数 | 超大数值 |
| FLOAT | 4字节浮点 | 近似计算 |
| DOUBLE | 8字节浮点 | 科学计算 |
| DECIMAL(p,s) | 定点数 | 金额、精确数值 |
| DATE | 日期 | 日期字段(无时间) |
| DATETIME | 日期时间 | 时间戳(精确到秒) |
| CHAR(n) | 定长字符串 | 固定长度编码 |
| VARCHAR(n) | 变长字符串 | 名称、描述 |
| STRING | 变长字符串 | 大文本(无长度限制) |
| BITMAP | 位图 | 精确去重(仅聚合模型) |
| HLL | HyperLogLog | 近似去重(仅聚合模型) |
| JSON | JSON | JSON数据存储 |
*/
-- ============================================================================
-- 建表规范说明
-- ============================================================================
/*
1. 模型选择
- Duplicate Key保留原始明细不做预聚合
- Aggregate Key预聚合相同 Key 的 Value 自动合并
- Unique Key按主键去重保留最新数据
2. 分区设计
- 按时间字段 RANGE 分区(最常用)
- 支持动态分区自动管理
- 单分区数据量建议 1GB~10GB
3. 分桶设计
- 使用高基数列做 HASH 分桶
- 分桶数 = BE节点数 × CPU核数参考值
- 单桶数据量建议 100MB~1GB
4. 副本数
- 生产环境建议 3 副本
- 测试环境可设 1 副本
5. Key 列选择
- Duplicate Key高频过滤/排序字段
- Aggregate Key聚合维度字段
- Unique Key业务主键
6. 注意事项
- Key 列必须在 Value 列之前
- 分区列必须是 Key 列
- 分桶列必须是 Key 列
- BITMAP/HLL 仅用于 Aggregate 模型的 Value 列
*/

View File

@@ -0,0 +1,128 @@
-- =====================================================================
-- @Name: DORIS-D-SQL-{表名}-ETL
-- @Version: 2.0
-- @Desc: Apache Doris ETL 数据处理模板(临时表链式处理)
-- @TargetDatabase: Apache Doris
-- @说明: 统一规范:禁止 CTE每步物化为临时表先 DROP 再 CREATE
-- =====================================================================
-- ============================================================================
-- Step01: 基础清洗与过滤
-- ============================================================================
-- 说明:从源表读取数据,进行基础过滤和清洗
-- 输入:{源表名}
-- 输出:${db_tmp_env}.tmp_xxx_01
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT
order_id,
user_id,
dept_id,
total_amount,
status,
order_date
FROM db_name.source_table
WHERE order_date = '${day_id}'
AND status IN ('completed', 'shipped') -- 业务过滤
AND total_amount > 0 -- 数据质量过滤
AND user_id IS NOT NULL; -- NULL过滤
-- ============================================================================
-- Step02: 多表关联与维度补全
-- ============================================================================
-- 说明:关联维度表,补全业务属性字段
-- 输入:${db_tmp_env}.tmp_xxx_01, dim_department, dim_category
-- 输出:${db_tmp_env}.tmp_xxx_02
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT
a.order_id,
a.user_id,
a.total_amount,
a.status,
b.dept_name, -- 维度补全:部门名称
c.category_name, -- 维度补全:类别名称
a.order_date
FROM ${db_tmp_env}.tmp_xxx_01 a
LEFT JOIN db_name.dim_department b
ON a.dept_id = b.dept_id
LEFT JOIN db_name.dim_category c
ON a.category_id = c.category_id;
-- ============================================================================
-- Step03: 聚合计算与指标生成
-- ============================================================================
-- 说明:按业务维度聚合,计算统计指标
-- 输入:${db_tmp_env}.tmp_xxx_02
-- 输出:${db_tmp_env}.tmp_xxx_03
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_03;
CREATE TABLE ${db_tmp_env}.tmp_xxx_03 AS
SELECT
order_date,
dept_name,
category_name,
COUNT(*) AS record_count, -- 记录数
COUNT(DISTINCT user_id) AS unique_users, -- 去重用户数
SUM(total_amount) AS total_amount, -- 总金额
AVG(total_amount) AS avg_amount, -- 平均金额
MAX(total_amount) AS max_amount -- 最大金额
FROM ${db_tmp_env}.tmp_xxx_02
GROUP BY order_date, dept_name, category_name;
-- ============================================================================
-- Step04: 最终输出写入目标表
-- ============================================================================
-- 说明:补全目标表标准字段,写入结果表
-- 输入:${db_tmp_env}.tmp_xxx_03
-- 输出:目标表
INSERT INTO ${db_eda_env}.target_table
SELECT
-- 业务字段
dept_name,
category_name,
record_count,
unique_users,
total_amount,
avg_amount,
max_amount,
-- 技术字段
NOW() AS etl_time, -- 数据加工时间
'${day_id}' AS stat_date -- 统计日期
FROM ${db_tmp_env}.tmp_xxx_03;
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. 禁止使用 CTE (WITH 子句)
- 每个步骤必须物化为临时表
- 原因:便于调试、断点续跑、统一编码规范
2. 先 DROP 再 CREATE
- 每个临时表创建前必须先 DROP TABLE IF EXISTS
- 原因:防止表已存在导致失败
3. Doris 写入方式
- 默认使用 INSERT INTO
- Aggregate Key 表:自动合并相同 Key 的数据
- Unique Key 表:自动按主键去重,保留最新数据
- Doris 2.0+ 也支持 INSERT OVERWRITE
4. 过滤条件前置
- 所有过滤在最早阶段应用
- 减少中间数据量
5. 临时表命名规范
- 格式tmp_{业务简称}_{步骤序号}
- 示例tmp_order_stats_01, tmp_order_stats_02
6. Doris 特有注意事项
- 不支持 LEFT SEMI JOIN / LEFT ANTI JOIN
- 日期函数用 MySQL 风格DATE_FORMAT, DATE_ADD(INTERVAL)
- 不支持 collect_list/collect_set用 GROUP_CONCAT 替代
*/

View File

@@ -0,0 +1,147 @@
-- =====================================================================
-- @Name: DORIS-D-SQL-{表名}-INSERT
-- @Version: 1.0
-- @Desc: Apache Doris 数据插入模板
-- @TargetDatabase: Apache Doris
-- =====================================================================
-- ============================================================================
-- 场景1INSERT INTO追加写入
-- ============================================================================
-- 适用:向 Doris 表追加数据,不会删除已有数据
INSERT INTO db_name.target_table
SELECT
stat_date,
department,
region,
order_count,
total_amount
FROM db_name.source_table
WHERE stat_date = '${day_id}';
-- ============================================================================
-- 场景2INSERT OVERWRITE覆盖写入
-- ============================================================================
-- 适用:覆盖目标表(或指定分区)的全部数据
-- 注意Doris 2.0+ 支持,且仅适用于 Partition 表
-- 覆盖整表
INSERT OVERWRITE db_name.target_table
SELECT
stat_date,
department,
region,
order_count,
total_amount
FROM db_name.source_table;
-- 覆盖指定分区(推荐)
INSERT OVERWRITE db_name.target_table
PARTITION(p202605)
SELECT
department,
region,
order_count,
total_amount
FROM db_name.source_table
WHERE stat_date >= '2026-05-01'
AND stat_date < '2026-06-01';
-- ============================================================================
-- 场景3从查询结果写入ETL 场景)
-- ============================================================================
-- 简单转换后写入
INSERT INTO db_name.target_table
SELECT
order_date,
department,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(total_amount) AS total_amount,
AVG(total_amount) AS avg_amount
FROM db_name.source_orders o
LEFT JOIN db_name.dim_department d ON o.dept_id = d.dept_id
WHERE o.order_date = '${day_id}'
GROUP BY order_date, department;
-- ============================================================================
-- 场景4批量 VALUES 写入
-- ============================================================================
INSERT INTO db_name.target_table (stat_date, department, amount)
VALUES
('2026-05-01', '市场部', 10000.00),
('2026-05-01', '技术部', 25000.00),
('2026-05-01', '运营部', 18000.00);
-- ============================================================================
-- 场景5Stream Load数据导入
-- ============================================================================
-- 适用:大批量数据导入(百万级以上)
-- 注意Stream Load 通过 HTTP 协议提交,不是 SQL 语法
/*
-- curl 命令示例
curl --location-trusted -u user:password \
-H "label:load_order_20260501" \
-H "column_separator:," \
-H "columns:order_id,order_date,user_id,total_amount" \
-T data.csv \
http://fe_host:8030/api/db_name/orders/_stream_load
*/
-- ============================================================================
-- 场景6Broker Load外部数据源导入
-- ============================================================================
/*
LOAD LABEL db_name.load_label_20260501
(
DATA INFILE('hdfs://namenode:8020/path/to/data/*')
INTO TABLE target_table
COLUMNS TERMINATED BY ','
(stat_date, department, region, amount)
SET (amount = amount * 1.0)
)
WITH BROKER 'broker_name'
(
'username' = 'hdfs_user',
'password' = 'hdfs_password'
)
PROPERTIES
(
'timeout' = '3600',
'max_filter_ratio' = '0.01'
);
*/
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. INSERT INTO vs INSERT OVERWRITE
- INSERT INTO追加数据不删除已有数据
- INSERT OVERWRITE覆盖数据Doris 2.0+ 支持)
- 日常增量推荐 INSERT INTO全量刷新推荐 INSERT OVERWRITE
2. Doris 不使用临时表链式处理
- 与 Spark 不同Doris 通常用单条 SQL 或 CTE 完成 ETL
- 直接 INSERT INTO ... SELECT ... 即可
3. 字段顺序
- SELECT 字段顺序必须与目标表列定义顺序一致
- 或显式指定列名INSERT INTO table (col1, col2) SELECT ...
4. 数据导入方式选择
- 少量数据INSERT INTO ... SELECT ... 或 INSERT INTO ... VALUES ...
- 大批量导入Stream LoadHTTP PUT最高性能
- HDFS 导入Broker Load
- 外部数据源Routine LoadKafka 等)
5. 性能建议
- 批量写入优于逐条写入
- Stream Load 是最高性能的导入方式
- 建议攒批后一次性写入,避免频繁小批量导入
*/

View File

@@ -0,0 +1,189 @@
-- =====================================================================
-- @Name: DORIS-D-SQL-{表名}-QUERY
-- @Version: 1.0
-- @Desc: Apache Doris 查询模板
-- @TargetDatabase: Apache Doris
-- =====================================================================
-- ============================================================================
-- 1. 单表查询
-- ============================================================================
SELECT
order_id,
user_name,
total_amount,
create_time
FROM db_name.orders
WHERE order_date = '${day_id}'
AND status = 'completed'
ORDER BY total_amount DESC
LIMIT 100;
-- ============================================================================
-- 2. JOIN 查询
-- ============================================================================
-- 两表 JOIN
SELECT
o.order_id,
o.total_amount,
u.user_name,
u.vip_level
FROM db_name.orders o
JOIN db_name.users u ON o.user_id = u.user_id
WHERE o.order_date = '${day_id}'
AND o.status = 'completed';
-- 多表 JOIN
SELECT
o.order_id,
u.user_name,
p.product_name,
oi.quantity,
oi.unit_price
FROM db_name.orders o
JOIN db_name.users u ON o.user_id = u.user_id
JOIN db_name.order_items oi ON o.order_id = oi.order_id
JOIN db_name.products p ON oi.product_id = p.product_id
WHERE o.order_date BETWEEN '${start_date}' AND '${end_date}';
-- ============================================================================
-- 3. 聚合查询
-- ============================================================================
-- GROUP BY + HAVING
SELECT
department,
COUNT(*) AS order_count,
SUM(total_amount) AS total_amount,
AVG(total_amount) AS avg_amount
FROM db_name.orders
WHERE order_date = '${day_id}'
GROUP BY department
HAVING COUNT(*) >= 5
ORDER BY total_amount DESC;
-- 多字段分组 + 去重计数
SELECT
order_date,
region,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(total_amount) AS total_amount
FROM db_name.orders
WHERE order_date BETWEEN '${start_date}' AND '${end_date}'
GROUP BY order_date, region;
-- ============================================================================
-- 4. 窗口函数
-- ============================================================================
-- ROW_NUMBER分组取Top N
SELECT *
FROM (
SELECT
department,
user_name,
total_amount,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY total_amount DESC) AS rn
FROM db_name.orders
WHERE order_date = '${day_id}'
) t
WHERE rn <= 3;
-- 累计聚合
SELECT
order_date,
daily_amount,
SUM(daily_amount) OVER (ORDER BY order_date) AS cumulative_amount,
AVG(daily_amount) OVER (
ORDER BY order_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg_7d
FROM (
SELECT order_date, SUM(total_amount) AS daily_amount
FROM db_name.orders
GROUP BY order_date
) t;
-- LAG/LEAD环比计算
SELECT
order_date,
daily_amount,
LAG(daily_amount, 1) OVER (ORDER BY order_date) AS prev_amount,
daily_amount - LAG(daily_amount, 1) OVER (ORDER BY order_date) AS daily_change,
ROUND(
(daily_amount - LAG(daily_amount, 1) OVER (ORDER BY order_date))
/ LAG(daily_amount, 1) OVER (ORDER BY order_date) * 100, 2
) AS growth_rate_pct
FROM (
SELECT order_date, SUM(total_amount) AS daily_amount
FROM db_name.orders
GROUP BY order_date
) t;
-- ============================================================================
-- 5. 分页查询
-- ============================================================================
-- LIMIT OFFSET 分页Doris 原生支持)
SELECT
order_id, user_name, total_amount
FROM db_name.orders
WHERE order_date = '${day_id}'
ORDER BY order_id
LIMIT 20 OFFSET 0; -- 第1页每页20条
-- ============================================================================
-- 6. 子查询
-- ============================================================================
-- IN 子查询
SELECT *
FROM db_name.orders
WHERE user_id IN (
SELECT user_id FROM db_name.users WHERE vip_level >= 3
)
AND order_date = '${day_id}';
-- EXISTS 子查询
SELECT *
FROM db_name.products p
WHERE EXISTS (
SELECT 1 FROM db_name.inventory i
WHERE i.product_id = p.product_id
AND i.quantity > 0
);
-- ============================================================================
-- 7. 条件聚合CASE WHEN + 聚合)
-- ============================================================================
SELECT
order_date,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
SUM(CASE WHEN total_amount > 1000 THEN total_amount ELSE 0 END) AS high_value_amount
FROM db_name.orders
WHERE order_date = '${day_id}'
GROUP BY order_date;
-- ============================================================================
-- 8. Bitmap 精确去重Doris 特有)
-- ============================================================================
-- 使用 bitmap 做精确去重(需要在 Aggregate Key 模型中定义 BITMAP 类型列)
-- 注意bitmap 函数只能用于包含 BITMAP 类型列的表
-- 精确去重计数(预计算场景,在 Aggregate Key 表中使用 BITMAP_UNION
-- 建表时 Value 列定义user_id_bitmap BITMAP BITMAP_UNION
-- 查询时:
-- SELECT date, bitmap_union_count(user_id_bitmap) AS uv FROM table GROUP BY date;
-- HLL 近似去重
-- 建表时 Value 列定义user_id_hll HLL HLL_UNION
-- 查询时:
-- SELECT date, hll_union_agg(user_id_hll) AS approx_uv FROM table GROUP BY date;

View File

@@ -0,0 +1,211 @@
-- =====================================================================
-- @Name: HIVE-D-SQL-{表名}-CREATE
-- @Version: 1.0
-- @Desc: Hive 建表模板(内部表/外部表/分区/分桶)
-- @TargetDatabase: Hive
-- =====================================================================
-- ============================================================================
-- 场景1内部表Managed Table
-- ============================================================================
-- 适用Hive 管理数据和元数据DROP TABLE 时数据一并删除
CREATE TABLE IF NOT EXISTS db_name.managed_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
category STRING COMMENT '类别',
amount DECIMAL(18,2) COMMENT '金额',
status STRING COMMENT '状态',
created_at TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP COMMENT '更新时间',
etl_time TIMESTAMP COMMENT '数据加工时间',
etl_remark STRING COMMENT '备注信息'
)
COMMENT '内部表示例'
STORED AS ORC; -- 推荐存储格式
-- ============================================================================
-- 场景2外部表External Table
-- ============================================================================
-- 适用数据由外部系统管理DROP TABLE 只删元数据不删数据
CREATE EXTERNAL TABLE IF NOT EXISTS db_name.external_table (
id BIGINT COMMENT '主键ID',
user_id STRING COMMENT '用户ID',
action STRING COMMENT '操作类型',
page_url STRING COMMENT '页面URL',
ip_address STRING COMMENT 'IP地址',
event_time TIMESTAMP COMMENT '事件时间'
)
COMMENT '日志外部表'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION '/data/external/logs/';
-- ============================================================================
-- 场景3分区表单分区
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.partitioned_table (
id BIGINT COMMENT '主键ID',
user_id STRING COMMENT '用户ID',
user_name STRING COMMENT '用户姓名',
order_count BIGINT COMMENT '订单数',
total_amount DECIMAL(18,2) COMMENT '总金额',
department STRING COMMENT '部门',
region STRING COMMENT '地区',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '按日分区的统计表'
PARTITIONED BY (day_id STRING COMMENT '统计日期格式yyyy-MM-dd')
STORED AS ORC;
-- ============================================================================
-- 场景4多分区字段
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.multi_partition_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '多分区字段示例表'
PARTITIONED BY (
year_id STRING COMMENT '年份',
month_id STRING COMMENT '月份'
)
STORED AS ORC;
-- ============================================================================
-- 场景5分桶表
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.bucketed_table (
id BIGINT COMMENT '主键ID',
user_id BIGINT COMMENT '用户ID',
user_name STRING COMMENT '用户姓名',
amount DECIMAL(18,2) COMMENT '金额'
)
COMMENT '分桶表示例'
PARTITIONED BY (day_id STRING)
CLUSTERED BY (user_id) -- 分桶列
SORTED BY (amount DESC) -- 桶内排序
INTO 16 BUCKETS -- 桶数量
STORED AS ORC;
-- ============================================================================
-- 场景6ORC 格式 + 表属性
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.orc_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT 'ORC格式带属性配置'
PARTITIONED BY (day_id STRING)
STORED AS ORC
TBLPROPERTIES (
'orc.compress' = 'SNAPPY', -- 压缩格式
'orc.create.index' = 'true', -- 创建索引
'transactional' = 'false' -- 非事务表
);
-- ============================================================================
-- 场景7Parquet 格式
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.parquet_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
tags ARRAY<STRING> COMMENT '标签数组',
props MAP<STRING,STRING> COMMENT '属性映射'
)
COMMENT 'Parquet格式表示例'
PARTITIONED BY (day_id STRING)
STORED AS PARQUET
TBLPROPERTIES (
'parquet.compression' = 'SNAPPY'
);
-- ============================================================================
-- 场景8临时表
-- ============================================================================
-- 会话级临时表(会话结束自动删除)
CREATE TEMPORARY TABLE tmp_processing (
id BIGINT,
name STRING,
amount DECIMAL(18,2)
);
-- CTAS 快速创建临时表
CREATE TEMPORARY TABLE tmp_source AS
SELECT id, name, amount
FROM source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 字段类型速查
-- ============================================================================
/*
| 类型 | 说明 | 适用场景 |
|-------------------|----------------|------------------------|
| TINYINT | 1字节整数 | 小范围数值 |
| SMALLINT | 2字节整数 | 小范围数值 |
| INT | 4字节整数 | 数量、等级 |
| BIGINT | 8字节整数 | ID、计数 |
| FLOAT | 4字节浮点 | 近似计算 |
| DOUBLE | 8字节浮点 | 科学计算 |
| DECIMAL(p,s) | 定点数 | 金额、精确数值 |
| BOOLEAN | 布尔 | 状态标志 |
| STRING | 变长字符串 | 名称、描述(最常用) |
| VARCHAR(n) | 变长字符串 | 限定长度字符串 |
| CHAR(n) | 定长字符串 | 固定长度编码 |
| DATE | 日期 | 日期字段 |
| TIMESTAMP | 时间戳 | 时间字段 |
| BINARY | 二进制 | 二进制数据 |
| ARRAY<type> | 数组 | 多值字段 |
| MAP<k,v> | 映射 | 属性字典 |
| STRUCT<f1:t1,...> | 结构体 | 嵌套结构 |
*/
-- ============================================================================
-- 建表规范说明
-- ============================================================================
/*
1. 内部表 vs 外部表
- 内部表Hive 管理数据DROP 删数据和元数据
- 外部表外部管理数据DROP 只删元数据
- 生产推荐:原始数据用外部表,加工结果用内部表
2. 存储格式选择
- ORC推荐压缩好列存储支持谓词下推
- PARQUET跨平台兼容好列存储
- TEXTFILE原始数据导入性能最差
3. 分区设计
- 按时间分区最常用day_id, month_id
- 分区列不能出现在表定义的列中Hive 特有)
- 查询时分区列作为普通字段使用
4. 分桶设计
- 选择高基数列做分桶列
- 用于优化 JOIN分桶列相同可做 map-side join
- 用于数据抽样TABLESAMPLE
5. 字段命名规范
- snake_case 格式user_id, total_amount
- 主键id 或 {业务}_id
- 技术字段etl_time, etl_remark
- 分区字段day_id, month_id, year_id
6. COMMENT 必须添加
- 每个字段必须有 COMMENT
- 表必须有 COMMENT
*/

View File

@@ -0,0 +1,138 @@
-- =====================================================================
-- @Name: HIVE-D-SQL-{表名}-ETL
-- @Version: 1.0
-- @Desc: Hive ETL 数据处理模板(临时表链式处理)
-- @TargetDatabase: Hive
-- @说明: 和 Spark 类似,禁止 CTE每步物化为临时表
-- =====================================================================
-- ============================================================================
-- Step01: 基础清洗与过滤
-- ============================================================================
-- 说明:从源表读取数据,进行基础过滤和清洗
-- 输入:{源表名}
-- 输出tmp_etl_01
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT
id,
name,
amount,
status,
dept_id,
category_id,
created_at,
day_id
FROM db_name.source_table
WHERE day_id = '${day_id}' -- 分区过滤(必须)
AND status IN ('active', 'valid') -- 业务过滤
AND amount > 0 -- 数据质量过滤
AND id IS NOT NULL; -- NULL过滤
-- ============================================================================
-- Step02: 多表关联与维度补全
-- ============================================================================
-- 说明:关联维度表,补全业务属性字段
-- 输入tmp_xxx_01, dim_department, dim_category
-- 输出tmp_xxx_02
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT
a.id,
a.name,
a.amount,
a.status,
b.dept_name, -- 维度补全:部门名称
c.category_name, -- 维度补全:类别名称
a.created_at,
a.day_id
FROM ${db_tmp_env}.tmp_xxx_01 a
LEFT JOIN db_name.dim_department b
ON a.dept_id = b.dept_id
AND b.day_id = '${day_id}' -- 维度表分区过滤
LEFT JOIN db_name.dim_category c
ON a.category_id = c.category_id
AND c.day_id = '${day_id}'; -- 维度表分区过滤
-- ============================================================================
-- Step03: 聚合计算与指标生成
-- ============================================================================
-- 说明:按业务维度聚合,计算统计指标
-- 输入tmp_xxx_02
-- 输出tmp_xxx_03
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_03;
CREATE TABLE ${db_tmp_env}.tmp_xxx_03 AS
SELECT
day_id,
dept_name,
category_name,
COUNT(*) AS record_count, -- 记录数
COUNT(DISTINCT id) AS unique_count, -- 唯一计数
SUM(amount) AS total_amount, -- 总金额
AVG(amount) AS avg_amount, -- 平均金额
MAX(amount) AS max_amount, -- 最大金额
MIN(amount) AS min_amount -- 最小金额
FROM ${db_tmp_env}.tmp_xxx_02
GROUP BY day_id, dept_name, category_name;
-- ============================================================================
-- Step04: 最终输出写入目标表
-- ============================================================================
-- 说明:补全目标表标准字段,写入结果表
-- 输入tmp_xxx_03
-- 输出:目标表
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT
-- 业务字段
dept_name,
category_name,
record_count,
unique_count,
total_amount,
avg_amount,
max_amount,
min_amount,
-- 技术字段
current_timestamp() AS etl_time, -- 数据加工时间
'${day_id}' AS stat_date; -- 统计日期
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. 禁止使用 CTE (WITH 子句)
- 每个步骤必须物化为临时表
- 原因Hive CTE 可能在某些版本有性能问题
- 物化临时表便于调试和断点续跑
2. 先 DROP 再 CREATE
- 每个临时表创建前必须先 DROP TABLE IF EXISTS
- 原因:防止表已存在导致失败
3. 分区过滤必须前置
- 所有源表和维度表查询必须带 day_id 过滤
- 原因:避免全表扫描,提升性能
4. JOIN 条件下推
- 维度表关联时带上分区过滤条件
- 原因:减少关联数据量
5. 临时表命名规范
- 格式tmp_{业务简称}_{步骤序号}
- 示例tmp_order_stats_01, tmp_order_stats_02
6. 目标表写入规范
- 使用 INSERT OVERWRITE覆盖写入幂等
- 明确指定分区
- 补全技术字段etl_time 等)
7. 存储格式建议
- 临时表:默认格式即可(中间结果不需要优化存储)
- 如需优化STORED AS ORC
*/

View File

@@ -0,0 +1,141 @@
-- =====================================================================
-- @Name: HIVE-D-SQL-{表名}-INSERT
-- @Version: 1.0
-- @Desc: Hive 数据插入模板
-- @TargetDatabase: Hive
-- =====================================================================
-- ============================================================================
-- 场景1分区表覆盖写入最常用
-- ============================================================================
-- 适用:每日/每周/每月增量写入分区表
INSERT OVERWRITE TABLE db_name.target_table
PARTITION (day_id = '${day_id}')
SELECT
user_id,
user_name,
order_count,
total_amount,
current_timestamp() AS etl_time
FROM db_name.source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景2动态分区写入
-- ============================================================================
-- 适用:数据中包含分区值,自动写入对应分区
-- 先启用动态分区
SET hive.exec.dynamic.partition = true;
SET hive.exec.dynamic.partition.mode = nonstrict;
INSERT OVERWRITE TABLE db_name.target_table
PARTITION (day_id, region) -- 动态分区字段
SELECT
user_id,
user_name,
order_count,
total_amount,
current_timestamp() AS etl_time,
day_id, -- 分区字段1数据中包含
region -- 分区字段2数据中包含
FROM db_name.source_table
WHERE day_id BETWEEN '${start_day}' AND '${end_day}';
-- ============================================================================
-- 场景3追加写入
-- ============================================================================
-- 适用:日志表、流水表(允许追加)
INSERT INTO TABLE db_name.target_table
SELECT
field1,
field2,
field3,
current_timestamp() AS etl_time
FROM db_name.source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景4多分区插入Multi-Insert
-- ============================================================================
-- 适用:一次扫描,写入多个目标(提高效率)
FROM db_name.source_table
INSERT OVERWRITE TABLE db_name.target_summary
PARTITION (day_id = '${day_id}')
SELECT
department,
COUNT(*) AS record_count,
SUM(amount) AS total_amount
WHERE day_id = '${day_id}'
GROUP BY department
INSERT OVERWRITE TABLE db_name.target_detail
PARTITION (day_id = '${day_id}')
SELECT
id,
name,
amount,
department
WHERE day_id = '${day_id}'
AND amount > 1000;
-- ============================================================================
-- 场景5导出到文件
-- ============================================================================
INSERT OVERWRITE DIRECTORY '/output/data/export/'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
STORED AS TEXTFILE
SELECT
id,
name,
amount,
day_id
FROM db_name.target_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景6CTASCreate Table As Select
-- ============================================================================
-- 从查询结果创建新表
CREATE TABLE db_name.new_table AS
SELECT
department,
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary
FROM db_name.employees
WHERE day_id = '${day_id}'
GROUP BY department;
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. INSERT OVERWRITE vs INSERT INTO
- INSERT OVERWRITE覆盖分区/表数据(推荐,幂等)
- INSERT INTO追加数据可能产生重复
2. 分区表写入必须指定分区
- 静态分区PARTITION (day_id = '${day_id}')
- 动态分区:需先 SET 配置PARTITION (day_id)
- 混合分区PARTITION (day_id = '2026-05-01', region)
3. 动态分区配置
SET hive.exec.dynamic.partition = true;
SET hive.exec.dynamic.partition.mode = nonstrict; -- 允许全动态
SET hive.exec.max.dynamic.partitions = 1000; -- 最大动态分区数
4. 字段顺序
- SELECT 字段顺序必须与目标表列定义一致
- 分区字段在 SELECT 最后(动态分区时)
5. 性能优化
- 多分区插入Multi-Insert一次扫描多次写入
- INSERT OVERWRITE 比 INSERT INTO 更安全(幂等性)
- 大数据量写入时注意 reducer 数量设置
*/

View File

@@ -0,0 +1,235 @@
-- =====================================================================
-- @Name: HIVE-D-SQL-{表名}-QUERY
-- @Version: 1.0
-- @Desc: Hive 查询模板
-- @TargetDatabase: Hive
-- =====================================================================
-- ============================================================================
-- 1. 单表查询
-- ============================================================================
SELECT
id,
name,
amount,
created_at
FROM db_name.source_table
WHERE day_id = '${day_id}' -- 分区过滤(必须)
AND status = 'active'
ORDER BY created_at DESC
LIMIT 1000;
-- ============================================================================
-- 2. JOIN 查询
-- ============================================================================
-- 两表 JOIN
SELECT
a.id,
a.name,
a.amount,
b.category_name
FROM db_name.main_table a
JOIN db_name.dim_table b ON a.category_id = b.id
WHERE a.day_id = '${day_id}';
-- 多表 JOIN带别名
SELECT
o.order_id,
u.user_name,
p.product_name,
oi.quantity,
oi.unit_price
FROM db_name.orders o
JOIN db_name.users u ON o.user_id = u.id
JOIN db_name.order_items oi ON o.order_id = oi.order_id
JOIN db_name.products p ON oi.product_id = p.id
WHERE o.day_id = '${day_id}'
AND o.status IN ('completed', 'shipped');
-- ============================================================================
-- 3. 聚合查询
-- ============================================================================
-- GROUP BY + HAVING
SELECT
department,
COUNT(*) AS employee_count,
SUM(salary) AS total_salary,
AVG(salary) AS avg_salary,
MAX(salary) AS max_salary
FROM db_name.employees
WHERE day_id = '${day_id}'
GROUP BY department
HAVING COUNT(*) >= 5
ORDER BY total_salary DESC;
-- 多字段分组 + 去重计数
SELECT
date,
region,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(amount) AS total_amount
FROM db_name.orders
WHERE day_id = '${day_id}'
GROUP BY date, region;
-- ============================================================================
-- 4. 窗口函数
-- ============================================================================
-- ROW_NUMBER分组取Top N
SELECT *
FROM (
SELECT
department,
name,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM db_name.employees
WHERE day_id = '${day_id}'
) t
WHERE rn <= 3;
-- 累计聚合
SELECT
date,
amount,
SUM(amount) OVER (ORDER BY date) AS cumulative_amount,
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg_7d
FROM daily_sales
WHERE day_id = '${day_id}';
-- LAG/LEAD环比
SELECT
date,
amount,
LAG(amount, 1) OVER (ORDER BY date) AS prev_amount,
amount - LAG(amount, 1) OVER (ORDER BY date) AS daily_change
FROM daily_sales
WHERE day_id = '${day_id}';
-- ============================================================================
-- 5. 子查询
-- ============================================================================
-- IN 子查询
SELECT *
FROM db_name.orders
WHERE user_id IN (
SELECT id FROM db_name.users WHERE vip_level >= 3
)
AND day_id = '${day_id}';
-- EXISTS 子查询
SELECT *
FROM db_name.products p
WHERE EXISTS (
SELECT 1 FROM db_name.inventory i
WHERE i.product_id = p.id
AND i.quantity > 0
)
AND p.day_id = '${day_id}';
-- ============================================================================
-- 6. 条件聚合CASE WHEN + 聚合)
-- ============================================================================
SELECT
date,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
SUM(CASE WHEN amount > 1000 THEN amount ELSE 0 END) AS high_value_amount
FROM db_name.orders
WHERE day_id = '${day_id}'
GROUP BY date;
-- ============================================================================
-- 7. LATERAL VIEW + explodeHive 特有)
-- ============================================================================
-- 展开数组字段
SELECT
id,
name,
tag
FROM db_name.articles
LATERAL VIEW explode(tags) t AS tag
WHERE day_id = '${day_id}';
-- 展开数组并统计
SELECT
tag,
COUNT(*) AS article_count
FROM db_name.articles
LATERAL VIEW explode(tags) t AS tag
WHERE day_id = '${day_id}'
GROUP BY tag;
-- 展开 Map
SELECT
id,
map_key,
map_value
FROM db_name.data_table
LATERAL VIEW explode(props) m AS map_key, map_value
WHERE day_id = '${day_id}';
-- posexplode带索引展开
SELECT
id,
pos,
tag
FROM db_name.articles
LATERAL VIEW posexplode(tags) t AS pos, tag
WHERE day_id = '${day_id}';
-- ============================================================================
-- 8. 复杂类型查询
-- ============================================================================
-- ARRAY 操作
SELECT
id,
size(tags) AS tag_count, -- 数组长度
array_contains(tags, '大数据') AS has_tag, -- 包含判断
tags[0] AS first_tag -- 取第一个元素
FROM db_name.articles
WHERE day_id = '${day_id}';
-- MAP 操作
SELECT
id,
props['city'] AS city, -- 取值
map_keys(props) AS all_keys, -- 所有 key
map_values(props) AS all_values -- 所有 value
FROM db_name.user_table
WHERE day_id = '${day_id}';
-- STRUCT 操作
SELECT
id,
user_info.name AS user_name, -- 结构体字段访问
user_info.age AS user_age
FROM db_name.data_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 9. 集合聚合
-- ============================================================================
-- collect_list / collect_set
SELECT
department,
collect_list(name) AS all_names, -- 收集为数组(不去重)
collect_set(name) AS unique_names, -- 收集为数组(去重)
size(collect_set(name)) AS unique_count
FROM db_name.employees
WHERE day_id = '${day_id}'
GROUP BY department;

View File

@@ -0,0 +1,211 @@
-- =====================================================================
-- @Name: KUDU-D-SQL-{表名}-CREATE
-- @Version: 1.0
-- @Desc: Kudu (via Impala) 建表模板
-- @TargetDatabase: Apache Kudu (via Impala)
-- @说明: Kudu 通过 Impala 访问,使用 Impala DDL 操作 Kudu 表
-- =====================================================================
-- ============================================================================
-- 场景1基础表创建Hash 分区)
-- ============================================================================
-- 适用:按主键 Hash 分布数据,写入和点查性能好
CREATE TABLE IF NOT EXISTS db_name.kudu_basic (
-- 主键列Kudu 表必须有主键)
id BIGINT NOT NULL COMMENT '主键ID',
-- 业务字段
name STRING COMMENT '名称',
category STRING COMMENT '类别',
amount DECIMAL(18,2) COMMENT '金额',
status STRING COMMENT '状态',
created_at TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP COMMENT '更新时间'
)
PRIMARY KEY (id)
PARTITION BY HASH(id) PARTITIONS 8
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3'
);
-- ============================================================================
-- 场景2Hash + Range 组合分区
-- ============================================================================
-- 适用:按时间范围 + Hash 组合,兼顾范围查询和写入性能
CREATE TABLE IF NOT EXISTS db_name.kudu_range_hash (
-- 主键列(必须包含分区列)
id BIGINT NOT NULL COMMENT '主键ID',
stat_date STRING NOT NULL COMMENT '统计日期 yyyy-MM-dd',
-- 业务字段
department STRING COMMENT '部门',
metric_name STRING COMMENT '指标名称',
metric_value DECIMAL(18,2) COMMENT '指标值',
etl_time TIMESTAMP COMMENT '加工时间'
)
PRIMARY KEY (id, stat_date)
PARTITION BY
HASH(id) PARTITIONS 4,
RANGE(stat_date) (
PARTITION '2026-01-01' <= VALUES < '2026-02-01',
PARTITION '2026-02-01' <= VALUES < '2026-03-01',
PARTITION '2026-03-01' <= VALUES < '2026-04-01',
PARTITION '2026-04-01' <= VALUES < '2026-05-01',
PARTITION '2026-05-01' <= VALUES < '2026-06-01'
)
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3',
'kudu.compression' = 'LZ4'
);
-- ============================================================================
-- 场景3多列主键
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.kudu_composite_pk (
user_id BIGINT NOT NULL COMMENT '用户ID',
order_date STRING NOT NULL COMMENT '订单日期',
order_seq INT NOT NULL COMMENT '当日订单序号',
user_name STRING COMMENT '用户姓名',
product_name STRING COMMENT '商品名称',
quantity INT COMMENT '数量',
total_amount DECIMAL(18,2) COMMENT '总金额',
status STRING COMMENT '状态',
create_time TIMESTAMP COMMENT '创建时间'
)
PRIMARY KEY (user_id, order_date, order_seq)
PARTITION BY
HASH(user_id) PARTITIONS 8,
RANGE(order_date) (
PARTITION '2026-01-01' <= VALUES < '2026-02-01',
PARTITION '2026-02-01' <= VALUES < '2026-03-01',
PARTITION '2026-03-01' <= VALUES < '2026-04-01'
)
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3'
);
-- ============================================================================
-- 场景4纯 Range 分区
-- ============================================================================
-- 适用:按时间顺序写入,范围查询多
CREATE TABLE IF NOT EXISTS db_name.kudu_range_only (
id BIGINT NOT NULL COMMENT '主键ID',
stat_date STRING NOT NULL COMMENT '统计日期',
metric_value DECIMAL(18,2) COMMENT '指标值',
dimension STRING COMMENT '维度',
etl_time TIMESTAMP COMMENT '加工时间'
)
PRIMARY KEY (id, stat_date)
PARTITION BY RANGE(stat_date) (
PARTITION '2026-01-01' <= VALUES < '2026-04-01',
PARTITION '2026-04-01' <= VALUES < '2026-07-01',
PARTITION '2026-07-01' <= VALUES < '2026-10-01',
PARTITION '2026-10-01' <= VALUES < '2027-01-01'
)
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3'
);
-- ============================================================================
-- 场景5外部表映射已有 Kudu 表
-- ============================================================================
CREATE EXTERNAL TABLE IF NOT EXISTS db_name.kudu_external
STORED AS KUDU
TBLPROPERTIES (
'kudu.master_addresses' = 'kudu-master-1:7051,kudu-master-2:7051,kudu-master-3:7051',
'kudu.table_name' = 'impala.db_name.existing_table'
);
-- ============================================================================
-- 场景6带压缩和副本配置
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.kudu_with_props (
id BIGINT NOT NULL COMMENT '主键ID',
data_date STRING NOT NULL COMMENT '数据日期',
content STRING COMMENT '内容',
value DOUBLE COMMENT '数值'
)
PRIMARY KEY (id, data_date)
PARTITION BY
HASH(id) PARTITIONS 8,
RANGE(data_date) (
PARTITION '2026-01-01' <= VALUES < '2026-02-01',
PARTITION '2026-02-01' <= VALUES < '2026-03-01'
)
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3',
'kudu.compression' = 'LZ4', -- 压缩算法
'kudu.encryption' = 'false' -- 加密
);
-- ============================================================================
-- 字段类型速查Kudu 支持的类型)
-- ============================================================================
/*
| 类型 | 说明 | 适用场景 |
|---------------|----------------|------------------------|
| BOOLEAN | 布尔 | 状态标志 |
| TINYINT | 1字节整数 | 小范围枚举 |
| SMALLINT | 2字节整数 | 小范围数值 |
| INT | 4字节整数 | 数量、等级 |
| BIGINT | 8字节整数 | ID、计数 |
| FLOAT | 4字节浮点 | 近似计算 |
| DOUBLE | 8字节浮点 | 科学计算 |
| DECIMAL(p,s) | 定点数 | 金额、精确数值 |
| STRING | 变长字符串 | 名称、描述 |
| VARCHAR(n) | 变长字符串 | 限定长度字符串 |
| CHAR(n) | 定长字符串 | 固定长度编码 |
| TIMESTAMP | 时间戳 | 时间字段(微秒精度) |
| DATE | 日期 | 日期字段 |
| BINARY | 二进制 | 二进制数据 |
注意Kudu 不支持 ARRAY, MAP, STRUCT 等复杂类型
*/
-- ============================================================================
-- 建表规范说明
-- ============================================================================
/*
1. 主键约束Kudu 特有)
- 每张 Kudu 表必须有 PRIMARY KEY
- 主键列不能为 NULL必须 NOT NULL
- 主键值不可 UPDATE只能删除后重新插入
- 主键列必须包含在分区列中
2. 分区策略
- Hash 分区:均匀分布,适合写入和点查
- Range 分区:按范围查询,适合时间序列
- Hash + Range 组合:兼顾两者优势(推荐)
- 分区数 = tablet 数量,影响并行度
3. 分区设计建议
- Hash 分区数:建议 4 的倍数,参考数据量
- Range 分区:按时间维度,定期添加新分区
- 单个 tablet 建议 1GB~10GB
4. 副本数
- 生产环境建议 3 副本(默认)
- Raft 协议保证一致性
5. 压缩
- 推荐 LZ4速度和压缩比平衡
- 可选SNAPPY, ZLIB, LZ4
6. 与 Hive/Spark 表的区别
- Kudu 表支持 UPDATE 和 DELETE
- Kudu 表不支持 INSERT OVERWRITE
- Kudu 表不支持复杂类型ARRAY, MAP, STRUCT
- Kudu 表主键有约束Hive/Spark 无约束
*/

View File

@@ -0,0 +1,146 @@
-- =====================================================================
-- @Name: KUDU-D-SQL-{表名}-ETL
-- @Version: 2.0
-- @Desc: Kudu (via Impala) ETL 数据处理模板(临时表链式处理)
-- @TargetDatabase: Apache Kudu (via Impala)
-- @说明: 统一规范:禁止 CTE每步物化为临时表先 DROP 再 CREATE
-- 最后一步用 UPSERT INTO 写入 Kudu 目标表
-- =====================================================================
-- ============================================================================
-- Step01: 基础清洗与过滤
-- ============================================================================
-- 说明:从源表读取数据,进行基础过滤和清洗
-- 输入:{源表名}
-- 输出:${db_tmp_env}.tmp_xxx_01
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT
order_id,
user_id,
dept_id,
product_id,
quantity,
amount,
status,
stat_date
FROM db_name.source_table
WHERE stat_date = '${day_id}'
AND status IN ('completed', 'shipped') -- 业务过滤
AND amount > 0 -- 数据质量过滤
AND user_id IS NOT NULL; -- NULL过滤
-- ============================================================================
-- Step02: 多表关联与维度补全
-- ============================================================================
-- 说明:关联维度表,补全业务属性字段
-- 输入:${db_tmp_env}.tmp_xxx_01, dim_department, dim_product
-- 输出:${db_tmp_env}.tmp_xxx_02
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT
a.order_id,
a.user_id,
a.amount,
a.quantity,
b.dept_name, -- 维度补全:部门名称
c.category_name, -- 维度补全:类别名称
a.stat_date
FROM ${db_tmp_env}.tmp_xxx_01 a
LEFT JOIN db_name.dim_department b
ON a.dept_id = b.dept_id
LEFT JOIN db_name.dim_product c
ON a.product_id = c.product_id;
-- ============================================================================
-- Step03: 聚合计算与指标生成
-- ============================================================================
-- 说明:按业务维度聚合,计算统计指标
-- 输入:${db_tmp_env}.tmp_xxx_02
-- 输出:${db_tmp_env}.tmp_xxx_03
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_03;
CREATE TABLE ${db_tmp_env}.tmp_xxx_03 AS
SELECT
stat_date,
dept_name,
category_name,
COUNT(*) AS record_count, -- 记录数
COUNT(DISTINCT user_id) AS unique_users, -- 去重用户数
SUM(amount) AS total_amount, -- 总金额
SUM(quantity) AS total_quantity, -- 总数量
AVG(amount) AS avg_amount, -- 平均金额
MAX(amount) AS max_amount -- 最大金额
FROM ${db_tmp_env}.tmp_xxx_02
GROUP BY stat_date, dept_name, category_name;
-- ============================================================================
-- Step04: 最终输出写入 Kudu 目标表
-- ============================================================================
-- 说明:使用 UPSERT 写入 Kudu 目标表
-- 输入:${db_tmp_env}.tmp_xxx_03
-- 输出Kudu 目标表
-- 方式1UPSERT推荐主键存在则更新不存在则插入
UPSERT INTO ${db_eda_env}.target_table
SELECT
-- 主键字段Kudu 表必须有主键)
dept_name,
category_name,
stat_date,
-- 指标字段
record_count,
unique_users,
total_amount,
total_quantity,
avg_amount,
max_amount,
-- 技术字段
NOW() AS etl_time -- 数据加工时间
FROM ${db_tmp_env}.tmp_xxx_03;
-- 方式2需要全量刷新时先删后插
-- DELETE FROM ${db_eda_env}.target_table WHERE stat_date = '${day_id}';
-- INSERT INTO ${db_eda_env}.target_table
-- SELECT ... FROM ${db_tmp_env}.tmp_xxx_03;
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. 禁止使用 CTE (WITH 子句)
- 每个步骤必须物化为临时表
- 原因:便于调试、断点续跑、统一编码规范
2. 先 DROP 再 CREATE
- 每个临时表创建前必须先 DROP TABLE IF EXISTS
- 原因:防止表已存在导致失败
3. Kudu 写入方式
- 推荐 UPSERT INTOKudu 核心优势)
- 主键存在 → 更新(整行替换)
- 主键不存在 → 插入新行
- 需要全量刷新 → 先 DELETE 再 INSERT
4. Kudu 表约束
- 不支持 INSERT OVERWRITE用 UPSERT 或 DELETE + INSERT 替代)
- 必须有 PRIMARY KEY
- 主键列不能为 NULL
- 支持 UPDATE 和 DELETE
5. 过滤条件前置
- 所有过滤在最早阶段应用
- 减少中间数据量
6. 临时表命名规范
- 格式tmp_{业务简称}_{步骤序号}
7. Kudu 特有注意事项
- CONCAT 只接受 2 个参数,多参数用 CONCAT_WS
- 不支持 collect_list/collect_set用 GROUP_CONCAT 替代
- 近似去重用 NDV() 函数
*/

View File

@@ -0,0 +1,160 @@
-- =====================================================================
-- @Name: KUDU-D-SQL-{表名}-INSERT
-- @Version: 1.0
-- @Desc: Kudu (via Impala) 数据插入模板
-- @TargetDatabase: Apache Kudu (via Impala)
-- @说明: Kudu 表不支持 INSERT OVERWRITE支持 INSERT INTO 和 UPSERT
-- =====================================================================
-- ============================================================================
-- 场景1INSERT INTO追加写入
-- ============================================================================
-- 适用:向 Kudu 表追加新数据
INSERT INTO db_name.kudu_table
SELECT
id,
stat_date,
name,
department,
amount,
current_timestamp() AS etl_time
FROM db_name.source_table
WHERE stat_date = '${day_id}';
-- ============================================================================
-- 场景2UPSERT INTO更新插入Kudu 特有)
-- ============================================================================
-- 适用:如果主键存在则更新,不存在则插入
-- 这是 Kudu 的核心优势,其他 Hive/Spark 表不支持
-- 基础 UPSERT
UPSERT INTO db_name.kudu_table
SELECT
id,
stat_date,
name,
department,
amount,
current_timestamp() AS etl_time
FROM db_name.staging_table
WHERE stat_date = '${day_id}';
-- 聚合后 UPSERT增量更新指标表
UPSERT INTO db_name.kudu_metrics
SELECT
department,
'${day_id}' AS stat_date,
COUNT(*) AS order_count,
SUM(amount) AS total_amount,
current_timestamp() AS etl_time
FROM db_name.incremental_orders
WHERE stat_date = '${day_id}'
GROUP BY department;
-- ============================================================================
-- 场景3UPDATEKudu 表特有)
-- ============================================================================
-- 适用:修改已有数据
-- 注意:主键列不能被 UPDATE
-- 单条更新
UPDATE db_name.kudu_table
SET status = 'processed',
updated_at = current_timestamp()
WHERE id = 12345;
-- 批量条件更新
UPDATE db_name.kudu_table
SET status = 'expired',
updated_at = current_timestamp()
WHERE stat_date < '2026-01-01'
AND status = 'active';
-- 关联更新(用子查询)
UPDATE db_name.kudu_table t
SET t.department = d.new_dept_name
FROM db_name.dept_mapping d
WHERE t.department = d.old_dept_name;
-- ============================================================================
-- 场景4DELETEKudu 表特有)
-- ============================================================================
-- 适用:删除数据
-- 注意Kudu 的 DELETE 比 Hive/Spark 方便得多
-- 条件删除
DELETE FROM db_name.kudu_table
WHERE stat_date < '2026-01-01';
-- 按主键删除
DELETE FROM db_name.kudu_table
WHERE id IN (1001, 1002, 1003);
-- 关联删除(用子查询)
DELETE FROM db_name.kudu_table
WHERE user_id IN (
SELECT user_id FROM db_name.blacklist
);
-- ============================================================================
-- 场景5从查询结果写入
-- ============================================================================
-- 简单 ETL清洗后写入
INSERT INTO db_name.kudu_target
SELECT
id,
'${day_id}' AS stat_date,
name,
COALESCE(department, '未知') AS department,
amount,
current_timestamp() AS etl_time
FROM db_name.raw_data
WHERE stat_date = '${day_id}'
AND id IS NOT NULL
AND amount > 0;
-- ============================================================================
-- 场景6批量 VALUES 写入
-- ============================================================================
INSERT INTO db_name.kudu_table (id, stat_date, name, amount)
VALUES
(1, '2026-05-01', '测试1', 100.00),
(2, '2026-05-01', '测试2', 200.00),
(3, '2026-05-01', '测试3', 300.00);
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. Kudu 表与 Hive/Spark 表的核心区别
- 支持 INSERT INTO
- 支持 INSERT OVERWRITE不支持
- 支持 UPSERTKudu 独有,核心能力)
- 支持 UPDATEKudu 独有)
- 支持 DELETEKudu 独有)
2. UPSERT 是 Kudu 的核心优势
- 主键存在 → 更新(整行替换)
- 主键不存在 → 插入新行
- 适用于:增量更新、数据修正、指标回填
3. INSERT INTO 注意事项
- 如果主键冲突会报错(不会自动去重)
- 需要确保写入数据的主键不重复,或使用 UPSERT
4. UPDATE 限制
- 主键列不能被 UPDATE
- WHERE 条件建议包含主键或分区列(性能)
5. DELETE 建议
- 删除大量数据时按分区范围删除
- 定期清理历史数据
6. 性能建议
- 批量写入优于逐条写入
- UPSERT 比 DELETE + INSERT 更高效
- 利用主键做点查,避免全表扫描
*/

View File

@@ -0,0 +1,179 @@
-- =====================================================================
-- @Name: KUDU-D-SQL-{表名}-QUERY
-- @Version: 1.0
-- @Desc: Kudu (via Impala) 查询模板
-- @TargetDatabase: Apache Kudu (via Impala)
-- =====================================================================
-- ============================================================================
-- 1. 单表查询
-- ============================================================================
SELECT
id,
name,
amount,
created_at
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
AND status = 'active'
ORDER BY created_at DESC
LIMIT 1000;
-- ============================================================================
-- 2. JOIN 查询
-- ============================================================================
-- 两表 JOINKudu 表 JOIN 非 Kudu 表也支持)
SELECT
k.id,
k.name,
k.amount,
d.dept_name
FROM db_name.kudu_table k
JOIN db_name.dim_department d ON k.dept_id = d.dept_id
WHERE k.stat_date = '${day_id}';
-- 多表 JOIN
SELECT
k.id,
k.user_name,
p.product_name,
k.quantity,
k.total_amount
FROM db_name.kudu_orders k
JOIN db_name.dim_users u ON k.user_id = u.user_id
JOIN db_name.dim_products p ON k.product_id = p.product_id
WHERE k.stat_date BETWEEN '${start_date}' AND '${end_date}';
-- ============================================================================
-- 3. 聚合查询
-- ============================================================================
SELECT
department,
COUNT(*) AS record_count,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount,
MAX(amount) AS max_amount,
MIN(amount) AS min_amount
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
GROUP BY department
HAVING COUNT(*) >= 5
ORDER BY total_amount DESC;
-- 多字段分组 + 去重计数
SELECT
stat_date,
region,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(amount) AS total_amount
FROM db_name.kudu_table
WHERE stat_date BETWEEN '${start_date}' AND '${end_date}'
GROUP BY stat_date, region;
-- ============================================================================
-- 4. 窗口函数
-- ============================================================================
-- ROW_NUMBER分组取Top N
SELECT *
FROM (
SELECT
department,
user_name,
amount,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY amount DESC) AS rn
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
) t
WHERE rn <= 3;
-- 累计聚合
SELECT
stat_date,
daily_amount,
SUM(daily_amount) OVER (ORDER BY stat_date) AS cumulative_amount
FROM (
SELECT stat_date, SUM(amount) AS daily_amount
FROM db_name.kudu_table
GROUP BY stat_date
) t;
-- LAG/LEAD环比
SELECT
stat_date,
daily_amount,
LAG(daily_amount, 1) OVER (ORDER BY stat_date) AS prev_amount,
daily_amount - LAG(daily_amount, 1) OVER (ORDER BY stat_date) AS daily_change
FROM (
SELECT stat_date, SUM(amount) AS daily_amount
FROM db_name.kudu_table
GROUP BY stat_date
) t;
-- ============================================================================
-- 5. 子查询
-- ============================================================================
-- IN 子查询
SELECT *
FROM db_name.kudu_table
WHERE user_id IN (
SELECT user_id FROM db_name.vip_users WHERE vip_level >= 3
)
AND stat_date = '${day_id}';
-- EXISTS 子查询
SELECT *
FROM db_name.kudu_products p
WHERE EXISTS (
SELECT 1 FROM db_name.kudu_inventory i
WHERE i.product_id = p.product_id
AND i.quantity > 0
);
-- ============================================================================
-- 6. 条件聚合CASE WHEN + 聚合)
-- ============================================================================
SELECT
stat_date,
COUNT(*) AS total_count,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
SUM(CASE WHEN amount > 1000 THEN amount ELSE 0 END) AS high_value_amount
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
GROUP BY stat_date;
-- ============================================================================
-- 7. LIMIT / OFFSET分页
-- ============================================================================
SELECT id, name, amount
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
ORDER BY id
LIMIT 20 OFFSET 0;
-- ============================================================================
-- 8. Kudu 特有:通过主键高效点查
-- ============================================================================
-- Kudu 主键查询可跳过扫描,直接定位 tablet
-- 单主键点查
SELECT * FROM db_name.kudu_table
WHERE id = 12345;
-- 复合主键点查
SELECT * FROM db_name.kudu_composite_pk
WHERE user_id = 1001
AND order_date = '2026-05-01'
AND order_seq = 1;
-- 主键 IN 查询
SELECT * FROM db_name.kudu_table
WHERE id IN (1001, 1002, 1003, 1004, 1005);

View File

@@ -0,0 +1,176 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-CREATE
-- @Version: 1.0
-- @Desc: 建表模板CREATE TABLE
-- @TargetTables: {新表名}
-- @TargetDatabase: Paimon
-- =====================================================================
-- ============================================================================
-- 场景1基础表创建非分区
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.basic_table (
-- 主键/标识字段
id BIGINT COMMENT '主键ID',
-- 业务字段
name STRING COMMENT '名称',
category STRING COMMENT '类别',
amount DECIMAL(18,2) COMMENT '金额',
status STRING COMMENT '状态',
-- 时间字段
created_at TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP COMMENT '更新时间',
-- 技术字段
etl_time TIMESTAMP COMMENT '数据加工时间',
etl_remark STRING COMMENT '备注信息'
)
COMMENT '基础业务表'
STORED AS PARQUET; -- 存储格式
-- ============================================================================
-- 场景2分区表创建单分区字段
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.partitioned_table (
-- 主键/标识字段
id BIGINT COMMENT '主键ID',
-- 业务字段
user_id STRING COMMENT '用户ID',
user_name STRING COMMENT '用户姓名',
order_count BIGINT COMMENT '订单数',
total_amount DECIMAL(18,2) COMMENT '总金额',
-- 维度字段
department STRING COMMENT '部门',
region STRING COMMENT '地区',
-- 技术字段
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '按日分区的统计表'
PARTITIONED BY (day_id STRING COMMENT '统计日期格式yyyy-MM-dd')
STORED AS PARQUET;
-- ============================================================================
-- 场景3多分区字段表
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.multi_partition_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '多分区字段示例表'
PARTITIONED BY (
year_id STRING COMMENT '年份',
month_id STRING COMMENT '月份'
)
STORED AS PARQUET;
-- ============================================================================
-- 场景4带表属性配置
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.configured_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '带属性配置的表'
PARTITIONED BY (day_id STRING)
STORED AS PARQUET
TBLPROPERTIES (
'parquet.compression' = 'SNAPPY', -- 压缩格式
'spark.sql.partitionOverwriteMode' = 'dynamic' -- 动态分区覆盖模式
);
-- ============================================================================
-- 场景5Paimon 表创建(主键表)
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.paimon_pk_table (
-- 主键字段Paimon 主键表必须包含所有主键字段)
id BIGINT COMMENT '主键ID',
day_id STRING COMMENT '分区日期',
-- 业务字段
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
status STRING COMMENT '状态',
-- 技术字段
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT 'Paimon 主键表(支持 MERGE INTO'
PARTITIONED BY (day_id)
TBLPROPERTIES (
'primary-key' = 'id,day_id', -- 主键定义
'bucket' = '4', -- 分桶数
'changelog-producer' = 'input' -- 变更日志生产
);
-- ============================================================================
-- 场景6临时表创建
-- ============================================================================
CREATE TEMPORARY TABLE tmp_processing_table (
id BIGINT,
name STRING,
amount DECIMAL(18,2)
);
-- 或使用 AS 创建临时表
CREATE TEMPORARY TABLE tmp_source AS
SELECT id, name, amount
FROM source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 字段类型速查
-- ============================================================================
/*
| 类型 | 说明 | 适用场景 |
|---------------|----------------|------------------------|
| STRING | 字符串 | 名称、编码、描述 |
| INT | 整数 | 数量、等级、标志 |
| BIGINT | 大整数 | ID、计数、金额整数 |
| DECIMAL(p,s) | 定点数 | 金额、比例、精度数值 |
| DOUBLE | 浮点数 | 科学计算(慎用于金额) |
| BOOLEAN | 布尔 | 状态标志 |
| DATE | 日期 | 日期字段 |
| TIMESTAMP | 时间戳 | 时间字段 |
| ARRAY<type> | 数组 | 多值字段 |
| MAP<k,v> | 映射 | 属性字典 |
*/
-- ============================================================================
-- 建表规范说明
-- ============================================================================
/*
1. 字段命名规范
- 使用 snake_caseuser_id, total_amount
- 主键id 或 {业务}_id
- 技术字段etl_time, etl_remark
2. COMMENT 必须添加
- 每个字段必须有 COMMENT
- 表必须有 COMMENT
3. 分区字段选择
- 按时间分区day_id, month_id
- 分区粒度:日分区最常用
4. 存储格式
- 推荐PARQUET列存储压缩好
- 可选ORC、AVRO
5. Paimon 表特性
- primary-key主键字段列表
- bucket分桶数影响并发
- 支持 MERGE INTO 操作
6. 表属性配置
- 压缩格式SNAPPY推荐、GZIP、LZ4
- 动态分区模式dynamic推荐
*/

View File

@@ -0,0 +1,148 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-ETL
-- @Version: 1.0
-- @Desc: ETL 数据处理模板(临时表链式处理)
-- @TargetTables: ${db_eda_env}.{目标表名}
-- @SourceTables: {源表列表}
-- @TargetDatabase: Paimon
-- @SourceDatabase: Paimon
-- @任务调度频度: {日/周/月}
-- @修改记录:
-- 版本号 更新时间 更新人员 更新内容
-- V1.0 {日期} {人员} 创建脚本
-- @数据处理步骤:
-- Step01: {步骤描述}
-- Step02: {步骤描述}
-- Step03: {步骤描述}
-- 参数说明
-- 账期参数:
-- ${day_id} 日账期格式20250101
-- 环境变量:
-- 变量名 测试环境值 生产环境值
-- ${db_tmp_env} {库名} {库名}
-- ${db_eda_env} {库名} {库名}
-- =====================================================================
-- ============================================================================
-- Step01: 基础清洗与过滤
-- ============================================================================
-- 说明:从源表读取数据,进行基础过滤和清洗
-- 输入:{源表名}
-- 输出:${db_tmp_env}.tmp_{表名}_01
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT
id,
name,
amount,
status,
created_at,
day_id
FROM source_table
WHERE day_id = '${day_id}' -- 分区过滤(必须)
AND status IN ('active', 'valid') -- 业务过滤
AND amount > 0 -- 数据质量过滤
AND id IS NOT NULL -- NULL过滤;
-- ============================================================================
-- Step02: 多表关联与维度补全
-- ============================================================================
-- 说明:关联维度表,补全业务属性字段
-- 输入:${db_tmp_env}.tmp_xxx_01, {维度表1}, {维度表2}
-- 输出:${db_tmp_env}.tmp_xxx_02
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT
a.id,
a.name,
a.amount,
a.status,
b.category_name, -- 维度补全:类别名称
c.department_name, -- 维度补全:部门名称
a.created_at,
a.day_id
FROM ${db_tmp_env}.tmp_xxx_01 a
LEFT JOIN dim_category b
ON a.category_id = b.id
AND b.day_id = '${day_id}' -- 维度表分区过滤
LEFT JOIN dim_department c
ON a.department_id = c.id
AND c.day_id = '${day_id}'; -- 维度表分区过滤
-- ============================================================================
-- Step03: 聚合计算与指标生成
-- ============================================================================
-- 说明:按业务维度聚合,计算统计指标
-- 输入:${db_tmp_env}.tmp_xxx_02
-- 输出:${db_tmp_env}.tmp_xxx_03
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_03;
CREATE TABLE ${db_tmp_env}.tmp_xxx_03 AS
SELECT
day_id,
category_name,
department_name,
COUNT(*) AS record_count, -- 记录数
COUNT(DISTINCT id) AS unique_count, -- 唯一计数
SUM(amount) AS total_amount, -- 总金额
AVG(amount) AS avg_amount, -- 平均金额
MAX(amount) AS max_amount, -- 最大金额
MIN(amount) AS min_amount -- 最小金额
FROM ${db_tmp_env}.tmp_xxx_02
GROUP BY day_id, category_name, department_name;
-- ============================================================================
-- Step04: 最终输出写入目标表
-- ============================================================================
-- 说明:补全目标表标准字段,写入结果表
-- 输入:${db_tmp_env}.tmp_xxx_03
-- 输出:${db_eda_env}.{目标表名}
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT
-- 业务字段
category_name,
department_name,
record_count,
unique_count,
total_amount,
avg_amount,
max_amount,
min_amount,
-- 技术字段
current_timestamp() AS etl_time, -- 数据加工时间
'${day_id}' AS stat_date -- 统计日期;
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. 禁止使用 CTE (WITH 子句)
- 每个步骤必须物化为临时表
- 原因:避免内存溢出,便于调试和断点续跑
2. 先 DROP 再 CREATE
- 每个临时表创建前必须先 DROP
- 原因:防止表已存在导致失败
3. 分区过滤必须前置
- 所有源表和维度表查询必须带 day_id 过滤
- 原因:避免全表扫描,提升性能
4. JOIN 条件下推
- 维度表关联时带上分区过滤条件
- 原因:减少关联数据量
5. 临时表命名规范
- 格式tmp_{业务简称}_{步骤序号}
- 示例tmp_order_stats_01, tmp_order_stats_02
6. 目标表写入规范
- 使用 INSERT OVERWRITE覆盖写入
- 明确指定分区
- 补全技术字段etl_time 等)
*/

View File

@@ -0,0 +1,131 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-INSERT
-- @Version: 1.0
-- @Desc: 数据插入模板INSERT OVERWRITE
-- @TargetTables: ${db_eda_env}.{目标表名}
-- @SourceTables: {源表列表}
-- @TargetDatabase: Paimon
-- @SourceDatabase: Paimon
-- =====================================================================
-- ============================================================================
-- 场景1分区表覆盖写入
-- ============================================================================
-- 适用:每日/每周/每月增量写入分区表
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT
field1,
field2,
field3,
current_timestamp() AS etl_time
FROM source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景2动态分区写入
-- ============================================================================
-- 适用:多分区字段,数据中包含分区值
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id, region) -- 动态分区字段
SELECT
field1,
field2,
field3,
day_id, -- 分区字段1数据中包含
region, -- 分区字段2数据中包含
current_timestamp() AS etl_time
FROM source_table
WHERE day_id BETWEEN '${start_day}' AND '${end_day}';
-- ============================================================================
-- 场景3全表覆盖写入
-- ============================================================================
-- 适用:全量刷新、初始化数据
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
SELECT
field1,
field2,
field3,
current_timestamp() AS etl_time
FROM source_table;
-- ============================================================================
-- 场景4追加写入慎用
-- ============================================================================
-- 适用:日志表、流水表(无分区或允许重复)
INSERT INTO TABLE ${db_eda_env}.target_table
SELECT
field1,
field2,
field3,
current_timestamp() AS etl_time
FROM source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景5从临时表写入目标表
-- ============================================================================
-- 适用ETL 流程最后一步
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT
-- 业务字段(与目标表字段顺序一致)
user_id,
user_name,
order_count,
total_amount,
-- 技术字段
current_timestamp() AS etl_time,
'${day_id}' AS stat_date
FROM ${db_tmp_env}.tmp_xxx_final;
-- ============================================================================
-- 场景6MERGE INTO更新插入
-- ============================================================================
-- 适用:增量更新、修正历史数据
MERGE INTO ${db_eda_env}.target_table t
USING ${db_tmp_env}.tmp_xxx_source s
ON t.id = s.id AND t.day_id = s.day_id
WHEN MATCHED THEN
UPDATE SET
t.name = s.name,
t.amount = s.amount,
t.etl_time = current_timestamp()
WHEN NOT MATCHED THEN
INSERT (id, day_id, name, amount, etl_time)
VALUES (s.id, s.day_id, s.name, s.amount, current_timestamp());
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. INSERT OVERWRITE vs INSERT INTO
- INSERT OVERWRITE覆盖写入推荐
- INSERT INTO追加写入可能导致重复数据
2. 分区表写入必须指定分区
- 避免全表覆盖导致历史数据丢失
- 格式PARTITION (day_id = '${day_id}')
3. 字段顺序必须与目标表一致
- 目标表字段顺序:业务字段 → 技术字段 → 分区字段
- SELECT 字段顺序必须匹配
4. 技术字段补全
- etl_time数据写入时间
- stat_date统计日期可选
- etl_remark备注信息可选
5. MERGE INTO 注意事项
- Spark 3.x+ 支持
- 目标表必须支持事务(如 Paimon/Delta
- 关联字段必须唯一(避免多条匹配)
*/

View File

@@ -0,0 +1,179 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-PARTITION
-- @Version: 1.0
-- @Desc: 分区表操作模板
-- @TargetTables: {分区表名}
-- @TargetDatabase: Paimon
-- =====================================================================
-- ============================================================================
-- 分区表创建
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.daily_partition_table (
id BIGINT COMMENT '主键ID',
user_id STRING COMMENT '用户ID',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '按日分区表'
PARTITIONED BY (day_id STRING COMMENT '统计日期')
STORED AS PARQUET;
-- ============================================================================
-- 分区写入操作
-- ============================================================================
-- 1. 静态分区写入(指定分区值)
INSERT OVERWRITE TABLE ${db_eda_env}.daily_partition_table
PARTITION (day_id = '2026-05-09')
SELECT
id,
user_id,
amount,
current_timestamp() AS etl_time
FROM source_table
WHERE day_id = '${day_id}';
-- 2. 动态分区写入(数据中包含分区值)
-- 需要先设置动态分区模式
SET spark.sql.partitionOverwriteMode = dynamic;
INSERT OVERWRITE TABLE ${db_eda_env}.daily_partition_table
PARTITION (day_id) -- 动态分区字段
SELECT
id,
user_id,
amount,
current_timestamp() AS etl_time,
day_id -- 数据中包含分区值
FROM source_table
WHERE day_id BETWEEN '2026-05-01' AND '2026-05-09';
-- ============================================================================
-- 分区查询操作
-- ============================================================================
-- 3. 单分区查询
SELECT *
FROM ${db_eda_env}.daily_partition_table
WHERE day_id = '2026-05-09';
-- 4. 多分区查询
SELECT *
FROM ${db_eda_env}.daily_partition_table
WHERE day_id IN ('2026-05-01', '2026-05-02', '2026-05-03');
-- 5. 分区范围查询
SELECT *
FROM ${db_eda_env}.daily_partition_table
WHERE day_id >= '2026-05-01'
AND day_id <= '2026-05-09';
-- 6. 最近 N 天分区查询(动态计算)
SELECT *
FROM ${db_eda_env}.daily_partition_table
WHERE day_id >= date_format(date_sub(current_date(), 30), 'yyyy-MM-dd');
-- ============================================================================
-- 分区管理操作
-- ============================================================================
-- 7. 查看分区列表
SHOW PARTITIONS ${db_eda_env}.daily_partition_table;
-- 8. 查看特定分区详情
DESCRIBE EXTENDED ${db_eda_env}.daily_partition_table PARTITION (day_id = '2026-05-09');
-- 9. 添加分区(手动创建空分区,部分表类型支持)
ALTER TABLE ${db_eda_env}.daily_partition_table
ADD IF NOT EXISTS PARTITION (day_id = '2026-05-10');
-- 10. 删除分区(清理历史数据)
ALTER TABLE ${db_eda_env}.daily_partition_table
DROP IF EXISTS PARTITION (day_id = '2026-01-01');
-- ============================================================================
-- 多分区字段操作
-- ============================================================================
-- 11. 多分区字段表创建
CREATE TABLE IF NOT EXISTS ${db_eda_env}.multi_partition_table (
id BIGINT,
name STRING,
amount DECIMAL(18,2),
etl_time TIMESTAMP
)
PARTITIONED BY (year_id STRING, month_id STRING)
STORED AS PARQUET;
-- 12. 多分区字段写入
INSERT OVERWRITE TABLE ${db_eda_env}.multi_partition_table
PARTITION (year_id = '2026', month_id = '05')
SELECT
id,
name,
amount,
current_timestamp() AS etl_time
FROM source_table
WHERE year_id = '2026' AND month_id = '05';
-- 13. 多分区字段动态写入
SET spark.sql.partitionOverwriteMode = dynamic;
INSERT OVERWRITE TABLE ${db_eda_env}.multi_partition_table
PARTITION (year_id, month_id)
SELECT
id,
name,
amount,
current_timestamp() AS etl_time,
year_id,
month_id
FROM source_table;
-- ============================================================================
-- 分区数据清理
-- ============================================================================
-- 14. 清理指定分区数据
INSERT OVERWRITE TABLE ${db_eda_env}.daily_partition_table
PARTITION (day_id = '2026-05-09')
SELECT * FROM ${db_eda_env}.daily_partition_table
WHERE day_id = '2026-05-09'
AND status = 'valid'; -- 只保留有效数据
-- 15. 清理 N 天前分区(批量)
-- 使用脚本或程序循环执行
-- ALTER TABLE xxx DROP PARTITION (day_id = '历史分区')
-- ============================================================================
-- 分区最佳实践
-- ============================================================================
/*
1. 分区字段选择原则
- 查询高频过滤字段
- 数据量分布均匀的字段
- 时间字段最常用day_id, month_id
2. 分区粒度选择
- 日增量数据 → day_id 分区
- 月增量数据 → month_id 分区
- 大数据量 → 可细分到 hour_id
3. 分区数量控制
- 单表分区数建议 < 10000
- 过多分区影响元数据性能
4. 查询必须带分区过滤
- 避免SELECT * FROM table全表扫描
- 推荐SELECT * FROM table WHERE day_id = '${day_id}'
5. 动态分区写入设置
- SET spark.sql.partitionOverwriteMode = dynamic;
- 避免误覆盖其他分区
6. 分区数据清理
- 定期清理历史分区如保留近90天
- 使用 ALTER TABLE DROP PARTITION
*/

View File

@@ -0,0 +1,160 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-QUERY
-- @Version: 1.0
-- @Desc: 标准 SELECT 查询模板
-- @TargetTables: 无(查询输出)
-- @SourceTables: {源表列表}
-- @TargetDatabase: Paimon
-- @SourceDatabase: Paimon
-- =====================================================================
-- ============================================================================
-- 基础查询示例
-- ============================================================================
-- 1. 单表查询
SELECT
id,
name,
amount,
created_at
FROM source_table
WHERE day_id = '${day_id}' -- 分区过滤
AND status = 'active' -- 业务过滤
ORDER BY created_at DESC
LIMIT 1000;
-- ============================================================================
-- JOIN 查询示例
-- ============================================================================
-- 2. 两表 JOIN
SELECT
a.id,
a.name,
b.category_name
FROM main_table a
JOIN dim_table b ON a.category_id = b.id
WHERE a.day_id = '${day_id}'
AND b.is_active = true;
-- 3. 多表 JOIN带别名
SELECT
o.order_id,
u.user_name,
p.product_name,
oi.quantity,
oi.unit_price
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.day_id = '${day_id}'
AND o.status IN ('completed', 'shipped');
-- ============================================================================
-- 聚合查询示例
-- ============================================================================
-- 4. GROUP BY 聚合
SELECT
department,
COUNT(*) AS employee_count,
SUM(salary) AS total_salary,
AVG(salary) AS avg_salary,
MAX(salary) AS max_salary,
MIN(salary) AS min_salary
FROM employees
WHERE day_id = '${day_id}'
GROUP BY department
HAVING COUNT(*) >= 5
ORDER BY total_salary DESC;
-- 5. 多字段分组 + 去重计数
SELECT
date,
region,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(amount) AS total_amount
FROM orders
WHERE day_id = '${day_id}'
GROUP BY date, region;
-- ============================================================================
-- 窗口函数示例
-- ============================================================================
-- 6. ROW_NUMBER分组取Top N
SELECT *
FROM (
SELECT
department,
name,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
WHERE day_id = '${day_id}'
) t
WHERE rn <= 3; -- 每个部门薪资前3名
-- 7. 累计聚合
SELECT
date,
amount,
SUM(amount) OVER (ORDER BY date) AS cumulative_amount,
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg_7d
FROM daily_sales
WHERE day_id = '${day_id}';
-- 8. LAG/LEAD环比计算
SELECT
date,
amount,
LAG(amount, 1) OVER (ORDER BY date) AS prev_amount,
amount - LAG(amount, 1) OVER (ORDER BY date) AS daily_change,
ROUND((amount - LAG(amount, 1) OVER (ORDER BY date))
/ LAG(amount, 1) OVER (ORDER BY date) * 100, 2) AS growth_rate_pct
FROM daily_sales
WHERE day_id = '${day_id}';
-- ============================================================================
-- 子查询示例
-- ============================================================================
-- 9. IN 子查询
SELECT *
FROM orders
WHERE user_id IN (
SELECT id FROM users WHERE vip_level >= 3
)
AND day_id = '${day_id}';
-- 10. EXISTS 子查询
SELECT *
FROM products p
WHERE EXISTS (
SELECT 1 FROM inventory i
WHERE i.product_id = p.id
AND i.quantity > 0
)
AND p.day_id = '${day_id}';
-- ============================================================================
-- 条件聚合示例
-- ============================================================================
-- 11. CASE WHEN + 聚合
SELECT
date,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
SUM(CASE WHEN amount > 1000 THEN amount ELSE 0 END) AS high_value_amount
FROM orders
WHERE day_id = '${day_id}'
GROUP BY date;