❄️

【dbt × Snowflake】Google Sheetsへの連携を最小構成で実現する方法 with Omnata

に公開

この記事について

この記事では、dbtとSnowflakeを使用してGoogle Sheetsへデータを連携する、シンプルかつ実用的な方法をご紹介します。

🎯 想定読者

  • Snowflakeでデータ基盤を運用している方
  • dbtを使ってデータパイプラインを構築している方
  • アドホックな分析のためにGoogle Sheetsへの連携を検討している方

🔖 この記事で分かること

  • SnowflakeからシンプルにGoogle Sheetsへデータを連携する方法
  • 追加のオーケストレーションツールなしで実装する方法
  • dbtを活用した実践的な連携パイプラインの構築方法

🔤 はじめに

データ基盤を提供する際に、BIとして何を利用することが多いでしょうか?

商用ツールからOSSまで広くありますが、集計値をグラフやら表やらでプレゼンテーションするという役割をこなすのであれば、広くあまねく選択肢がありますね。

あまりBIツールとして挙げた際には候補として出てこないと思いますが、Google Sheets(通称スプレッドシート)も分析に使う場面自体は多いと思います。
あんまりお勧めできない使い方ではあると思いますが、BIとして作りこみをするにはやや重たいなと思うときに、BI代わりに使ってみたり、手元でガチャガチャ数字をいじったりする際に馴染みのツールを使いたい要望やらいろいろあると思いますので、時にはデータエンジニアとしてそのパイプラインを組む場面に出会うこともあるでしょう。

BigQueryを利用している場合は至極当たり前に連携できると思いますが、Snowflakeを利用する場合はいろいろ選択肢があってどのような連携方式を取るべきか悩むことも多くあると思います。

以下のような要件を満たす必要があると仮定してどのような連携方式を思い浮かべるでしょうか?

  • dbtで加工したテーブルを転送したい
  • dbtモデルは日時のバッチで生成するが、データ量の違いなどで生成が完了するタイミングが安定しない
  • ただし、モニタリングは朝一で実施するのでそこまでなるべく早く連携が完了している必要がある

本記事では上記の要件を満たしつつ特殊なオーケストレーションツールの設定などを必要としないで最もシンプルに実装できる方法をご紹介します。

❓ なぜGoogle Sheetsなのか?

多くの場合、データ可視化にはLookerやTableauなどの本格的なBIツールを使用します。しかし、以下のようなケースではGoogle Sheetsが有効な選択肢となることがあります:

  • ✅ アドホックな分析や一時的なモニタリングが必要な場合
  • ✅ 既存のスプレッドシートデータとの結合が必要な場合
  • ✅ チーム内で手軽にデータを共有・編集したい場合

📋 実装要件

今回は以下のような要件を満たす実装を目指します:

  • ✅ dbtで加工したテーブルを自動転送
  • ✅ バッチ処理の完了タイミングが不安定でも対応可能
  • ✅ 朝一の分析に間に合うよう、確実なデータ連携

🛠 実装方法

採用する技術スタック

  1. Snowflake Marketplace提供のOmnata Sync Engine
  2. Omnata Google Sheets Plugin
  3. dbtマクロによる汎用的な実装

メリット

  • 👍 dbtのみで完結(SQL + YAML)
  • 👍 追加ライセンス不要
  • 👍 既存のdbtパイプラインに統合可能

デメリット

  • ⚠️ Omnataは比較的新しいツール
  • ⚠️ レコードの順序性は保証されない
  • ⚠️ 日付型の扱いに制限あり(文字列として連携される)

🗺️ どのように実装するのか

今回は以下の仕組みで実現しようと思います。

  • SnowflakeのMarketplaceで提供されているOmnata Sync Engine + Omnata Google Sheets Pluginを利用
  • dbtのマクロを利用してOmnataのUDFを呼び出せるようにして汎用的に利用できるようにする
  • dbtモデルで連携対象のデータの最終的な加工処理のロジックを組みつつマクロを呼び出して転送処理を実行する

この連携方式のメリットは以下の通りです。

  • dbtで完結するのでアナリストなどが開発に参加できるようになる(SQLとYAMLファイルを書くだけ)
  • Omnataの無料のプラグインを利用するので追加のライセンスが不要
  • dbtのパイプラインにそのまま組み込めるのでオーケストレーションツールなどに手を入れる必要がない

デメリットは以下の通りです。

  • Omnataおよびその基礎となるNative App Frameworkは進化が早く、あまり枯れていないのでたまにエラーを引くことがある
  • いつまでも無料で提供されるとは限らない
  • 現時点では書き込みする際のレコードはorder byなどでソートしても意味がないのでレコードの順序性が重要な場合は利用できない
  • 日付は文字列として連携されるのでつらい
  • 現時点では洗い替えでしか連携できないので追記が必要な場合はSQL側で過去のデータとUNIONするなどの実装をしないといけない

とはいえ、現状はとてもコスパのよい連携方式だと思っています。

🔨 導入手順

1️⃣ 初期設定

  1. Snowflake Marketplaceでの設定
  2. 必要な権限の付与
  3. 動作確認

2️⃣ dbtの実装

  1. マクロの作成
  2. カスタムテストの実装
  3. モデルの作成

3️⃣ 実装例

後述します。

🔰 導入手順の解説とサンプル実装のコード

以下の手順でOmnataとGoogle Sheetsの初期設定をしていきます。

  1. SnowflakeのMarketplaceからOmnata Sync EngineとOmnata Google Sheets Pluginを導入する。
    参考:https://dev.classmethod.jp/articles/snowflake-try-omnata-google-sheets/

  2. 以下のApplication Roleをdbtを実行するロールに付与する(GRANTの構文
    ※アプリケーションの名前はデフォルトを採用している前提
    OMNATA_GOOGLE_SHEETS_PLUGIN.CONSUMER_FUNCTION_CALLER: WRITE_SHEETを実行する権限
    OMNATA_SYNC_ENGINE.OMNATA_ADMINISTRATOR: PLUGIN_CONNECTIONを実行する権限

-- dbtを実行するロールに付与(この例ではENGINEERロール)
grant application role OMNATA_GOOGLE_SHEETS_PLUGIN.CONSUMER_FUNCTION_CALLER to role ENGINEER;
grant application role OMNATA_SYNC_ENGINE.OMNATA_ADMINISTRATOR to role ENGINEER;
  1. 可能であれば、この時点でサンプルのSQLを実行して転送が問題なくできるかを確認しておくといい。(WITE_SHEET

  2. dbtのマクロとカスタムデータテスト(今回の例はGeneric test)を追加する(コードは後述)

  3. dbtモデルのSQLファイルとYAMLファイルを追加して動作確認

dbt macro

以下のサンプルを個別のプロジェクトに合うように加工してみてください。
基本的にdev環境と本番環境が異なる形で運用していることが多いと思います。
profileで定義しているtargetに属しているかどうかでwrite_sheetを実行するかどうかが分かれるように分岐にしています。

macros/omnata_google_sheet_write_sheet.sql
{%- macro omnata_google_sheet_write_sheet(
  connection_slug,
  spreadsheet_id,
  sheet_name,
  include_headers='true',
  clear_sheet='true',
  interpret_raw='true'
) -%}

{#- check target env #}
{%- if target.name in config.get('target_envs', []) -%}

  {#- get column names from model #}
  {%- set column_names = [] -%}
  {%- if model %}
    {%- if model.columns %}
      {%- set column_names = model.columns.keys() -%}
    {%- endif -%}
  {%- endif -%}

  {#- object_construct arguments #}
  {%- set object_construct_args -%}
    {%- for column_name in column_names -%}
      '{{ column_name }}', {{ column_name }}{{ "," if not loop.last }}
    {%- endfor -%}
  {%- endset -%}

  {#- heading_order argument #}
  {%- set heading_order -%}
    [
      {%- for column_name in column_names -%}
        '{{ column_name }}'{{ "," if not loop.last }}
      {%- endfor -%}
      ]
  {%- endset -%}

  {#- render table function #}
  ,table(OMNATA_GOOGLE_SHEETS_PLUGIN.UDFS.WRITE_SHEET(
    OMNATA_SYNC_ENGINE.API.PLUGIN_CONNECTION('{{ connection_slug }}'),
    '{{ spreadsheet_id }}',
    object_construct_keep_null({{ object_construct_args }}
    ),
    '{{ sheet_name }}',
    {{ include_headers }},
    {{ heading_order }},
    {{ clear_sheet }},
    {{ interpret_raw }}
  ) over (partition by 1))

  {%- do log("[omnata_google_sheet_write_sheet] write_sheet to " ~ sheet_name ~ " in " ~ spreadsheet_id, info=True) %}

{%- elif execute -%}
  {%- do log("[omnata_google_sheet_write_sheet] \u001b[33mskip write_sheet\u001b[0m because target " ~ target.name ~ " is not in " ~ config.get('target_envs', []) ~ " envs", info=True) -%}
{%- endif -%}

{%- endmacro -%}

dbt test

ephemeralのモデルなのでgeneric testを実行する中でCTEの一部としてクエリが実行されるようにします。

tests/generic/omnata_google_sheet_test.sql
{% test omnata_google_sheet_test(model, column_name) %}

{#- check if the model has any rows #}
with validation as (
  select count(*) as row_count from {{ model }}
),

validation_errors as (
  select row_count
  from validation
  where row_count = 0
)

select *
from validation_errors

{% endtest %}

dbt model SQL file

SQLファイルは末尾にマクロの呼び出しの設定を書くだけで通常のSELECT文を書くだけで大丈夫です。
引数は以下の3つが必須です。

  • connection_slug: - つなぎで設定されるのでご注意を(表示されるconnection名だと違うことがある)
  • spreadsheet_id: URLの一部にあるのでコピペしてきましょう
  • sheet_name: シート名を貼り付けましょう。全クリアされるので専用のシートが推奨です
models/<sub_directory>/omnata_gs__test.sql
with _target_cte as (
  select 1 as INT_COL, 'abc' as VARCHAR_COL, '2025-03-15' as DATE_COL
  union
  select 2 as INT_COL, 'def' as VARCHAR_COL, '2025-03-16' as DATE_COL
  union
  select 3 as INT_COL, null as VARCHAR_COL, '2025-03-17' as DATE_COL
)

select * from _target_cte
{{ omnata_google_sheet_write_sheet(
  connection_slug = 'snowflake-to-google-sheet',
  spreadsheet_id = '1NSwf0gsJCW5g49XmX8P1Y3Imk1YQ4oDoUsiBOur-xxx',
  sheet_name = 'test'
) }}

サンプルは適当なデータを生成するだけのCTEですが、実際にはref関数で加工・集計済みのテーブルなどを参照することになると思います。

dbt model YAML file

YAMLファイルには以下の設定を入れておきます

  • configで以下を設定する
    • materialized: ephemeral
    • target_envs: WRITE_SHEETを実行する際に指定するtargetのリスト
  • date_testsとしてomnata_google_sheet_testを指定する
  • columnsで連携対象のカラムの一覧を設定する
models/<sub_directory>/omnata_gs__test.yml
version: 2

models:
  - name: omnata_gs__test
    description: "Test model for omnata_google_sheet_write_sheet"
    config:
      materialized: ephemeral
      target_envs:
        - prd
    data_tests:
      - omnata_google_sheet_test
    columns:
      - name: INT_COL
      - name: VARCHAR_COL
      - name: DATE_COL

この例では --target prd を指定してbuild or testを実行するとwrite_sheetが実行されます。

動作確認

上記のファイルを作成したら、動作確認として以下を実行してみましょう
(targetは各々の環境に合わせて修正してください)

# target_envsのリスト外だと何も動作しないこと
dbt build --select omnata_gs__test --target dev

# target_envsのリスト内だと書き込み処理が走ること
dbt build --select omnata_gs__test --target prd

😋 最後に

dbt + Omnata を組み合わせることでシンプルにGoogle SheetsへのReverse ETLが実現できました。

実際の現場では永続的にGoogle Sheetsを使い続けるのはあまり良くない状態だとは思うので、定常的に使うというよりは一時的に簡易にモニタリングしたい施策の効果測定など、いちいち仰々しくBIのダッシュボードを作るほどでもない暫定的なお試し分析(だけど、データはある程度最新のものが鮮度高めに欲しいケースなど)で利用する形がいいかなと思います。

Google Sheetsにしかいないデータと突合させたり、手元でガチャガチャやりたい場合にまだまだ便利なことが多いGoogle Sheets。データ基盤の利用者に寄り添って無下には断れない際に参考にしていただければ幸いです。

🔗 参考リンク

Discussion