You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Airflow:如何将Operator数据传入Snowflake存储过程(同会话访问临时表)

问题解决:Airflow中向Snowflake传递批量数据并保证临时表会话一致性

问题背景

需将Airflow Operator生成的批量数据传入SQL文件,创建临时表并确保后续存储过程能在同一会话访问该表,尝试三种方法均失败,具体代码及报错如下:

Operator核心代码

def list_of_tuples(self, worksheet):
    list_of_dicts = worksheet.get_all_records()
    reviewed_rows = [row for row in list_of_dicts]

    manual_data = [
        (
            str(row["NAME"]),
            str(row["VERSION"]),
            row.get("RSCORE", None),
            row["SERVERDATE"],
        )
        for row in reviewed_rows
    ]

    return manual_data

def run_proc(self, manual_data):
    source_hook = SnowflakeHook(snowflake_conn_id=self.source_conn_id)
   
    with closing(source_hook.get_conn()) as source_conn:
        with closing(source_conn.cursor(DictCursor)) as cur:
            try:
                cur.execute(self.sql, manual_data)

错误方法及原因

方法1:直接用Snowflake参数绑定批量数据

SQL文件:

EXECUTE IMMEDIATE $$
BEGIN
    CREATE TEMPORARY TABLE temp_manual_map (
        name STRING,
        version STRING,
        score NUMBER,
        serverdate DATE
    );

    INSERT INTO temp_manual_map
    VALUES :manual_map;
END;
$$;

报错:

TypeError: not all arguments converted during string formatting

原因:Snowflake Python Connector不支持将整个tuple列表作为单个参数传入VALUES子句,且参数传递逻辑与Python字符串格式化冲突,导致解析失败。

方法2:错误混用Jinja2与Snowflake语法

SQL文件:

EXECUTE IMMEDIATE $$
BEGIN
    CREATE TEMPORARY TABLE temp_manual_map (
        name STRING,
        version STRING,
        score INT,
        serverdate DATE
    );

    INSERT INTO manual_map (
        name,
        version,
        score,
        serverdate
    )
    VALUES
    {% for value in :manual_map %}
        (
            '{{ value[0] }}',
            '{{ value[1] }}',
            {{ value[2] }},
            '{{ value[3] }}'
        ){% if not loop.last %},{% endif %}
    {% endfor %};

END;
$$;

报错:

jinja2.exceptions.TemplateSyntaxError: unexpected ':'

原因:Jinja2模板引用变量无需加冒号,:manual_map是Snowflake参数语法,与Jinja2语法冲突。

方法3:无效SQL语句

SQL文件:

EXECUTE IMMEDIATE $$
BEGIN
    manual_map;
END;
$$;

报错:同方法1的字符串格式化错误
原因:manual_map不是合法SQL语句,参数传递逻辑仍未修正。


正确解决方案

方案1:使用Snowflake批量参数绑定(推荐,防SQL注入)

修改Operator的run_proc方法,拆分批量数据为单个参数,用executemany实现批量插入,确保会话一致性:

def run_proc(self, manual_data):
    source_hook = SnowflakeHook(snowflake_conn_id=self.source_conn_id)
   
    with closing(source_hook.get_conn()) as source_conn:
        with closing(source_conn.cursor(DictCursor)) as cur:
            try:
                # 创建临时表
                cur.execute("""
                    CREATE TEMPORARY TABLE temp_manual_map (
                        name STRING,
                        version STRING,
                        score NUMBER,
                        serverdate DATE
                    );
                """)
                # 批量插入数据,使用%s作为占位符
                insert_sql = """
                    INSERT INTO temp_manual_map (name, version, score, serverdate)
                    VALUES (%s, %s, %s, %s);
                """
                # 执行批量插入
                cur.executemany(insert_sql, manual_data)
                # 同一会话内调用存储过程,可访问临时表
                # cur.execute("CALL your_target_procedure();")
            except Exception as e:
                source_conn.rollback()
                raise e
            else:
                source_conn.commit()

说明:全程在同一个数据库会话中执行,临时表可被后续操作直接访问,且批量插入方式避免SQL注入风险。

方案2:正确使用Jinja2模板渲染SQL(适配模板文件场景)

修改SQL模板文件(移除冒号,添加SQL注入防护)

CREATE TEMPORARY TABLE temp_manual_map (
    name STRING,
    version STRING,
    score NUMBER,
    serverdate DATE
);

INSERT INTO temp_manual_map (name, version, score, serverdate)
VALUES
{% for row in manual_data %}
    (
        '{{ row[0] | replace("'", "''") }}',  -- 转义单引号防止注入
        '{{ row[1] | replace("'", "''") }}',
        {{ row[2] if row[2] is not none else 'NULL' }},
        '{{ row[3] }}'
    ){% if not loop.last %},{% endif %}
{% endfor %};

-- 同一会话内调用存储过程
-- CALL your_target_procedure();

修改Operator代码(渲染模板并执行)

from jinja2 import Template

def run_proc(self, manual_data):
    source_hook = SnowflakeHook(snowflake_conn_id=self.source_conn_id)
   
    # 读取并渲染SQL模板
    with open(self.sql_path, 'r') as f:
        sql_template = Template(f.read())
    rendered_sql = sql_template.render(manual_data=manual_data)
   
    with closing(source_hook.get_conn()) as source_conn:
        with closing(source_conn.cursor(DictCursor)) as cur:
            try:
                cur.execute(rendered_sql)
                source_conn.commit()
            except Exception as e:
                source_conn.rollback()
                raise e

说明:通过Jinja2渲染生成合法SQL,全程在同一会话执行,临时表可被后续存储过程访问。


内容的提问来源于stack exchange,提问作者Kar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 00:55:24