
4 practical ways to use SQL and Python together for modern data analysis
TL;DR: Using a Fabi.ai Smartbook, start by querying your data using SQL. Then using SQL or Python, build strings, Pandas or Polars DataFrames and figures that you want to share with your team. Using the Fabi.ai email cell, you can then embed those in an email using a simple templating approach {{your_dataframe}}.
Ever needed to send regular analytics reports but found yourself frustrated with rigid, screenshot-filled emails from BI tools? We recently launched a new feature in Fabi.ai that make it incredibly easy to build and send emails just using SQL and Python.
In this tutorial, we’ll show you how to send your first email. If you prefer video tutorials, you can follow along here:
Most automated reports from BI platforms have some annoying limitations that reduce their effectiveness:
This means your carefully analyzed data often ends up ignored in someone's inbox because it's too cumbersome to extract meaningful insights.
The combination of Python's flexibility and SQL's data querying power creates opportunities for much more useful reports. Instead of sending generic dashboards, you can craft emails that automatically highlight exactly what stakeholders need to know.
Let's walk through how to build a weekly usage report that automatically highlights the most important changes in your data.
First, pull in your data with a simple SQL query in a Fabi.ai Smartbook:
SELECT * FROM users_data
This example uses a CSV with user information, creation dates, widget counts, and week-over-week changes. The beauty of starting with SQL is that you can transform and filter your data right at the query level before it even reaches your Python processing.
Create a chart to give your recipients a visual overview of trends:
import pandas as pd
import plotly.express as px
from datetime import datetime
# Create a copy of the dataframe to avoid modifying the original
df = dataframe1.copy()
# Extract month and year from the User creation date
df['Month-Year'] = df['User creation date'].dt.strftime('%Y-%m')
# Group by month-year and prospect/customer status, count users
user_counts = df.groupby(['Month-Year', 'Prospect or customer']).size().reset_index(name='Count')
# Create the stacked bar chart
fig = px.bar(
user_counts,
x='Month-Year',
y='Count',
color='Prospect or customer',
title='Total Number of Users Created Over Time',
labels={'Month-Year': 'Month-Year', 'Count': 'Number of Users'},
color_discrete_sequence=['#FF8A80', '#80D8FF'],
width=800,
height=450
)
# Improve layout
fig.update_layout(
xaxis_title='Month-Year',
yaxis_title='Number of Users',
legend_title='User Type',
barmode='stack'
)
# Display the chart
fig.show()
Visual elements like charts provide immediate context that numbers alone can't convey. They help recipients quickly grasp patterns and trends without having to analyze rows of data.
This is where things get interesting. You can use Python to identify and format your most important metrics.
First let’s filter the data on April users:
import pandas as pd
import numpy as np
# Convert the dataframe1 to a pandas DataFrame if it's not already
df = dataframe1.copy()
# Convert User creation date to datetime if it's not already
if not pd.api.types.is_datetime64_any_dtype(df['User creation date']):
df['User creation date'] = pd.to_datetime(df['User creation date'])
# Filter for users created in April 2025
april_2025_users = df[df['User creation date'].dt.year == 2025]
april_2025_users = april_2025_users[april_2025_users['User creation date'].dt.month == 4]
Then let’s look for the week-over-week change and add an emoji based on whether the change is positive or negative.
# Function to add emojis based on percentage change
def add_emoji(value):
if value > 0:
return f"{value}% ↗️ 🟢" # Up arrow and green dot for increase
else:
return f"{value}% ↘️ 🔴" # Down arrow and red dot for decrease
# Apply the emoji function to the percentage column
april_2025_users['Formatted Change'] = april_2025_users['WoW widget creation diff (%)'].apply(add_emoji)
This conditional formatting immediately draws attention to what's changed since the last report. The visual indicators make it easy to spot positive and negative trends at a glance, rather than forcing recipients to compare numbers manually. You could even enhance this by using Python to detect and flag anomalies. Tons of potential for advanced analysis and reporting.
And then finally we find the top users with highest and lowest changes:
# Create dataframes for top 10 increases and decreases
top_increases = april_2025_users.sort_values('WoW widget creation diff (%)', ascending=False).head(15)
# Filter for only negative WoW changes before sorting for decreases
negative_changes = april_2025_users[april_2025_users['WoW widget creation diff (%)'] < 0]
top_decreases = negative_changes.sort_values('WoW widget creation diff (%)', ascending=True).head(15)
# Select and rename columns for better readability
columns = ['User ID', 'Name', 'Number of widgets created', 'Formatted Change', 'Prospect or customer']
top_increases = top_increases[columns].rename(columns={'Formatted Change': 'WoW Change'})
top_decreases = top_decreases[columns].rename(columns={'Formatted Change': 'WoW Change'})
With that last piece, we now have two DataFrames: top_increases and top_decreases.
Now put it all together with a few lines of Python:
Hey there,
Here are the top users in terms of increased widget creation:
{{top_increases}}
Here are the top users in terms of decreased widget creation:
{{top_decreases}}
Users created by month:
{{fig}}
What makes this approach powerful is that your email content isn't static. It changes based on what's happening in the data. If there's a sudden spike in negative changes, that will be front and center. If a particular customer segment is showing unusual activity, you can have Python automatically highlight that segment.
In Fabi.ai, you can:
Once scheduled, these reports run without any manual intervention, saving hours of work while delivering more valuable insights.
Beyond the technical advantages, this approach fundamentally changes how your organization interacts with data:
When reports highlight what matters most, recipients can make faster, better-informed decisions. Instead of digging through data to find issues, they see immediately where attention is needed.
People are much more likely to engage with reports that are easy to understand and relevant to their specific needs. By customizing content based on roles or departments, you can ensure everyone gets exactly what they need.
This approach saves time for both report creators and recipients:
When reports automatically flag concerning trends, teams can address issues before they become serious problems. This shifts your organization from reactive to proactive data use.
If you're ready to try this yourself:
Not only is it easier to build better, more advanced data insights than you can in legacy BI with Fabi.ai Smartbooks, but sending these insights via email puts your work front-and-center and increases the engagement with data.
This approach has saved me hours of manually creating reports while actually delivering more useful information. The combination of Python's flexibility with SQL's data access makes for powerful, automated reporting that people actually want to read.
The best part is that once you've created one successful report, you can easily adapt the pattern for other metrics and departments. Your data becomes more accessible and actionable across the entire organization.
You can send your first email out in less than 5 minutes for free! And if you have any feedback or questions, please feel free to reach out: hello@fabi.ai