statement 블록

statement 블록

(앱: dbt v2.0 이상)

statement는 데이터베이스에 쿼리를 날리고 결과를 Jinja 컨텍스트에 반환하는 SQL 쿼리예요. 다음은 users 테이블에서 모든 state를 가져오는 statement 예시예요.

출처: 문서

본문

dbt v2에서 statement 블록을 쓸 때 — 결과를 가져와야 하는 쿼리(예: 매크로나 Jinja 코드가 데이터베이스에서 반환된 데이터를 써야 할 때)에는 fetch_result=True를 가진 statement 블록이나 run_query 매크로를 쓸 수 있어요. DDL이나 유틸리티 작업(예: OPTIMIZE, VACUUM, 유지보수 쿼리)에서는 Jinja에서 결과에 접근할 필요가 없을 때 fetch_result=False를 가진 statement 블록을 사용해요. 이렇게 하면 non-nullable로 선언된 컬럼에 NULL 값이 포함된 결과 셋을 처리할 때 실패할 수 있는 dbt v2의 엄격한 타입 검사 문제를 피할 수 있어요.

get_states_statement.sql

-- depends_on: {{ ref('users') }}

{%- call statement('states', fetch_result=True) -%}

    select distinct state from {{ ref('users') }}

{%- endcall -%}

statement 블록의 시그니처는 이렇게 생겼어요:

statement(name=None, fetch_result=False, auto_begin=True)

statement를 실행할 때 dbt는 다른 dbt 모델이나 리소스에 대한 참조를 어떻게 해석할지 이해해야 해요. statement 블록 밖에서 이미 모델을 ref했다면 의존성이 자동으로 추론되지만, 그렇지 않다면 -- depends_on으로 의존성을 강제해야 해요.

-- depends_on 사용 예시

-- depends_on: {{ ref('users') }}

{% call statement('states', fetch_result=True) -%}

    select distinct state from {{ ref('users') }}

    /*
    The unique states are: {{ load_result('states')['data'] }}
    */
{%- endcall %}

ref() 함수 사용 예시


{% call statement('states', fetch_result=True) -%}

    select distinct state from {{ ref('users') }}

    /*
    The unique states are: {{ load_result('states')['data'] }}
    */

{%- endcall %}

select id * 2 from {{ ref('users') }}

인자(Args):

  • name (string): 이 statement가 반환한 결과 셋의 이름
  • fetch_result (bool): True면 statement의 결과를 Jinja 컨텍스트에 로드
  • auto_begin (bool): True면 트랜잭션이 없을 때 트랜잭션을 염. False면 트랜잭션을 열지 않음

statement 블록이 실행된 후에는 load_result 함수로 결과 셋에 접근할 수 있어요. 결과 객체는 세 개의 키를 포함해요:

  • response: 데이터베이스에서 반환된 메타데이터를 담은 구조화된 객체로, 어댑터에 따라 달라져요. 예: 성공 code, rows_affected 수, 총 bytes_processed 등. Result 객체adapter_response와 비슷해요.
  • data: 쿼리가 반환한 데이터의 Python 표현(배열, 튜플, 사전)
  • table: 쿼리가 반환한 데이터의 Agate 테이블 표현

위 statement의 경우 다음과 같을 수 있어요:

load_states.sql

{%- set states = load_result('states') -%}
{%- set states_data = states['data'] -%}
{%- set states_status = states['response'] -%}

반환된 data 필드의 내용은 행렬(matrix)이에요. 각 행이 데이터베이스가 반환한 값들의 리스트인 행 리스트를 담고 있어요. 위 예시에서 이 데이터 구조는 다음과 같을 수 있어요:

states.sql

>>> log(states_data)

[
  ['PA'],
  ['NY'],
  ['CA'],
	...
]

Fire and forget 작업

(앱: dbt v2.0 이상)

결과가 필요 없는 DDL이나 유틸리티 작업(반환된 행을 Jinja에서 쓰지 않기 때문에)에서는 fetch_result=False로 설정해요. 이건 Databricks에서 OPTIMIZEVACUUM 같은 작업에 권장되는 패턴이에요. 이런 작업은 non-nullable 컬럼에 null 값이 포함될 수 있는 결과 셋을 반환하거든요.

macros/optimize_table.sql

{% macro optimize_table(table, zorder_fields=[]) %}
  {% set zorder_str = zorder_fields | join(', ') %}

  {% set query %}
    OPTIMIZE {{ table }}
    {% if zorder_str | length > 0 %}
      ZORDER BY ({{ zorder_str }})
    {% endif %}
  {% endset %}

  {% call statement('optimize', fetch_result=False) %}
    {{ query }}
  {% endcall %}
{% endmacro %}

이 매크로를 post-hook에서 쓸 수 있어요:

dbt_project.yml

models:
  my_project:
    +post-hook:
      - "{{ optimize_table(this, ['customer_id', 'order_date']) }}"

더 알아보기 (Learn more)

  • 결과가 필요하면 fetch_result=True, 필요 없으면 fetch_result=False로 statement 블록을 써요.
  • 관련 개념: run_query, load_result.