运行dbt Jinja模型时提示unexpected ')'语法错误如何解决
dbt对接Snowflake动态生成UNION语句报意外右括号语法错误
问题场景
- 基于dbt对接Snowflake构建模型,通过Jinja语法遍历指定Schema下的表名列表,为每张表生成对应COPY_HISTORY查询语句
- 预期逻辑:循环遍历每张表生成独立SQL片段,除最后一次循环外,片段之间用
UNION拼接,实现批量查询所有表的近90天复制历史 - 运行时抛出SQL编译错误,手动检查代码未发现明显多余括号:
11:44:23 001003 (42000): SQL compilation error
11:44:23 syntax error line 14 at position 6 unexpected ')'.
原始代码如下:
{{ config( query_tag = 'DBT: Staging_History' ) }} {% set table_names_query %} select table_name from information_schema.tables where table_type = 'BASE TABLE' and TABLE_CATALOG = 'PROD_SOURCE' and TABLE_SCHEMA = 'DIS_STG' {% endset %} {% set results = run_query(table_names_query) %} {% if execute %} {% set results_list = results.columns[0].values() %} {% else %} {% set results_list = [] %} {% endif %} {% for table_name in results_list %} SELECT * FROM TABLE(INFORMATION_SCHEMA.copy_history(table_name=>'PROD_SOURCE.DIS_STG.DBO_ORGANIZATION', start_time=>dateadd(DAY, -90, current_timestamp))) {% if not loop.last %} UNION {% endif %} {% endfor %}
错误根因
- 核心逻辑bug:循环遍历
results_list表名列表,但SELECT语句中COPY_HISTORY的table_name参数被硬编码为固定表DBO_ORGANIZATION,完全没有使用循环变量table_name,不符合批量查询所有表的预期。 - 语法错误直接诱因:
run_query调用放在了{% if execute %}判断外,dbt解析阶段(execute=False)不会实际执行查询,此时results_list为空数组,for循环不会输出任何SELECT语句,最终编译出的SQL为空。部分dbt版本和Snowflake驱动处理空SQL时会出现解析异常,抛出多余括号的报错。- 代码中如果存在转义后的箭头符号(比如从网页复制时带的
=>而非标准=>),Snowflake解析器会将其识别为独立的=、>运算符,打乱表函数的参数结构,导致括号匹配失败。
- 性能隐患:使用
UNION会对所有结果做全局去重,COPY_HISTORY查询场景下无去重必要,会造成不必要的算力浪费。
修复方案
调整代码结构,增加空结果兜底逻辑,修正动态表名拼接,替换为性能更好的UNION ALL,修复后代码如下:
{{ config( query_tag = 'DBT: Staging_History' ) }} {% if execute %} {% set table_names_query %} select table_name from information_schema.tables where table_type = 'BASE TABLE' and TABLE_CATALOG = 'PROD_SOURCE' and TABLE_SCHEMA = 'DIS_STG' {% endset %} {% set results = run_query(table_names_query) %} {% set results_list = results.columns[0].values() %} {% else %} {% set results_list = [] %} {% endif %} {% if results_list | length > 0 %} {% for table_name in results_list %} SELECT * FROM TABLE(INFORMATION_SCHEMA.copy_history( table_name => 'PROD_SOURCE.DIS_STG.{{ table_name }}', start_time => dateadd(DAY, -90, current_timestamp()) )) {% if not loop.last %} UNION ALL {% endif %} {% endfor %} {% else %} -- 无匹配表时返回空结构,避免空SQL报错 SELECT cast(null as varchar) as FILE_NAME, cast(null as varchar) as STAGE_LOCATION, cast(null as timestamp_ltz) as LAST_LOAD_TIME, cast(null as varchar) as STATUS, cast(null as int) as ROW_COUNT, cast(null as int) as ROW_PARSED WHERE 1=0 {% endif %}
验证说明
- 先执行
dbt compile查看target目录下编译出的SQL,确认:- 无多余括号、特殊转义字符
- 每张表的COPY_HISTORY参数中,表名已正确替换为查询到的表名
UNION ALL仅存在于两个SELECT片段之间,末尾无多余的UNION关键字
- 如果使用低版本Snowflake不支持
=>命名参数语法,可将COPY_HISTORY调用改为位置参数写法即可:INFORMATION_SCHEMA.copy_history('PROD_SOURCE.DIS_STG.{{ table_name }}', dateadd(DAY, -90, current_timestamp()))
内容的提问来源于stack exchange,提问作者Nick Hall
相关产品推荐
相关产品推荐

