修改dbt_date的convert_timezone宏支持从数据列读取时区参数
问题原因
原宏所有适配器实现中,都对target_tz、source_tz参数硬编码包裹了单引号,默认将传入值作为固定字符串常量(比如'Asia/Shanghai')解析。如果直接传入列名,会被SQL识别为固定字符串,不会读取行级的列值,自然无法实现动态时区转换。
改造方案
改造核心是增加参数标识传入的时区值是固定字符串还是列引用,同时100%兼容原有固定时区的传参习惯,不需要修改历史代码。
改造后完整宏代码
{# 顶层入口宏,新增两个布尔参数标识时区参数是否为列,默认False兼容旧逻辑 #} {%- macro convert_timezone(column, target_tz=None, source_tz=None, target_tz_is_column=false, source_tz_is_column=false) -%} {%- set source_tz = "UTC" if not source_tz else source_tz -%} {%- set target_tz = var("dbt_date:time_zone") if not target_tz else target_tz -%} {{ adapter.dispatch('convert_timezone', 'dbt_date') (column, target_tz, source_tz, target_tz_is_column, source_tz_is_column) }} {%- endmacro -%} {% macro default__convert_timezone(column, target_tz, source_tz, target_tz_is_column, source_tz_is_column) -%} {# 根据标识决定是否给时区参数加单引号:列引用直接透传,固定字符串补单引号 #} {%- set tz_src = source_tz if source_tz_is_column else "'" ~ source_tz ~ "'" -%} {%- set tz_tgt = target_tz if target_tz_is_column else "'" ~ target_tz ~ "'" -%} {%- if not source_tz -%} cast(convert_timezone({{ tz_tgt }}, {{ column }}) as {{ dbt_utils.type_timestamp() }}) {%- else -%} cast(convert_timezone({{ tz_src }}, {{ tz_tgt }}, {{ column }}) as {{ dbt_utils.type_timestamp() }}) {%- endif -%} {%- endmacro -%} {%- macro bigquery__convert_timezone(column, target_tz, source_tz, target_tz_is_column, source_tz_is_column) -%} {%- set tz_tgt = target_tz if target_tz_is_column else "'" ~ target_tz ~ "'" -%} timestamp(datetime({{ column }}, {{ tz_tgt }})) {%- endmacro -%} {%- macro spark__convert_timezone(column, target_tz, source_tz, target_tz_is_column, source_tz_is_column) -%} {%- set tz_src = source_tz if source_tz_is_column else "'" ~ source_tz ~ "'" -%} {%- set tz_tgt = target_tz if target_tz_is_column else "'" ~ target_tz ~ "'" -%} from_utc_timestamp( to_utc_timestamp({{ column }}, {{ tz_src }}), {{ tz_tgt }} ) {%- endmacro -%} {% macro postgres__convert_timezone(column, target_tz, source_tz, target_tz_is_column, source_tz_is_column) -%} {%- set tz_src = source_tz if source_tz_is_column else "'" ~ source_tz ~ "'" -%} {%- set tz_tgt = target_tz if target_tz_is_column else "'" ~ target_tz ~ "'" -%} {%- if source_tz -%} cast({{ column }} at time zone {{ tz_src }} at time zone {{ tz_tgt }} as {{ dbt_utils.type_timestamp() }}) {%- else -%} cast({{ column }} at time zone {{ tz_tgt }} as {{ dbt_utils.type_timestamp() }}) {%- endif -%} {%- endmacro -%} {%- macro redshift__convert_timezone(column, target_tz, source_tz, target_tz_is_column, source_tz_is_column) -%} {{ return(dbt_date.default__convert_timezone(column, target_tz, source_tz, target_tz_is_column, source_tz_is_column)) }} {%- endmacro -%}
使用方式
- 原有固定时区传参逻辑完全不变,不需要修改历史代码:
select {{ dbt_date.convert_timezone('created_at', 'Asia/Shanghai', 'UTC') }} as created_at_cst from orders - 传入列作为动态时区参数时,将对应的
*_is_column参数设为true即可:
上述代码中select {{ dbt_date.convert_timezone('created_at', 'user_timezone_col', 'UTC', target_tz_is_column=true) }} as created_at_local from user_eventsuser_timezone_col是表内存储每行对应时区的字段,SQL执行时会逐行读取该列值完成时区转换。
注意事项
- 作为参数传入的时区列,存储值必须是对应数据平台支持的合法IANA时区格式(比如
America/Los_Angeles、Asia/Tokyo),否则执行时会抛出时区无效错误。 - 如果source_tz也需要使用动态列,同理设置
source_tz_is_column=true即可。
内容的提问来源于stack exchange,提问作者Dug
相关产品推荐
相关产品推荐

