Jinja2 SQL Templating — Conditional Blocks & Reusable Fragments

Jinja2 SQL Templating — Conditional Blocks & Reusable Fragments

Jinja2 lets you write SQL templates with conditional blocks, loops, and macros — keeping query logic readable while adapting to runtime conditions. It is the engine behind dbt, SQLFluff, and many query builders.

from jinja2 import Environment, BaseLoader
import psycopg2
from psycopg2.extras import RealDictCursor

env = Environment(loader=BaseLoader())

# ── Template with conditional blocks ─────────────────────────────
TWEET_REPORT_TEMPLATE = """
SELECT
    u.username,
    {{ "u.verified," if include_verified else "" }}
    COUNT(t.id)        AS tweet_count,
    SUM(t.like_count)  AS total_likes
    {% if include_impressions %},
    SUM(t.impression_count) AS total_impressions
    {% endif %}
FROM tw_users u
JOIN tw_tweets t ON t.user_id = u.id
WHERE t.retweet_of_id IS NULL
  {% if since %}
  AND t.created_at >= :since
  {% endif %}
  {% if hashtags %}
  AND t.id IN (
      SELECT th.tweet_id
      FROM tw_tweet_hashtags th
      JOIN tw_hashtags h ON h.id = th.hashtag_id
      WHERE h.tag IN ({{ hashtags | map("lower") | list | join(", ") | tojson }})
  )
  {% endif %}
GROUP BY u.username {{ ", u.verified" if include_verified else "" }}
HAVING COUNT(t.id) >= :min_tweets
ORDER BY total_likes DESC
LIMIT :limit
"""

def render_tweet_report(
    include_verified: bool = True,
    include_impressions: bool = False,
    since: str | None = None,
    hashtags: list[str] | None = None,
    min_tweets: int = 5,
    limit: int = 20,
):
    template = env.from_string(TWEET_REPORT_TEMPLATE)
    sql = template.render(
        include_verified=include_verified,
        include_impressions=include_impressions,
        since=since,
        hashtags=hashtags or [],
    )
    # Replace :param with %s for psycopg2
    params = {
        "since": since,
        "min_tweets": min_tweets,
        "limit": limit,
    }
    return sql, params

sql, params = render_tweet_report(
    include_verified=True,
    include_impressions=True,
    since="2024-06-01",
    hashtags=["#sql", "#python"],
)
print(sql)

Purchase this course to unlock the full lesson.

Sign up