-- Oracle虚拟列语法
-- CREATE TABLE oracle_table (
-- id NUMBER,
-- birth_date DATE,
-- age NUMBER GENERATED ALWAYS AS (
-- FLOOR(MONTHS_BETWEEN(SYSDATE, birth_date) / 12)
-- ) VIRTUAL
-- );
-- 云器Lakehouse调整后语法
CREATE TABLE lakehouse_table (
id INT,
birth_date DATE,
-- Oracle的SYSDATE是非确定性的,需要重新设计
year_part INT GENERATED ALWAYS AS (year(birth_date))
);
-- 主要差异:
-- 1. Oracle支持非确定性函数,Lakehouse不支持
-- 2. Oracle的CASE WHEN支持,Lakehouse需要用if()嵌套
Hive/Spark迁移
-- Hive/Spark通常使用视图
-- CREATE VIEW hive_view AS
-- SELECT id, event_time,
-- hour(event_time) as hour_col,
-- date_format(event_time, 'yyyy-MM-dd') as date_str
-- FROM raw_table;
-- 云器Lakehouse生成列:真实物理存储,性能更好
CREATE TABLE lakehouse_table (
id INT,
event_time TIMESTAMP_LTZ,
hour_col INT GENERATED ALWAYS AS (hour(event_time)),
date_str STRING GENERATED ALWAYS AS (date_format(event_time, 'yyyy-MM-dd'))
);
-- 优势对比:
-- Hive/Spark视图:查询时计算,性能开销大
-- Lakehouse生成列:预计算存储,查询性能好
VIRTUAL 与 STORED 模式选择
核心区别
维度
VIRTUAL(默认)
STORED
存储开销
无,不写入 Parquet
占用磁盘,写入时物化
读取性能
每次查询重新计算
直接读列,0 计算
写入开销
无
每行写入时多一次计算
索引支持
支持(索引独立存储)
支持
CLUSTER BY key
不支持
支持
ALTER TABLE ADD
支持
需开启配置,旧数据读出 NULL
Dynamic Table / MV
不支持
不支持
选 VIRTUAL 的场景
JSON 字段提取 + 索引:JSON 原始列很大,只需对某个字段建倒排/向量索引
CREATE TABLE logs (
id INT,
payload JSON,
level STRING GENERATED ALWAYS AS (json_extract_string(payload, '$.level')),
INDEX idx_level(level) USING INVERTED
) USING PARQUET;
分区键:将时间戳格式化为日期字符串作分区键,无需额外存储
CREATE TABLE events (
ts TIMESTAMP,
sale_date STRING GENERATED ALWAYS AS (date_format(ts, 'yyyy-MM-dd'))
) PARTITIONED BY (sale_date);
-- 业务逻辑封装在生成列中
CREATE TABLE order_analysis (
order_id INT,
customer_id INT,
order_time TIMESTAMP_LTZ,
amount DOUBLE,
-- 时间维度生成列
order_date STRING GENERATED ALWAYS AS (date_format(order_time, 'yyyy-MM-dd')),
order_hour INT GENERATED ALWAYS AS (hour(order_time)),
order_quarter STRING GENERATED ALWAYS AS (
concat(cast(year(order_time) as string), '-Q', cast(quarter(order_time) as string))
),
-- 业务逻辑生成列(使用if()嵌套)
amount_level STRING GENERATED ALWAYS AS (
if(amount >= 1000, 'HIGH',
if(amount >= 500, 'MEDIUM', 'LOW'))
),
-- 时间段分类
time_period STRING GENERATED ALWAYS AS (
if(hour(order_time) >= 6 AND hour(order_time) <= 11, 'MORNING',
if(hour(order_time) >= 12 AND hour(order_time) <= 17, 'AFTERNOON',
if(hour(order_time) >= 18 AND hour(order_time) <= 23, 'EVENING', 'NIGHT')))
)
) PARTITIONED BY (order_date);
数据质量保证
-- 数据标准化和清洗
CREATE TABLE customer_data_clean (
customer_id INT,
raw_phone STRING,
raw_email STRING,
registration_time TIMESTAMP_LTZ,
-- 数据清洗生成列
clean_phone STRING GENERATED ALWAYS AS (
regexp_replace(raw_phone, '[0-9]', '') -- 只保留数字
),
clean_email STRING GENERATED ALWAYS AS (
lower(trim(raw_email)) -- 转小写并去空格
),
-- 数据验证生成列
phone_valid STRING GENERATED ALWAYS AS (
if(length(regexp_replace(raw_phone, '[0-9]', '')) = 11, 'VALID', 'INVALID')
),
email_valid STRING GENERATED ALWAYS AS (
if(raw_email LIKE '%@%' AND raw_email LIKE '%.%', 'VALID', 'INVALID')
),
-- 注册时间维度
reg_date STRING GENERATED ALWAYS AS (date_format(registration_time, 'yyyy-MM-dd'))
) PARTITIONED BY (reg_date);
IoT传感器数据处理
-- IoT数据处理表
CREATE TABLE iot_sensor_data (
sensor_id STRING,
device_id STRING,
timestamp_utc TIMESTAMP_LTZ,
temperature DOUBLE,
humidity DOUBLE,
pressure DOUBLE,
-- 时间维度生成列
date_str STRING GENERATED ALWAYS AS (date_format(timestamp_utc, 'yyyy-MM-dd')),
hour_int INT GENERATED ALWAYS AS (hour(timestamp_utc)),
-- 数据质量生成列
temp_status STRING GENERATED ALWAYS AS (
if(temperature IS NULL, 'MISSING',
if(temperature < -50 OR temperature > 80, 'OUTLIER', 'NORMAL'))
),
-- 业务分析生成列
temp_level STRING GENERATED ALWAYS AS (
if(temperature >= 30, 'HOT',
if(temperature >= 20, 'WARM',
if(temperature >= 10, 'COOL', 'COLD')))
),
-- 15分钟时间块(lpad 补零,输出如 "09:00", "09:15")
time_block STRING GENERATED ALWAYS AS (
concat(
lpad(cast(hour(timestamp_utc) as string), 2, '0'),
':',
lpad(cast((minute(timestamp_utc) / 15) * 15 as string), 2, '0')
)
)
) PARTITIONED BY (date_str);
使用限制和使用注意事项
关键限制
1. 条件表达式限制
-- ❌ 错误:使用CASE WHEN表达式(不支持)
CREATE TABLE wrong_table (
score INT,
grade STRING GENERATED ALWAYS AS (
CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
ELSE 'C'
END
)
);
-- ✅ 正确做法:使用if()函数嵌套
CREATE TABLE correct_table (
score INT,
grade STRING GENERATED ALWAYS AS (
if(score >= 90, 'A',
if(score >= 80, 'B', 'C'))
)
);
2. 函数支持限制
-- ❌ 常见错误:使用不支持的函数
CREATE TABLE wrong_functions (
id INT,
created_at TIMESTAMP_LTZ GENERATED ALWAYS AS (current_timestamp()), -- 非确定性
random_val DOUBLE GENERATED ALWAYS AS (random()), -- 非确定性
name_cap STRING GENERATED ALWAYS AS (initcap('test')) -- 函数不存在
);
-- ✅ 正确做法:使用支持的确定性函数
CREATE TABLE correct_functions (
id INT,
input_time TIMESTAMP_LTZ,
hour_part INT GENERATED ALWAYS AS (hour(input_time)),
formatted STRING GENERATED ALWAYS AS (date_format(input_time, 'yyyy-MM-dd'))
);
3. VIRTUAL 列不能用于 CLUSTER BY
-- ❌ 错误:VIRTUAL 生成列不能作为 CLUSTER BY key
CREATE TABLE t (
c1 INT,
c2 INT GENERATED ALWAYS AS (c1 + 1) -- VIRTUAL
) CLUSTERED BY (c2);
-- 报错:generated.column.conflict.with.cluster
-- ✅ 正确做法:改为 STORED
CREATE TABLE t (
c1 INT,
c2 INT GENERATED ALWAYS AS (c1 + 1) STORED
) CLUSTERED BY (c2);
4. Dynamic Table / 物化视图中不支持生成列定义
-- ❌ 错误:Dynamic Table 列定义中不能使用生成列(VIRTUAL 或 STORED 均不支持)
CREATE DYNAMIC TABLE dt (c1, c2, c3 INT GENERATED ALWAYS AS (c1 + 1))
AS SELECT * FROM base_table;
5. ALTER TABLE ADD STORED 生成列默认禁用
-- ❌ 默认报错:only support virtual generated column
ALTER TABLE t ADD COLUMN c4 INT GENERATED ALWAYS AS (c1 + 1) STORED;
-- 需开启配置才可执行(注意:旧文件中该列值为 NULL)
-- SET cz.sql.alter.table.add.generated.column.enable.stored=true;
6. DROP COLUMN 依赖检查
-- ❌ 错误:c3 被 c2 依赖,不能直接删除
ALTER TABLE t DROP COLUMN c3; -- 报错:column.dependency
-- ✅ 正确做法:先删依赖列,再删被依赖列
ALTER TABLE t DROP COLUMN c2;
ALTER TABLE t DROP COLUMN c3;
7. ALTER TABLE限制
-- ❌ 错误:不能在已有列上添加生成列属性
-- ALTER TABLE existing_table MODIFY COLUMN existing_col GENERATED ALWAYS AS (expression);
-- ✅ 正确做法:只能添加新的生成列(仅支持 VIRTUAL 模式)
ALTER TABLE existing_table ADD COLUMN
new_generated_col INT GENERATED ALWAYS AS (expression);
最佳实践
表达式设计原则
CASE WHEN 和 if() 嵌套均支持,选择可读性更好的写法即可
使用简单确定性函数
确保表达式性能良好
注意返回类型与列类型匹配
命名规范建议
CREATE TABLE naming_example (
raw_timestamp TIMESTAMP_LTZ, -- 基础列:原始数据
gen_hour INT GENERATED ALWAYS AS (hour(raw_timestamp)), -- 生成列:gen_前缀
gen_date STRING GENERATED ALWAYS AS (date_format(raw_timestamp, 'yyyy-MM-dd'))
);