🦆

tabpy × duckdbを使ってTableau上でSQLを使う。

2025/02/15に公開

まえがき

Tableau上でSQLを使いたいと思ったことはありませんか?
正直Tableauの計算フィールドでの固有関数の挙動は覚えるしかなくて手間なので何かいい方法はないかと考えていました。

そこでTableau Desktop上でpython版duckdb経由でSQLを使用する方法を考えて試してみました。

前提

Tableau Desktop(Publicではない)版が必要です。
無料でトライアル版が14日使用できるみたいです。
使用しているpython環境にduckdbがインストールされていることが必要です。

tabpyのインストール

pip install tabpy

でインストール完了します。
そのあとtableau上からlocalhostに接続しないといけないので
以下のコマンドでlocalhostを立ち上げます。

tabpy

すると以下のような表示が出ます。
(Do you wish to proceed without authentication? (y/N):はyを押す。)

Do you wish to proceed without authentication? (y/N): y
2025-02-15,20:57:39 [INFO] (app.py:app:510): Password file is not specified: Authentication is not enabled
2025-02-15,20:57:39 [INFO] (app.py:app:429): Call context logging is disabled
2025-02-15,20:57:39 [INFO] (app.py:app:197): Initializing TabPy...
2025-02-15,20:57:39 [INFO] (callbacks.py:callbacks:43): Initializing TabPy Server...
2025-02-15,20:57:39 [INFO] (app.py:app:201): Done initializing TabPy.
2025-02-15,20:57:39 [INFO] (callbacks.py:callbacks:64): Initializing models...
2025-02-15,20:57:39 [INFO] (app.py:app:167): Web service listening on port 9004

この状態で以下のURLにアクセスします。
(localhostなのでオフラインでお使いのパソコンのローカル環境のネットワークに接続します。)
http://localhost:9004/

するとこのような画面が出るので接続が確認できます。🙌

SQLをTableau上で使う

設定

Tableau Desktopを開いたら以下の画像のようなヘルプから設定とパフォーマンス→
分析の拡張機能接続の管理を開きます。

すると以下の画面が開くのでTabpyを選択。

そして以下のような設定にしてOK

SQLの記述

とりあえずサンプルストアの売上の合計を受け取って返すだけのクエリ

Tableauの計算フィールドで以下のようなSQLを記述します。

SCRIPT_REALはTableau内でpythonコードを使用する際に必要みたいです。
とりあえずサンプルストアのSUM[売上]を受け取って返すだけのSQL文です。

SCRIPT_REAL(
"
import duckdb
import numpy as np

# _arg1 を NumPy 配列に変換
arr = np.array(_arg1)  # _arg1 は 2D NumPy 配列を想定


con = duckdb.connect(database=':memory:')

# SQL クエリを実行(引数で渡ってきた合計をそのまま表示)
sql = duckdb.sql('SELECT column0 AS total FROM arr').fetchnumpy()


# 結果 辞書型で帰ってきているのでキー指定してリスト化
result = sql['total'].tolist()  

# 結果を返す
return result
",
SUM([売上])
)

_arg1として受け取ってきているSUM([売上])をnumpy配列に変換します。
そのあとduckdbでnumpy配列を受け取ってクエリ操作をして、numpy配列で再度返します。
最後にresultとして出力できるようにtolist()でリストを返却します。

ただし実行時間が50秒くらいかかります...🙃

利益/売上を返すクエリ

SCRIPT_REAL(
"
import duckdb
import numpy as np

# _arg1 を NumPy 配列に変換
arr = np.array(_arg1)  # _arg1 は 2D NumPy 配列を想定
arr2 = np.array(_arg2)

con = duckdb.connect(database=':memory:')

# SQL クエリを実行(引数で渡ってきた合計をそのまま表示)
duckdb.register('data', {'売上': arr, '利益': arr2})
sql = duckdb.sql('SELECT (利益 / 売上) * 100 AS profit_margin FROM data ORDER BY profit_margin ASC').fetchall()

 
# 結果をリストに変換
result = [row[0] for row in sql]  

# 結果を返す
return result
",
SUM([売上]),SUM([利益])
)

今回は二つ引数を持ってきているのでその二つをnumpyに変換してduckdbで一時的なテーブル作成。
その後クエリ操作してます。
今回は辞書型になっているのでrow[0]で値を取得して返却しています。

これも実行時間が50秒くらいかかります...🫠

ただ正直このくらいの計算ならTableau上でめちゃくちゃ簡単にできるので、今のところSQLを使っている人がTableau上の計算フィールドの書き方を覚えずに計算を行えるという利点しかないですね。(実行時間と引き換えに)

このコードも先ほどのコードも大体50秒くらいでそんなには時間に差はないので複雑な計算をSQLで済ませるという場合なら需要あるかもしれないですが...

あとはcsv保存とかS3接続とかできると思いますが、受け取れるのが引数なので...あえて引数を使用しないとか...?

もし活用法があれば試してみてください。🙌

Discussion