一个BigQueryOperator的模板化destination_dataset_table arg作为另一个模板化的From

时间:2018-07-12 22:35:10

标签: google-bigquery airflow

我正在尝试在ETL管道中将一堆BigQuery SQL命令链接在一起,其中一些输出和输入将带有时间戳。

from datetime import timedelta
import airflow
from airflow import DAG
from airflow.contrib.operators.bigquery_operator import BigQueryOperator

DAG_NAME = 'foo'

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': airflow.utils.dates.days_ago(7),
    'email': ['xxx@xxx.com'],
    'email_on_failure': True,
    'email_on_retry': False,
    'retries': 1,
    'retry_delay': timedelta(minutes=1),
}

dag = DAG(
    dag_id="blah",
    default_args=default_args,
    schedule_interval=None,
    template_searchpath=["/usr/local/airflow/dags/xxx/sql"])


GOOGLE_PROJECT_ID = 'xxx'
DATASET_ID = 'xxx'
first_output = GOOGLE_PROJECT_ID + ":" + DATASET_ID + "." + "first_output_" + '{{ ds_nodash }}'
second_output = GOOGLE_PROJECT_ID + ":" + DATASET_ID + "." + "second_output"
GOOGLE_CLOUD_PLATFORM_CONNECTION_ID="google_cloud_default"


first_op = BigQueryOperator(
    task_id='first_output',
    dag=dag,
    bigquery_conn_id=GOOGLE_CLOUD_PLATFORM_CONNECTION_ID,
    bql="XXX.sql",
    use_legacy_sql=True,
    allow_large_results=True,
    destination_dataset_table=first_output # {{ ds }} gets substituted because destination_dataset_table is a templated field
)

second_op = BigQueryOperator(
    task_id='second_op',
    dag=dag,
    bigquery_conn_id=GOOGLE_CLOUD_PLATFORM_CONNECTION_ID,
    bql="XXX_two.sql", # XXX_two.sql contains a {{ params.input_table }} reference
    params={'input_table': first_op.destination_dataset_table},
    use_legacy_sql=True,
    allow_large_results=True,
    destination_dataset_table=second_output

)

second_op.set_upstream(first_op)

XXX_two.sql的内容:

SELECT * FROM [{{ params.input_table }}

通过以下方式进行测试:

airflow test blah second_op  2015-06-01

我当前的错误是(也在生产中)

Exception: BigQuery job failed. Final error was: {'reason': 'invalid', 'location': BLAH, 'message': 'Invalid table name: xxx:xx.first_output_{{ ds_nodash }}'}. 

如何在执行操作员之外访问模板化字段?

3 个答案:

答案 0 :(得分:4)

字段destination_dataset_table绝对是模板的,可以看到in the source code(在1.9中,没有提供版本,所以我使用了最新的版本):

template_fields = ('bql', 'destination_dataset_table')

我将创建字符串更改为:

first_output = "[{project}:{dataset}.first_output_{{{{ ds_nodash }}}}]".format(
    project=GOOGLE_PROJECT_ID,
    dataset=DATASET_ID)

四个大括号应该变成两个,并且结果字符串应该看起来像

[my_project:my_dataset.first_output_{{ ds_nodash }}]

现在ds_nodashdestination_dataset_table中使用时应该被解析。

请注意,我还为旧声明添加了所需的括号[ ]。我不确定这是否也可能与缺少的括号有关。

编辑

正如@mask正确说明的那样,您正在使用first_op second_opparams中的字符串,我一开始没有看到它。

由于以下原因,此方法不起作用:

  • first_op不应该提供字符串,但是您应该使用first_output-我仍然想知道为什么这首先起作用
  • 如果您要从任务中提取字符串,则不会获取渲染的字符串,但始终获取原始的模板字符串*如果您不确定字段是否已处理(如遮罩)
  • params只是未模板化,因此无法正确更新

这些是我能想到的解决方案:

  • 派生您自己的BigDataOperator并将params添加到模板字段(如果在b / c中有效,则是字典)
  • 或扩展xxx_two.sql,使其不使用params.input_table,但也使用first_output。由于您希望first_output在模板中可用,因此必须首先将其添加到DAG参数user_defined_macros中。

要查看有关这些解决方案的更多信息,请查看以下相关问题:Make custom Airflow macros expand other macros

答案 1 :(得分:2)

您绝对可以像您正在做的那样从运算符外部引用宏,我正在做这是我的一些工作流程。

您是否尝试过更改为:

first_output = GOOGLE_PROJECT_ID + ":" + DATASET_ID + "." + "first_output_{{ ds_nodash }}"

也许JINJA不喜欢用不同引号引起的字符串连接?

答案 2 :(得分:2)

您正在将未呈现的模板化表名作为参数发送给second_op。

在任务实例first_op.destination_dataset_table上调用render_templates之前,将input_table的值分配给first_op。当在second_op中呈现bql时,它仅转换参数的值并因此返回:

SELECT * FROM xxx:xx.first_output_{{ ds_nodash }}

如果将bql转换为字符串,则可以使用,例如:

    BigQueryOperator(task_id='second_op',...,
                     bql='SELECT * FROM [{table}]'.format(table=first_op.destination_dataset_table)

并设置@ tobi6提到的first_output。

除非您的SQL与示例一样小,或者您愿意将SQL放在DAG文件中的某个位置,否则这可能不是可行的解决方案。

编辑:

由于在DAG的定义中添加了temaplate_searchpath,因此可以按以下方式更新XXX_two.sql

SELECT * FROM [{{ params.input_table }}_{{ ds_nodash }}]

这允许您传递上一个操作中的表名,但将渲染BQ表分区的任务留给了Airflow操作员。如果从同一DAG调用每个operator / task_instance,则可以解决您的问题。

您可以更新到:

first_ouput = GOOGLE_PROJECT_ID + ":" + DATASET_ID + "." + "first_output"
first_op = BigQueryOperator(...,destination_dataset_table= "{}_{{{{ ds_nodash }}}}".format(first_ouput))
second_op = BigQueryOperator(..., params={'input_table': first_output},...