データ処理のタスクをこなしていると、Python で SQL に変数を挿入し柔軟に SQL クエリを構築したくなる。
例えば、
- 中間テーブルを作るために Airflow などで定期的なジョブを実行し、SQL の
created
の時間を当日のものに変更する training
,dev
,test
でデータを分割する際に、created
の条件を変更して 3 パターンのデータを取得
のような状況が考えられる。
変数を SQL に組み込んで実行したい際には、kayak/pypikaのような SQL builder もあるが、個人的には可読性が悪くなるし、SQL クエリの作成のためだけに余計なパッケージをいれたくない。
そのためパッケージを入れずに簡潔なクエリ構築方法をここでは紹介する。
編集履歴
- 2021/05/12: twitter で docstring ではなく string literal ですよという指摘をいただき修正 ref
- 2021/05/12: twitter での意見を反映
1. 単なる文字列として SQL クエリを構築
f-string で文字列に変数を挿入して、SQL クエリを構築する
欠点
- SQL が長くなると PEP8 に準拠せず、E501 line too longに抵触する
- 視認性が低く、SQL クエリの実行内容を理解しづらい
2. 複数行文字列として SQL クエリを構築
1 番目と比較すると、複数行を扱えるstring literal-longstringを採用することで、SQL クエリが複数行になることでで見やすい (@cocu_tan さん、ご指摘ありがとうございます!)
欠点
-
SQL 構文に関する lint や フォーマッターを活用できない
-
SQL へのシンタックスハイライトが無いので、視認性が低い
3. SQL ファイルを文字列として読み込み、 .format()
で変数を挿入する
SQL ファイルが ./sql/test.sql
に設置されている状態とする。
Python 側での実装例
実行結果
-
SQL クエリを
SQL
ファイルで管理できるので、フォーマッターやシンタックスハイライトの恩恵を活用できる。 -
ファイルオブジェクトに対して
.read()
メソッドを使うことで文字列として扱うことができる。そのおかげで、SQL ファイルに{var_1}
,{var_2}
のような形式で、Python 上の変数を SQL の挿入して柔軟にクエリを構築可能になる。
SQL ファイルの中に Python の .format()
記法を事前に埋め込むことが今回のコツですね。
余談
SQL ファイル内に
1 行目のようなコメントを入れてしまうと、SQL ファイルの中ではコメントとして評価される部分も Pythn の文字列内では.format()
の挿入対象となる。その影響で.format()
で挿入されるべき変数の数が 3 つと評価され、実際の引数は 2 つしか与えておらずエラーが発生する。
SQL ファイルのコメントであろうと、{}
と記述しないように気をつけておきましょう。
追記
Twitter でみんなの管理方法をお聞きできたので、記しておく。
ありがたい。
@shuhei_fujiwara さん
BigQuery の話だけど僕は
- ほとんどの場合 ⇒ parameterized query
- 本当に短くて自明なクエリ ⇒ 1 or 2
- どうにもならんとき ⇒ 3
https://cloud.google.com/bigquery/docs/parameterized-queries
3 はわかるんだけど、Python コードで読み込む前提よりは SQL ファイル単体で成立している方が好きなので可能なら避けてます
https://twitter.com/shuhei_fujiwara/status/1387815866436243458
単体で成立したい気持ち、たしかに分かる~
@satoshihirose さん
Airflow のみの話にはなってしまいますが、sql はファイルとして管理して基本 jinja template 対応のオペレーター使ってましたね https://qiita.com/munaita_/items/6bdcfb10f36c8c6b4753 > https://twitter.com/satoshihirose/status/1387776005943840772
Airflow のときに jinja 使えるんですね。今度から使います!
@reto_nayuta さん
私も(Airflow ではないですが)jinja 使うことが多いですね
@SassaHero さん
3 を、from string import Template で変数置き換えを実現してます!
from string import Template
知らなかった