そのほか
2024/11/29
與田 龍人

JinjaとPythonで学ぶSQLの動的生成

logo_Python

JinjaはPythonのテンプレートエンジンで、動的にSQLクエリを生成できる便利なツールです。複雑な条件や繰り返し処理をシンプルに記述できるため、SQLの作成やデータ分析の効率を大幅に向上させます。

はじめに

JinjaはPythonのテンプレートエンジンで、動的にSQLクエリを生成できる便利なツールです。複雑な条件や繰り返し処理をシンプルに記述できるため、SQLの作成やデータ分析の効率を大幅に向上させます。


セットアップ

Google Colabまたは任意のPython環境で以下のライブラリをインストールしてください。




!pip install Jinja2 pandas



この例ではSQLiteを使用してサンプルデータを作成し、Jinjaを使ったSQLクエリを動的に生成します。


サンプルデータの作成

以下のコードを実行して、SQLiteデータベースにサンプルデータを作成します。



import sqlite3
import pandas as pd

# SQLiteデータベースを作成
conn = sqlite3.connect(":memory:")

# サンプルデータ
sales_data = pd.DataFrame({
"sale_id": [1, 2, 3],
"customer_id": [101, 102, 101],
"sale_date": ["2024-01-01", "2024-01-02", "2024-01-03"],
"amount": [100, 200, 150],
})

customer_orders = pd.DataFrame({
"customer_id": [1, 1, 2],
"order_id": [1001, 1002, 1003],
"order_date": ["2024-01-01", "2024-01-02", "2024-01-02"],
"total_amount": [500, 300, 700],
})

product_sales = pd.DataFrame({
"product_id": ["A", "B"],
"region": ["East", "West"],
"sales_q1": [500, 300],
"sales_q2": [600, 400],
"sales_q3": [700, 500],
"sales_q4": [800, 600],
})

# データをSQLiteに挿入
sales_data.to_sql("sales_data", conn, index=False, if_exists="replace")
customer_orders.to_sql("customer_orders", conn, index=False, if_exists="replace")
product_sales.to_sql("product_sales", conn, index=False, if_exists="replace")

# 各テーブルの内容を確認
print("=== sales_data ===")
print(pd.read_sql("SELECT * FROM sales_data", conn))

print("\\n=== customer_orders ===")
print(pd.read_sql("SELECT * FROM customer_orders", conn))

print("\\n=== product_sales ===")
print(pd.read_sql("SELECT * FROM product_sales", conn))



 

1. Jinjaの基本構文

1.1 テーブル参照


Jinjaを使って動的にテーブルを参照します。



from jinja2 import Template

# テンプレート定義
sql_template = """
SELECT *
FROM {{ table_name }}
"""

# テンプレートをレンダリング
template = Template(sql_template)
rendered_sql = template.render(table_name="sales_data")
print(rendered_sql)

# 実行
pd.read_sql(rendered_sql, conn)



解説:



  • {{ table_name }} の部分に変数 table_name の値が挿入されます。

  • SQLクエリを動的に生成することで、複数のテーブルに対して同じ処理を簡単に適用できます。


1.2 変数の活用


変数を使ってクエリの条件を動的に変更します。



sql_template_with_var = """
SELECT *
FROM sales_data
WHERE sale_date = '{{ target_date }}'
"""

template = Template(sql_template_with_var)
rendered_sql = template.render(target_date="2024-01-02")
print(rendered_sql)

# 実行
pd.read_sql(rendered_sql, conn)



解説:



  • {{ target_date }} の部分に変数 target_date が挿入されます。

  • クエリ実行時に変数を変更することで、条件を柔軟に切り替えられます。





1.3 制御構文 (If文)


条件によってクエリの内容を動的に変更します。



sql_template_with_if = """
SELECT *
FROM customer_orders
{% if filter_customer %}
WHERE customer_id = {{ target_customer }}
{% endif %}
"""

template = Template(sql_template_with_if)
rendered_sql = template.render(filter_customer=True, target_customer=1)
print(rendered_sql)

# 実行
pd.read_sql(rendered_sql, conn)



解説:



  • {% if filter_customer %}filter_customerTrue の場合にのみ、WHERE 条件が追加されます。

  • 条件分岐を簡潔に記述できます。


1.4 ループの利用


複数のカラムを対象に同じ処理を行う場合、ループを使用します。



sql_template_with_loop = """
SELECT
region,
{% for quarter in quarters %}
SUM({{ quarter }}) AS total_{{ quarter }}
{% if not loop.last %}, {% endif %}
{% endfor %}
FROM product_sales
GROUP BY region
"""

template = Template(sql_template_with_loop)
rendered_sql = template.render(quarters=["sales_q1", "sales_q2", "sales_q3", "sales_q4"])
print(rendered_sql)

# 実行
pd.read_sql(rendered_sql, conn)



解説:



  • for 文を使うことで、複数のカラムに対する処理を効率的に記述できます。

  • loop.last を使うと、最後の要素にはカンマを追加しないよう制御できます。


1.5 マクロ風の再利用


同じ処理を繰り返し使う場合、Python関数をマクロのように活用できます。



def get_table_columns_sql(table_name):
return f"""
SELECT name AS column_name
FROM pragma_table_info('{table_name}')
"""

# マクロ呼び出し
sql_macro = get_table_columns_sql("sales_data")
print(sql_macro)

# 実行
pd.read_sql(sql_macro, conn)



解説:



  • 再利用可能なクエリ部分をPython関数として定義しています。

  • 必要に応じて異なるテーブル名でクエリを呼び出せます。



 






2. 応用: 環境に応じた切り替え

 


本番環境と開発環境で異なるテーブルを参照する場合の例です。



sql_template_env = """
SELECT *
FROM {% if environment == 'prod' %}production_table{% else %}dev_table{% endif %}
"""

template = Template(sql_template_env)
rendered_sql = template.render(environment="dev")
print(rendered_sql)



解説:



  • {% if %} 文を使って、環境変数に基づいてクエリの内容を動的に変更できます。



 


終わりに

Jinjaを使うことで、SQLの動的生成が簡単に行えることを体験していただけたと思います。この記事では、Pythonを使って基礎から応用まで実例を紹介しました。特に、変数や制御構文、ループを活用することで、柔軟で効率的なクエリの生成が可能になります。 SQLを書く際、似たようなクエリを何度も書くのは面倒ですが、Jinjaを使えばこれらの繰り返しを自動化できます。また、条件に応じた処理や環境ごとの切り替えも簡単に実現できるため、業務の効率化に大いに役立ちます。 ぜひこの記事を参考に、手を動かしながらJinjaを学び、日々のデータ分析や開発に取り入れてみてください。最初はシンプルな例から始めて、徐々に応用的な使い方に挑戦することで、Jinjaの便利さを実感できるはずです。


New call-to-action