📘

dbt build --emptyが_TABLE_SUFFIXや_PARTITIONTIMEが原因で実行できないときの対処法

2024/07/18に公開

dbt core 1.8から空のリソースを作成することができる --empty フラグが追加されている。

このフラグを指定すると、裏側では limit 0 という条件が追加され、出力が空になるのはもちろん、スキャン量がゼロになる。

この機能は色々なところで使い道があり、

  • クエリは書けたので、まずはSELECT文に不備がないか簡単に確認したいとき
  • dbt-osmosisでyamlを書きたいので、空テーブルで良いので作成しておきたいとき

などが思いつく。特に最近はUSDが高く、スキャン量が大きいクエリをdebug目的で連発するのは気が引けるため、こういったコスト削減に繋がる機能は地味に有難い。

ただ、この機能を使うときに注意が必要なのが、_TABLE_SUFFIX_PARTITIONTIME などの疑似列を参照しているクエリの場合だ。

例えば以下のようなモデルがあったとする。

SELECT
  *
FROM
  {{ source("ga4", "events_*") }}
WHERE
  _TABLE_SUFFIX BETWEEN FORMAT_DATE("%Y%m%d", CURRENT_DATE("+9") -3 )
  AND FORMAT_DATE("%Y%m%d", CURRENT_DATE("+9"))

このモデルに対して、dbt build --emptyを実行すると、以下のようなエラーになる。

Unrecognized name: _TABLE_SUFFIX at [6:3]

compileされたクエリを見てみると、以下のようになっている。

SELECT
  *
FROM
  (select * from `bigquery-public-data`.`ga4_obfuscated_sample_ecommerce`.`events_*` where false limit 0)
WHERE
  _TABLE_SUFFIX BETWEEN FORMAT_DATE("%Y%m%d", CURRENT_DATE("+9") -3 )
  AND FORMAT_DATE("%Y%m%d", CURRENT_DATE("+9"))

挙動としては、 --empty フラグを付けると、sourceやrefで参照しているデータソースがlimit 0 をつけたサブクエリとしてラップされるのだが、

サブクエリの select * が実行されたタイミングで疑似列は保持されなくなるため、そのあとのクエリで _TABLE_SUFFIX 参照することができなくなりエラーになっている。

この挙動については、1.8リリース直後に早速報告されているが、実際のところ、この挙動はバグではないので回避策を考える必要がある。

https://github.com/dbt-labs/dbt-bigquery/issues/1243

比較的シンプルな対応方法として以下のissue内でも言及されているのが、jinja内で使用できる flags というcliでの指定有無を判別する変数を使う方法だ。

https://github.com/dbt-labs/dbt-core/issues/10152

https://docs.getdbt.com/reference/dbt-jinja-functions/flags

dbt-coreのコードを見ないと細かいことは分からないが、判別結果としてTrue/Falseが格納されており、is_incremental と同じような使い方ができることが分かる。

スキャン量ゼロであれば、 _TABLE_SUFFIX でのフィルターも不要になるので、あとはこの変数を使って、--empty フラグが指定された場合は_TABLE_SUFFIXを参照しないようにすれば良い。

SELECT
  *
FROM
  {{ source("ga4", "events_*") }}
{% if not flags.EMPTY %}
WHERE
  _TABLE_SUFFIX BETWEEN FORMAT_DATE("%Y%m%d", CURRENT_DATE("+9") -3 )
  AND FORMAT_DATE("%Y%m%d", CURRENT_DATE("+9"))
{% endif %}

とはいえ、この方法にも若干問題があり、require_partition_filter = true が設定されており、パーティション列として_PARTITIONTIMEなどを使用しているテーブルではクエリが実行できない。

その場合は

  • require_partition_filterを外す
  • 疑似列パーティションを使わずに、実データをパーティション列に指定する
  • issue内のコメントにあるように、他のjinja関数と組み合わせて --empty の挙動を書き換える

くらいしかない。ただ、dbtはアップデートも早いので、1.9が出る頃にはもっと良い方法が用意されているかもしれない。

また、こうしたdbt固有の変数や関数は便利な反面、新規メンバーの学習コストが跳ね上がるため、過度な使用は控えた方が良いだろう(使いこなせることに憧れは感じつつも)。

Discussion